OOP 5대 설계원칙 강좌

GMRyuj의 이미지

SRP vs ISP (SRP 원칙과 ISP 원칙의 모순?)

분명 SRP와 ISP는 처음 적용할때 모순이 존재하는 것처럼 보인다.
하지만 명백히 SRP와 ISP는 다르다.

SRP : 단일 책임의 원칙 => 즉, 클래스는 하나의 책임을 가진다.
ISP : 인터페이스 격리의 원칙 => 클래스의 사용하지 않는 기능은 인터페이스로 분리되어야한다. 또는 클라이언트는 자신이 사용하지 않는 메소드에 의존관계를 맺으면 안된다. 라는 깊은 뜻이 담겨있다.

여기서 자신이 사용하지 않는 메소드라는 문장에 깊은 관심을 가질 필요가 있다.

여기서 생기는 혼돈(?)은 SRP는 단일 책임을 지닌다고 하였는데.. ISP에 의해 나뉘어진 인터페이스를 구현하면 다수의 책임을 가지고 있는 것이 아닌가? 라는 의문이 생기기 마련이다. 대부분의 설계 원칙을 처음 접한 사람들은 분명 이러한 고민을 해봤을 것이다.
하지만 이것은 SRP와 ISP의 관계를 확실하게 파악하지 못했기 때문이다. 예로 설명하겠다.

게임 오브젝트(GameObject)는 자신을 관리하는 책임을 가진다. (여기서 관리한다라는 말은 그리기와 업데이트가 될 수 있다는 뜻이다. 즉, Update() , Draw() 메소드를 가진다.)
여기서 Update와 Draw 메소드는 다른 객체에서 호출될 수 있다. 정리하자면 GameObject의 Update와 Draw 메소드는 자신이 직접 호출하지 않는다는 뜻이다. 이 말을 간단하게 말하면 GameObject의 Update, Draw 메소드는 다른 클래스 혹은 다른 로직에서 호출될 수있다는 뜻이다. 그렇다면 이 Update, Draw 메소드를 클라이언트는 자신이 사용하지 않는 메소드에 의존관계를 맺으면 안된다. 의 ISP 원칙을 적용하여 인터페이스로 분리한다.

따라서 인터페이스 IDrawable : Draw() , IUpdateable : Update() 인터페이스로 분리해내고, GameObject 는 이 인터페이스 둘을 구현한다.
그리고 게임의 흐름에서 GameObject는 Draw와 Update메소드는 호출되어진다.(이때 DIP의 원칙이 적용될 수 있다.)

이로써 SRP와 ISP의 원칙은 모순이 존재하지 않는다는 것을 알게되었다.
그러면 다음으로 DIP 원칙을 위의 예제에 적용해보겠다.

DIP : 추상 클래스는 구체 클래스에 의존해선 안된다. 둘 다 추상 클래스를 의존해야 한다. (여기서 추상클래스는 인터페이스도 포함한다.) => 바꿔말하면 구체클래스가 추상클래스를 의존해야 한다. 라는 말이 된다.

앞서 설명한 GameObject 클래스의 Update 와 Draw 메소드는 자신이 호출하지 않는 메소드이다. 따라서 ISP 원칙에 의해 인터페이스 IDrawable : Draw() , IUpdateable : Update() 로 분리되었다. 그리고 GameObject 이 두 개의 인터페이스를 구현한다. 그렇다면 이 두개의 메소드는 어떻게 호출되어질까..

이에대한 답은 디자인 패턴에도 존재한다. 바로 Observer 패턴이다.

Opserver 패턴은 자신을 Obserable에게 등록하고, Obserable로부터 자신의 메소드가 호출 될 수 있도록 한다. (한마디로 이벤트 발생시 능동적으로 호출됨.) 다시 예제로 돌아가보자.

여기에서 존재하는 IDrawable : Draw() , IUpdateable : Update() 인터페이스중 IDrawable 인터페이스에 대한 예를 들어보자.

GameObject는 이제 IDrawable 인터페이스를 구현한다. 따라서 GameObject 는 반드시 Draw() 메소드를 구현하여야 한다. 그리고 DrawObserver 라는 클래스에 자신을 등록한다.

DrawObservable(Observable : 옵저버를 관찰 할 수 있다.) 는 다음과 같은 메소드를 가지고 있다.

RegisterObject(IDrawable drawable); //객체를 보관.
NotifyObject(); //보관된 객체의 Draw 메소드 호출.

위의 메소드를 통해 DrawObservable.RegisterObject(GameObject) 이런식으로 등록이 된다면 GameObject 는 DrawObservable.NotifyObject 로 인해 Draw 메소드가 호출될것이다.

