1. OCP (Open closed principle)

 버틀란트 메이어박사가 1998년 객체지향 소프트웨어 설계라는 책에서 Open/Closed Principle 언급함.
 http://en.wikipedia.org/wiki/Open/closed_principle#Meyer.27s_Open.2FClosed_Principle

 " 소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수등 )는 확장에 대해서는 개방되어야  하지만
 변경에 대해서는 폐쇄되어야 한다고 언급했습니다."
 먼저 이원리를 설명하기전에, 부절적한 예를 들어 보겠습니다.

예 : 휴대전화와 충전기의 관계
http://www.zdnet.co.kr/ArticleView.asp?artice_id=00000039134727
최상훈(핸디소프트) - 마소에 기재된 글

인용문 =>
휴대전화마다, 충전기가 달라서, 휴대전화변경시 충전기도 같이 변경해야 하는 불편함을
충전기 자체를 24핀 표준화하므로써,
이제 휴대전화만 사고 충전기는 재사용할 수 있게 됐다.
따라서 휴대전화의 여러 종류에는 ‘개방하지만’
충전기의 쓸데없는 생산은 ‘닫아두는’ 효과를 얻은 것이다.
바로 이번 호에서 소개할 개방-폐쇄의 원칙을 잘 반영한 결과라고 생각한다.

=>위의 예는 ocp 원칙을 설명하기에는 2% 부족하거나, 잘못된 예라고 볼수 있습니다.
왜냐하면, 이예제는 변경되는 부분은 표준화(규격통일)해라.
그러면, 재사용성이 높아진다 라는 것이지만,
그러면, 클래스등 s/w요소를 개발할때, 표준화하면서 개발해라. 라는 결론을
도출할 위험성이 있기때문입니다.

위의 예를 근거로 클래스를 설계하면서, OCP 원리를 지켜라 라고 하면 생뚱맞아 집니다.
왜나하면, 클래스를 표준화하면서, 개발해라. 아니면, 개방 폐쇄원리를 적용하면서, 개발해라 것은
너무 추상적이기 때문입니다.

그럼 메이어박사가 이야기한  OCP에서 강조하고 있는 것은 무엇일까요?
풀이하면,  요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며,
기존 구성요소를 쉽게 확장해서 재사용할수 있어야 한다는 뜻입니다.
그러기 위해서는 구성요소의 단위속성중 외부로 노출할것과 노출하지 말것을 구분하여야 한다는 겁니다.
즉,  변하지 않을 필수적인 단위속성과 변할 가능성이 있는 비 필수적인 단위속성을 구분해서,
필수적인 요구사항만, 구성요소(클래스)안에 구현하고, 나머지는, 구현자체를 생략하라는 겁니다.

예를 들어 전자제품을 설계해보도록 하겠습니다.
전자제품의 속성을 먼저 나열해보면
 1. 전기를 필요로한다.
 2. 끄고, 켤수있다.
 3. 작동하고, 멈춘다.
 4. 소리가 난다.
 5. 통신을 한다.
 6. 방송을 본다.
 7. 빨래를 한다.
 8. 시원하게 한다.
 9. 게임을 한다.
10. 복사를 한다.
....

전자제품의 속성은 위의 경우말고라도 무수히 많이 존재합니다.
여기에서  1번부터 3번까지는 전자제품의 고유한 기능이고,
4번이하는 개별전자제품의 기능이라고 분리할수 있습니다.

<?php
class electronic  {
  private $bolt  = '' ;
  public function setBolt($input) { $this->bolt = $input ; }
  public function getBolt()        { return $this->bolt ;    }
  public function powerOn()      {}
  public funciton powerOff()      {}
  public function play()            {}
  public funciton stop()            {}
}
?>
1번부터 3번까지의 고유한 속성만을 모아서 전자제품 클래스를 만들었습니다.

이제 tv라는 전자제품을 만들어라는 추가 요구사항이 발생하였습니다.
tv는 리모컨으로 파워를 온,오프라는 기능이 있습니다.

<?
class tv extends electronic  {
  public function setChannel() {}  //추가기능
  public function setVolumn() {}    //추가기능
  private function remote($mode) { }  //추가기능 : 리모트 기능
  public funciton powerOn() { this->remote('on'); }
  public funciton powerOff() { this->remote('off'); }
}
?>
상속의 다형성을 이용해서
추가요구사항 발생에도 불구하고, 기존 클래스인 electronic 수정이 발생하지 않고도,
쉽게 tv 클래스를 만들수 있습니다.