이때의 적용된 원칙은 DIP 원칙이 적용되었다. 분명 DrawObserable은 GameObject 의 Draw 메소드를 호출하기 위해 IDrawable 인터페이스에 의존할 것이다. GameObject 자신이 Draw 메소드를 호출하지 않는다. DIP원칙을 준수하고 있다.
만일 DrawObserable이 구체적인 클래스인 GameObject에 의존하게 되면 어떻게 될까..? 다른 Draw 될수있는 모든 다른 객체들은 DrawObserable에 의해 그려질 수 없을 것이다. 가령 GameObject 를 상속받지 않은 다른 그려질 수 있는 것들은 그려질 수 없다. 이를 수정하려면 반드시 GameObject를 상속받아 구현하거나, 혹은 DrawObservable2를 만들어야 할지도 모른다. 하지만 DrawObservable 클래스가 IDrawable에 의존한다면 인터페이스로 분리된 IDrawable 을 상속받은 모든 객체들은 DrawObservable에 의해 그려질 수 있게 될 것이다. 이것이 DIP 원칙을 준수하는 이유이다.

LSPOCP 최종단계 설명

위에서 우리는 SRP , ISP , DIP를 알아보았다.
지금까지 만들어진 클래스로는 IDrawable, DrawObservable, GameObject 클래스들이 있다.

위 클래스 다이어그램을 보면 IDrawable 을 포함하고, IDrawable 에게 Notify 를 해주는 것을 알수있다. 여기 우리는 OCP와 LSP를 적용해보겠다. 우선 OCP와 LSP의 원칙을 보면

4. 리스코프 대체 원칙(LSP: Liskov Substitution Principle) -> 기반클래스는 파생 클래스로 대체 가능해야 한다. -> 상속관계에서 부모 자식 간에는 is-a 관계가 성립해야 한다. ( 동물은 개다. != 개는 동물이다. )
5. 개방폐쇄 원칙(OCP: Open-Closed Principle) -> 모듈(Service)은 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다. -> 변경의 확산을 막아 주고 확장의 포인트가 되는 인터페이스가 새로운 요구사항을 반영할 수 있도록 견고하게 계 되어야 한다

LSP 와 OCP는 서로 조화를 이루기에 좋은 원칙이다. 위의 클래스에서 우리는 IDrawable 인터페이스를 통해 확장을 할 수 있다. 이 게임에 GameObject 말고 GameInterface라는 클래스를 추가해보자. GameInterface는 사용자에게 보여지는 게임상의 인터페이스이다. (스타크래프트의 어택, 홀드, 정지, 스킬을 표시해주는 아래쪽에 있는 인터페이스같은..)

위 다이어그램을 보면 GameInterface는 IDrawable 인터페이스를 상속받아 +Draw 라는 메소드를 가진다. 그리고 DrawObserver에 GameInterface 객체를 등록하여 Draw 메소드가 호출되어 질수 있다. 클래스의 관계도를 바꾸지 않고, IDrawable 인터페이스를 활용해 프로그램을 확장했다. 이처럼 그려질것이 있다면 IDrawable을 상속받아 구현하고, DrawObserver에 등록하면 그 객체는 그려질수가 있다. 이로써 OCP 원칙이 성립되었다.
동시에 LSP원칙 또한 성립이 되었다.

LSP 원칙 : 기반클래스는 파생 클래스로 대체 가능해야 한다.

아래의 코드를 살펴보자.

IDrawable : //IDrawable 인터페이스
abstract draw();

DrawObserver : //DrawObserver 클래스
Regiseter(IDrawable drawable){ drawable.draw(); //IDrawable 인터페이스로 받고있다.(DIP 원칙)
}

GameObject monster1 = new GameObject();
GameInterface zergInterface = new GameInterface();

drawObserv.Register(monster1);
drawObserv.Register(zergInterface);

위 코드를 보면 기반 클래스(IDrawable)은 파생클래스(GameObject, GameInterface)로 대체 가능하다. 이로써 LSP 원칙 또한 성립이 되었다.

지금까지 OOP 5대 원칙을 모두 살펴보았고, 실제 예문을 통해 OOP 원칙이 어떻게 성립하는지 알아보았습니다. 부족한 설명이었지만.. 저의 설명이 완벽하다고는 할 수 없습니다.. 위의 설명중 틀린 부분이 있으면 반드시 댓글을 달아 잘못된 부분을 지적해주기 바랍니다.

이상 Ryuj 였습니다. 부족한 글 읽어주셔서 감사합니다.

구글 문서로 보기 : https://docs.google.com/document/pub?id=1ez8rYUj2L3Nqg_7OtuPBV-t4NGTWQLUgO24FOAI_slc

Forums: 
익명 사용자의 이미지

.

shint의 이미지

변수에 값 대신. 함수 포인터를 넣어서. 그것을 실행 시킨다. 가 주된 내용으로 보입니다.
인터페이스'란 그런것이다. 라고 알려주는것 같습니다.

AOP (Aspect Oriented Programming)
- 다형성 (Polymorphism)
- 가상화 (virtual)
- 함수 포인터 (function pointer)
- 상속 (Inheritance)
- 순수 가상함수 (pure virtual function)
- TTC (Thin Transaction Client)

시키는 동작을 실행한다. 마치. 로봇 같습니다. ㅡ_ㅡ;;;

하튼. 응용분야로는
- 동적인 화면구성. 동적인 응답처리. 서버 이중화. 데이터 파서. 등등... 매우 쓸모 있는 방식이기도 하지만.
- 해킹등에 잘못 악용 될 수 도 있는 매우 위험한 방식입니다.

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.