=>메이어박사가 언급한  ocp 개념에서는  상속에 의한 다형성이 중요하였지만,
요즘은, 추상인터페이스 방식의 ocp원칙을 중요시 하고 있는것 같습니다.(이부분은 예제생략)

정리) 보통 객체지향 입문자의 경우, 발생할지 안할지 모르는 요구사항을 미리 고민해서,
 클래스 설계에  반영해야 한다는 강박관념이 있는듯 합니다.
 이 원리에서 강조하고 있는것은, 발생할 모든 요구사항을 파악하는 것이  중요한것이 아니라,
 변경되지 않을 필수적인 요구사항만 파악해라는 뜻으로 생각할 수 있습니다.

  ocp 적용설계시 주의할점3가지
  -  확장되는 것과 변경되지 않는 모듈을 분리하는 과정에서 크기 조절에 실패하면  관계가 더 복잡해진다.
  -  인터페이스는 가능하면 변경해서는 안 된다
  -  인터페이스는 본질적인 특성만 추상화해야 한다.


2.  SRP (The Single responsibility principle) - 단일책임원리
http://en.wikipedia.org/wiki/Single_responsibility_principle
(Robert C. Martin 언급함)

Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to change
(첵임이란, 변경해야 할이유이다. 이것은 클래스 또는 모듈이 변경할 이유가 단 한가지만 가져야 한다는 것을 의미한다.)

모든 객체는 단일책임을 가져야 한다.라고 언급하였습니다.
즉 클래스나 모듈은 단지 하나의 이유에 의해서만 변경되어야 한다는 것을 강조하는 원리입니다.

두개이상의 책임을 하는 경우, 변하는 책임에 의해 변하는 않는 책임도 영향을 받아서,
연쇄적인 수정이 발생할 가능성 또는 위험성을 미연에 방지하자는데 있습니다.
얼핏보면,  SRP는 OCP와 비슷해보이지만, OCP는 클래스에 존재해야 할 책임의 범위를 규정하는 것이라면,
SRP는 단일 책임을 강조하는 겁니다.
여기서 단일책임은 메소드 한개만을 의미하지는 않습니다.

예) 로그인 처리는 아이디 및 패스워드로 인증여부를 체크후,
인증에 성공하면, 세션이나 쿠키로 인증사실 기록후, 페이지 처리하는 행동을 합니다.

즉 loginProcess 는 다음과 같이  3개의 책임(행동)이 존재할수 있습니다.

<?php
class LoginProcess {
  //1.로그인처리
  function loginCheck($id,$password) {}
  //2.인증처리
  function saveAuthority()  {}
  //3. 페이지처리
  function movePage() {}
}
?>

=>SRP 원칙에 의거 클래스를 분리하겠습니다.

<?php
class Login {
  function Check($id,$password) {}
}

class Authority {
  function save()  {}
  function delete() {}
}

class PageControl {
  function go()    {}
  function back() {}
}
//구현 및 사용법 생략
?>


정리) 클래스나 모듈은 단지 하나의 이유에 의해서만 변경되어야 한다는 것을 강조하는 원리입니다.
위에서는 LoginProcess클래스는 3가지 이유에 의해 변경될 가능성이 있습니다.



3. LSP (The Liskov Substitution Principle) 리스코브의 치환 원리
(http://en.wikipedia.org/wiki/Liskov_Substitution_Principle :위키)

 바바라 리스코브(Barbara Liskov) 1987년에 언급함.
 Let q(x) be a property provable about objects x of type T.
 Then q(y) should be true for objects y of type S where S is a subtype of T.

 함수q(x)에서 클래스 T의 객체 x가 잘 작동한다면,
 T클래스의 서브클래스인 S클래스의 객체 y도 q(x) 매소드에서 잘 작동되어야 한다는 원리이다.
 따라서, 리스코브의 정의에 의하면, 만일 S가 T의 서브클래스라면,
 프로그램상에서  T클래스의 객체는 S 클래스의 객체로 변환하더라도  어떠한 변경이 없어야 한다.

 LSP는 당연하다고 할수 있으나, LSP원칙에 위배되는 코딩을 자주 목격하게 됩니다.

 위배되는 경우는 주로 다음의 3가지 경우입니다.
 첫째, 하위클래스의 동일한 메소드가 유저가 요구하는 메소드로서, 행동하지 않는 경우,  즉 무늬만 같고, 행동이 아예 다른 경우
 둘째, 하위클래스의 메소드가 존재하지 않는 경우
 셋째, 상위클래스와 하위클래스의 형타입 맞지 않는 경우로 요약할 수 있습니다.

 LSP의 의미는  상속받는 하위 클래스는 상위 클래스의 책임을 넘지 말아야  하며,
 하위클래스는 유저가 요구하는 행동되는 설계되어야 한다는 겁니다.
 즉 다형성을 오용해서 사용하면 안된다는 것과, 다형성이 작동안되게끔 하면 안된다는 것임.


4. DIP(Dependency inversion principle) 의존관계역전의 원리
http://en.wikipedia.org/wiki/Dependency_inversion_principle
 Robert C. Martin가 언급함.

High level modules should not depend upon low level modules.  Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions
높은레벨의 모듈은 낮은레벨의 모듈을 의존하면 안된다. 서로 추상에 의존해야 한다.
추상은 구체적인것에 의존하면 안되고, 구체적인것은 추상에 의존해야 한다.

흔히, 상위개념은  하위개념을 포함하게 된다,
예를 들어, 고양이과 동물에는 호랑이, 사자,표범 등등 동물들이 포함된다.
즉 고양이라는 상위개념은 하위개념인, 호랑이, 사자,표범등을 다 포괄하는개념이다.

하지만, S/W설계에서는, 개념상 포함되어 있다고 하더라도, 상위개념이 하위개념 모두를 표현하거나,
참조하는 것은 나쁘고, 하위개념이, 상위개념을 참고하는 것이 좋다.
즉 이런것을 의존관계가 역전되어 있다고 표현합니다.

예) 고양이과에는 호랑이, 사자, 표범등이 있다. ( 안좋은 참조의 예)
호랑이는 고양이과이다. (좋은 참조의 예)

나쁜 의존관계
<?php
  class tiger    {}
  class lion      {}
  class cheeta {}

  class cat {
    var $tiger ;
    var $lion ;
    var $cheeta ;
    function cat() {
        $this->tiger = new tiger ;
        $this->lion = new lion ;
        $this->cheeta = new cheeta ;
    }
  }
?>
상위개념인 고양이 클래스에서 하위개념인 타이거, 라이온, 치타를 참고하고 있습니다.
구현을 생략했지만, 틀림없이, 불필요한 중복코드가 많아 생깁니다.

객체와 객체의 참조관계에서 is a 관계이면 상속, has a 관계면 합성(위임)의 방법으로 설계해야 합니다. 지금은 is a 관계이기 때문에 상속으로 해결해야 합니다.

<?php
  class cat { }
  class tiger  extends cat  {}
  class lion  extends cat    {}
  class cheeta  extends cat{}
?>

DIP원칙을 지키면
상위모듈개발할때, 하위모듈 개발을 미리 할 필요가 없다는 것입니다.
즉, TopDown 접근방식으로 설계 및 개발이 가능하다는 겁니다.

DIP원리를 설명하면서, 헐리우드 원리가 설명되기도 하는데, 관련은 있지만,
완벽하게 일치 하는 개념은 아닙니다.

헐리우드원리란 배우지망생이 많아서, 오디션 담당자가 전화응답이 너무 힘들어
거꾸로, 오디션 합격자에게만, 전화를 거는 현상을 이야기 합니다.
전화하지 마세요. 우리가 연락할께요..

헐리우드원리예를 보면
온라인 채팅서버와 클라이언트의 관계를 보면, 잘 알수가 있습니다.
채팅정보를 원하는 클라이언트는 채팅서버에게 채팅정보를 게속 요구합니다.
하지만, 서버가 클라이언트에게 전달한 정보가 없다면, 불필요한 서비스요청을 하게 되는 겁니다. http 같이, 비연결성 프로토콜경우 이러한 현상을 잘 나타나는데,
서버가 클라이언트와의 연결정보를 유지할수 없기때문에,
부득이하게, 클라이언트가 몇초단위로 요구하게 됩니다. 즉 불필요한 서비스요청이 게속 발생하게 됩니다.  그래서 이를 해결하기 위한 방법이 서비스제공자인 서버가, 서비스요청자인 클라이언트에게 서비스를 거꾸로 전달하는 겁니다. 이러한 원리를 헐리우드원리 또는 제어권의 역전 현상이라고 이야기 합니다.

이러한 제어권의 역전현상 모두를 상위개념과 하위개념으로 나눌수 없기때문에
DIP 원칙이다 하기에는 무리가 있지만  밀접한 관련이 있는것은 사실입니다.

참고) 대규모의 채팅서비스는 서버소켓방식 통신이 적합하고, 소규모의 채팅서비스는, http통한 서비스도 가능할것 같습니다.


5.  ISP (The Interface Segregation Principle) 인터페이스 분리원칙

ISP원리는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리입니다.
즉 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 한다는겁니다.

예를 들어보겠습니다.
IWorker 인터페이스에는  work() 메소드와 eat() 메소드가 있습니다.
정규직은, 일하면서, 점식식사를 하지만,
파트타임 알바는 점식식사가 제공되지 않습니다.
이경우 알바는 eat()라는 메소드가 불필요함에도 불구하고,
구현을 해야합니다. 즉, 인터페이스가 분리가 안되어 나오는 문제입니다.
<?php
interface IWorker {    
  public function work();    
  public function eat();
}

//정규직
class standardWorker implements IWorker{    
  public function work() {//todo }    
  public function eat()    {//todo}    
}

//알바
class partTimeWorker implements IWorker{    
  public  function work() {//todo }    
  public  function eat()    {//사용하지 않음}    
}
?>

=>수정

<?php
interface IWorkable  {    
  public function work();    
}

interface IFeedable{    
  public  function eat();
}

//정규직
class standardWorker implements IWorkable,IFeedable{    
  public  function work() {//todo }    
  public  function eat()    {//todo}    
}

//알바
class partTimeWorker implements  IWorkable{    
  public function  work() {//todo }    
}
?>
즉 파트타임 노동자는 불필요한 메소드를 구현하지 않게 되었습니다.

ISP 인터페이스 분리원칙는 SRP 원칙과 관련이 있지만,  SRP는 클래스의 단일책임을 강조하는 거라면
ISP는 인터페이스의 단일책임을 강조하는 겁니다.


=========================================
이상으로 객체지향 5가지 원칙을 설명하였습니다.

디비졍규화가 있듯이 앞서 설명한바와 같이 객체지향설계에도 지켜야 할 5가지 원칙이 있습니다.
이러한 5가지 원칙이 앨랜케이가 스몰토크를 만들데, 개념화한것이 아니라,
수십년 지난 이후, 여러 고수 개발자들이 객체지향설계을 하면서 얻은 경험을 바탕으로
책이나 잡지에 발표한것이라서, 학문적으로 표준화되어 있지는 않는것 같습니다.

우째든 객체지향 설계 및 개발을 하면서 5가지원칙을 모두 지켜서, 프로그래밍 할수는 없지만,
원리가 지향하는바에 대한 이해와 적용노력을 하는 것이 좋다고 확신합니다.

참고사항

여기서 잠시 앨런케이가 1971년 스몰토크 개발하면서, 설명한 객체지향의 정의는 아래와 같습니다.

1. 모든것은 객체이다.(Everything Is An Object)
2. 객체들은 메세지를 주고 받음으로써, 서로 대화한다.(Objects communicate by sending and receiving messages (in terms of objects))
3. 객체는 고유의 메모리를 가진다.Objects have their own memory (in terms of objects).
4. 모든 객체는 클래스의 인스턴스이다.Every object is an instance of a class (which must be an object).
5. 클래스는 그들의 인스턴스의 공통된 형태를 가진다.The class holds the shared behavior for its instances (in the form of objects in a program list)

http://c2.com/cgi/wiki?AlanKaysDefinitionOfObjectOriented
(참고 : 모든 사유의 대상은 클래스의 인스턴스라는 명제는 고대서양철학자 플라톤의 책에 나온다고 합니다.)

즉 앨런케이가 객체지향이라는 용어를 컴퓨터 영역으로 가지고 왔지만,
설계원리에 대한 어떠한 원칙등을 언급도 하지 않아,
현재 개발자가 고생하는 것 같습니다.


by darkLittleSoldier

ps) 객체지향 설계5원칙에 관한 블로그 포스트마다, 뉴앙스나, 예제가 상이하거나, 부적합경우가 종종 있어서, 제 나름대로 정확한 설명이나 예제를 만들어 볼려고 시도했으나, 저역시도, 한계가 있는것 같군요..  우째든 제글이 다른 포스트를 보는데, 도움이 되었으면 합니다.

'개발도 하냐?' 카테고리의 다른 글

Trac, Ticket system과 workflow의 이해  (0) 2009.07.27
GDT  (0) 2009.07.27
yum repository setting for DAG  (1) 2009.07.12
Changing Oracle9i Database Language  (0) 2009.07.08
위젯시장전망  (0) 2009.06.25

+ Recent posts