Design Patterns2011. 11. 15. 18:14

이것 역시 생성 패턴 이다.
Abstract Factory 과 다른 점은 Abstract Factory 패턴은 불의 정령, 물의 정령의 공격, 방어 에 관련한 행위 객체를 추상화 하여 생성한 것이고, Builder 패턴은 불의정령, 물의정령 객체를 만드는 부분을 추상화한 패턴인다.

쉽게 정리하면 Abstract Factory 사용할 각 객체를 세트로 생성하는 것이고, Builder 패턴은 하나의 완성된 객체를 만드는 과정을 추상화 한 것이다.


Director = MonsterDirector

Builder = IMonsterBuilder

ConcreateBuilder1 = FireMonsterBuilder
ConcreateBuilder1 = IceMonsterBuilder

Product = IMonster

struct IAttackType
{    
    virtual void Attack() = 0;
};

struct FireAttack : public IAttackType
{
    void Attack() { cout << "공격: 불을 뿜다 화이아~" << endl; };
};

struct IceAttack : public IAttackType
{
    void Attack() { cout << "공격: 얼음 화살 ㄱㄱ" << endl; };
};

/////////////

struct IDefensType
{
    virtual void Defenns() = 0;
};

struct AvoidDefens : public IDefensType
{
    void Defenns() { cout << "방어: 회피 하기" << endl << endl; };
};

struct IceShieldDefens : public IDefensType
{
    void Defenns() { cout << "방어: 얼음방패 막기" << endl << endl; };
};

/////////////

struct IMonster
{
    IAttackType* attack_type_;
    IDefensType* defens_type_;
};

struct FireMonster : public IMonster
{
    
};

struct IceMonster : public IMonster
{
 
};

/////////////

struct IMonsterBuilder
{
    virtual void BuildAttackType()  = 0;
    virtual void BuildDefensType()  = 0;
    IMonster* GetResult() {return monster_;};

    IMonster*   monster_;    
};

struct FireMonsterBuilder : public IMonsterBuilder
{
    FireMonsterBuilder() {monster_ = new FireMonster;};

    void BuildAttackType() { monster_->attack_type_ = new FireAttack; };
    void BuildDefensType() { monster_->defens_type_ = new AvoidDefens; };
};

struct IceMonsterBuilder : public IMonsterBuilder
{
    IceMonsterBuilder() {monster_ = new IceMonster;};

    void BuildAttackType() { monster_->attack_type_ = new IceAttack; };
    void BuildDefensType() { monster_->defens_type_ = new IceShieldDefens; };
};

/////////////

struct MonsterDirector
{    
    void Construct(IMonsterBuilder* monster_builder)
    {
        monster_builder->BuildDefensType();
        monster_builder->BuildAttackType();
    }
};

////////////

void main()
{   
    MonsterDirector monster_director;

    IMonsterBuilder* fire_monster_builder = new FireMonsterBuilder;
    IMonsterBuilder* ice_monster_builder = new IceMonsterBuilder;

    monster_director.Construct(fire_monster_builder);
    monster_director.Construct(ice_monster_builder);

    fire_monster_builder->GetResult()->attack_type_->Attack();
    fire_monster_builder->GetResult()->defens_type_->Defenns();

    ice_monster_builder->GetResult()->attack_type_->Attack();
    ice_monster_builder->GetResult()->defens_type_->Defenns();
}

공격: 불을 뿜다 화이아~
방어: 회피 하기

공격: 얼음 화살 ㄱㄱ
방어: 얼음방패 막기

'Design Patterns' 카테고리의 다른 글

Prototype  (0) 2011.12.05
Factory Method  (0) 2011.11.16
Abstract Factory  (0) 2011.11.15
객체지향 설계 원칙 5-5 ISP (Interface Segregation Principle)  (2) 2008.07.08
객체지향 설계 원칙 5-4 DIP (Dependency Inversion Principle)  (0) 2008.06.28
Posted by 상현달
Design Patterns2011. 11. 15. 00:29
말 그대로 이다 생성을 추상화 시키는 패턴
사용 하기 위해 생성할 여러 객체들(ProductA1 ~ ProductB2) 을 선택적으로 한꺼번에 생성 가능하고, 그 부분을 추상화 한다.


AbstractFactory = IMonsterTypeFactory
ConcreateFactory1 = FireSoulFactory
ConcreateFactory2 = IceSoulFactory

AbstractProductA = IAttackType
ProductA1 = FireAttack
ProductA2 = IceAttack

AbstractProductB = IDefensType
ProductB1 = AvoidDefens
ProductB2 = iceShieldDefens


struct IAttackType
{    
    virtual void Attack() = 0;
};

struct FireAttack : public IAttackType
{
    void Attack() { cout << "공격: 불을 뿜다 화이아~" << endl; };
};

struct IceAttack : public IAttackType
{
    void Attack() { cout << "공격: 얼음 화살 ㄱㄱ" << endl; };
};

/////////////

struct IDefensType
{
    virtual void Defenns() = 0;
};

struct AvoidDefens : public IDefensType
{
    void Defenns() { cout << "방어: 회피 하기" << endl << endl; };
};

struct IceShieldDefens : public IDefensType
{
    void Defenns() { cout << "방어: 얼음방패 막기" << endl << endl; };
};

/////////////

struct IMonsterTypeFactory
{
    virtual IAttackType* CreateAttackType() = 0;
    virtual IDefensType* CreateDefensType() = 0;
};

struct FireSoulFactory : public IMonsterTypeFactory
{
    IAttackType* CreateAttackType() { return new FireAttack; };
    IDefensType* CreateDefensType() { return new AvoidDefens; };
};

struct IceSoulFactory : public IMonsterTypeFactory
{
    IAttackType* CreateAttackType() { return new IceAttack; };
    IDefensType* CreateDefensType() { return new IceShieldDefens; };
};

/////////////

struct IMonster
{
    IMonster(IMonsterTypeFactory* monster) 
    {
        attack_type_ = monster->CreateAttackType();
        defens_type_ = monster->CreateDefensType();
    };

    IAttackType* attack_type_;
    IDefensType* defens_type_;
};

struct FireMonster : public IMonster
{
    FireMonster() : IMonster(new FireSoulFactory) {};
};

struct IceMonster : public IMonster
{
    IceMonster() : IMonster(new IceSoulFactory) {};
};

/////////////

void MonsterAction(IMonsterTypeFactory* monster)
{
    IAttackType* attack_type = monster->CreateAttackType();
    IDefensType* defens_type = monster->CreateDefensType();

    attack_type->Attack();
    defens_type->Defenns();
}

////////////

void main()
{   
    FireMonster fire_monster;
    fire_monster.attack_type_->Attack();
    fire_monster.defens_type_->Defenns();
    
    IceMonster ice_monster;
    ice_monster.attack_type_->Attack();
    ice_monster.defens_type_->Defenns();
}

 

공격: 불을 뿜다 화이아~
방어: 회피 하기

공격: 얼음 화살 ㄱㄱ
 방어: 얼음방패 막기

Posted by 상현달
Design Patterns2008. 7. 8. 13:32

객체지향 설계 원칙 5-5 ISP (Interface Segregation Principle) 인터페이스 격리 원칙

객체지향의 설계원칙 중 마지막인 인터페이스 분리의 법칙이다.
객체는 자신의 기능을 다른 객체가 사용하기 위한 대표적인 방법은 인터페이스를 노출하는 것이다. 객체가 노출한 인터페이스를 통해 객체간의 커뮤니케이션이 일어난다. 이 인터페이스 분리의 법칙은 인터페이스를 노출하는 방법에 관한 이야기이다. ISP 를 한마디로 표현하면 다음과 같다.

"클라이언트가 사용하지 않는 인터페이스 때문에 클라이언트가 영향을 받아서는 안된다."

 여러개의 클라이언트가 사용하는 모든 인터페이스를 모아 두지 말고 하나의 클라이언트가 사용하는 하나의 인터페이스 집합을 만들어 사용해야 한다. 이것은 단일 책임의 원칙과 유사하게 해석될수도 있다. 하나가 인터페이스가 변경된다면 하나의 객체만 변경되어야 한다는 말이다.

마침 이에관한 좋은 예제가 하나 있다. 내가 요즘 서버를 개발중에 있다. 그것도 두개의 서버를 동시에 개발한다. 하나의 게임에서 사용할 서버이고 편의상 A 서버 B 서버로 칭하겠다.  그런데 클라이언트 쪽에서는 이 서버에 통신하는 부분을 개발할 시간이 없단다. ㅡㅡ 그래서 이 A, B 서버와 통신하는 클라이언트 Stub 을 라이브러리 형태로 같이 개발해 달라고 한다. 귀찮게 시리 ㅡㅡ

그래서 클라이언트 라이브러리를 개발하기 위해 다은과 같은 구성을 생각했다.


사용자 삽입 이미지

[그림 1]


위 그림을 보면 "A 클라이언트"는 A_prtocol 을 사용하여 "A 서버" 와 통신을 하고 B 클라이언트는 B_protocol 을 사용하여 통신할 것이다.  그러면 여기서 어떤 문제가 발생하겠는가 ?
먼저 "A 클라이언트"는 B_protocol() 을 사용하지 않는다. "A 클라이언트" 에서 알고 싶지도 알아야 할 필요도 없는 정보이다. 그런데 만일 B_protocol() 이 수정되었거나 또는 "B 클라이언트"의 새로운 기능때문에 인터페이스가 추가되어있다면 어떻게 될까 ? 네트웍 객체는 다시 컴파일 되어야 할것이고, 이것을 사용하는 클라이언트들도 다시 컴파일 되야 할 것이다. 그리고 계속 이런일이 발생하면 네트웍 객체는 필요 이상으로 점점 방대해 질 것이다.

 이것이 위에서 언급한 클라이언트가 사용하지 않는 인터페이스 때문에 영향을 받는 것이다. 그렇다면 이와 같은 문제를 해결하기 위한 방법은 무엇이 있을까 ?

사용자 삽입 이미지

[그림 2]

 위와 같이 상속을 통해 공통부분을 관리하고 클라이언트에 종속적인 인터페이스는 따로 분리하여 객체로 관리하면 클라이언트가 다른 클라이언트와 독립적인 인터페이스를 가질수 있다.

이로서 객체지향 설계원칙 5가지에 대해 알아 보았다. 점점을 글을 쓸때마다 내용도 더 충실해 지고 내용전달도 잘 되것을 기대 하였지만, 현실은 그렇지 못했다. ㅡㅡ  이 놈의 귀차니즘 때문인지 시작한건 빨리 끝내야 겠다는 생각이 들어서 인지 서둘러 끝내는 느낌이다. ㅠㅠ

 지금까지 살펴본 객체지향 설계 5 원칙은 어찌 보면 가장 쉽고 간단한 원리이지만 막상 적용하려고 하면 상당히 어렵고 복잡해 진다. 익숙해 질때 까지 이 원칙을 계속 적용해 봐야 할것 같다. 그렇지만 항상 불변의 원칙은 없는 것이니까 너무 집착할 필요는 없을것 같다.
Posted by 상현달
Design Patterns2008. 6. 28. 00:33

객체지향 설계 원칙 5-4 DIP (Dependency Inversion Principle) 의존 관계 역전 원칙

먼저 의존 관계 역전 이란 어떤 의미인지 알아보자.

A 객체가 B 객체를 멤버객체로 사용한다고 하면 A 객체는 B 객체를 사용하게 된다. B객체의 통제권이 A 객체에 있는것이다. B 객체는 A 객체의 존재도 알지 못한다. 이러한 관계에서 B 객체가 A 객체를 호출해야 하는 일이 필요할수 있다. B 객체가 A 객체를 Call 하는것을 의존관계 역전이라고 보면되겠다.

채팅프로그램을 하나 만들려고 한다. 그러면 네트웍 처리하는 부분을 개발해야 할것이다. 그래서 나는 네트웍 관련처리를 맡아서 할 소켓 객체를 하나 만들어서 사용하려고 한다.

간단하게 보면 다음과 같다.

사용자 삽입 이미지

[그림1]

이 구조에서 클라로직은 클라소켓 객체를 통해 서버와 통신을 한다. 클라소켁 객체의 Send 와 Receive 메소드를 비교해 보겠다.

클라로직에서 어떤 데이타를 서버에 Send 하고 그에 맞는 응답을 Receive 해야 한다고 가정한다. 그러면 클라로직은 클라소켓의 Send 메소드를 호출하면된다. Send 후에 Receive 를 받아야 하는데 서버에서 언제 데이타가 올지 모른다. 그러면 어떻게 해야 하나 ? 제일 간단한 방법으로는 클라소켓 객체에게 Receive 가 왔는지 계속해서 물어볼수 있을것이다. 이런 방법을 사용한다면 아주 비효율적인 방법이 될것이다. 그렇다면 가장효과적인 방법은 무엇일까 ? 바로 클라소켓이 서버로 부터 데이타를 받았을때 클라로직에게 알려주는것이다.

이 부분을 어떻게 구현할지 잠깐 살펴보도록 하자

사용자 삽입 이미지

[그림2]


클라이언트 쪽만 살펴보도록 한다. 먼저 클라로직과 클라소켓이 통신을 할수 있는 템플릿 클래스를 하나 만든다. 그리고 클라소켓에게는 AddEventObj 를 통해 자신의 객체 정보를 준다. 그러면 클라소켓은 Receive 가 왔을때 템플릿 클래스의 Receive 메소드만 호출하면 클라로직에 Receive 를 전달 할수 있다.

추상화를 많이 적용해보지 않았다면 이부분이 쉽게 이해가지 않을 것이다. 간단하게 샘플로 만들어 볼까 했는데 이놈의 귀차니즘이 ... (추후에 필요하다면 소스로 만들어서 추가 하겠다 ㅡㅡ)

% 참고로 이 부분을 C 형식으로 간단히 구현하고 싶으면 Callback 함수를 등록해서 사용하는 방법도 있을수 있다.

간략하게 DIP 에 대한 간략한 소개의 샘플을 만들어 봤다. 이런 객체 설계에 익숙해 지려면 많은 시간 꾸준히 패턴을 적용한 설계를 해봐야 할것 같다. 그러나 패턴을 적용하기 위한 설계 또한 상당히 위험하다.
그렇다고 아무것도 안하고 계속 현실에 안주 하기 보다 실패를 하더라도 무엇인가 시도해 보고 노력해야 할것이다.

아무것도 하지 않는다면 실패도 없지만 성공도 없다. (오늘 점심시간 만화에서 나온대사 이다. ㅡㅡ)
Posted by 상현달
Design Patterns2008. 6. 21. 17:12

객체지향 설계 원칙 5-3 SRP (Single Responsibility Principle) 단일 책임의 원칙

먼저 단일책임의 원칙을 한마디로 표현하자면 다음과 같다.

"하나의 객체는 하나의 책임만을 가진다" 

이다. 여기서 책임이란 말의 의미는 변경해야할 이유라고 보면 되겠다. 그러니까 다시 풀어서 얘기 하자면

"하나의 객체는 오직 한가지의 이유로 인해서만 변경되어야 한다."

정도가 되겠다.
평소에 소스를 보면서 복잡도, 결합도가 높다는 말을 많이 듣거나 사용했을것이다. 이게 바로 그말이다 ㅡㅡ

그러면 이제 간단한 샘플을 보면서 단일 책임이 무엇인지 알아보겠다.

내가 요즘 주식 재미에 푹~ 빠졌다. ㅎㅎ
그래서 주식관련한 샘플이 갑자기 생각났다.

다음의 클래스 구성도를 보자

사용자 삽입 이미지

[그림1]


샘플 클래슨 바로 주가 정보를 계산해서 알려주는 "주가정보" 클래스이다.
이 객체는 코스피 주가를 알고 싶은 객체와 코스닥 주가를 알고 싶은 객체가 사용하게 될것이다.

그러면 이 주가정보 객체에는 어떠한 문제점이 보이는가 ?
먼저 코스피정보만 취급하는 곳 에서는 코스닥 정보가 필요없다. 반대로 코스닥만 취급하는 곳 에서는 코스피 정보가 필요가 없다. 그러면 "현재코스피()", "현재코스닥()" 인터페이스중 하나는 불필요한 인터페이스가 될수 있다. 이런 문제는 크게 문제가 되어 보이지 않는다. 어차피 필요한 것만 가져다 쓰면 되기 때문이다. 하지만 많은 인터페이스가 존재한다면 복잡도가 증가될수는 있겠다.

그러면 더 큰 문제는 무엇인가 ? 이 문제는 주가정보 객체가 업데이트 되어야 할때 발생한다.
만일 코스피 정보를 알려주는 내부로직이 아무런 변경이 없고 코스닥 정보를 알려주는 내부로직이 변경되었다면 코스피 정보만 사용하는 곳에서는 불필요하게 주가정보 객체가 업데이트 되는것이다.

왜 이런 문제들이 발생하는 것일까 ? 그것은 주가정보 객체가 두가지의 책임을 가지고 있기 때문이다. 위에서 책임은 무엇이라고 했는가 ? "책임이란 객체가 변경되어야 할 이유이다." 라고 했다. 그렇다 이 주가 정보 객체는 변경되어야 하는 이유가 두가지인 것이다.

그러면 어떻게 이 문제를 해결할 것인가 ? 해결의 답은 간단한다. 두개의 책임을 하나씩 나누는 것이다. 주가 정보 객체를 코스피 정보를 계산해서 알려주는 객체와 코스닥 정보를 계산해서 알려주는 객체로 나누는 것이다. [그림2] 와 같다.

사용자 삽입 이미지

[그림2]


이 밖에도 하나의 객체가 하나의 책임을 가지지 못하면서 발생하는 문제는 또 있다. 예들들어 하나의 책임을 여러개의 객체에서 같이 가지고 있다면 어떻게 될까 ? 바로 산탄총에 맞은 사람을 수술하는 의사가 될것이다. 일반 총에 맞으면 하나의 상처가 생긴다. 그러나 산탄총에 맞은 사람은 몸의 여러군데에 상처가 생긴다. 그래서 산탄총에 맞은 환자를 수술하려면 같은 한방을 맞더라도 여러군데를 시술해야 한다.

개인적으로 보면 5가지 원칙중 "단일 책임의 원칙" 이 제일 직관적이고 이해하기가 쉽다. 그것은 C 를 배울때 부터 이러한 얘기를 많이 들어와서 였던거 같다. 예들들어 처음 C 를 시작하면서 많이 듣는 얘기중 하나가 "하나의 함수에는 하나의 기능만을 넣어서 함수를 만들어라" 란 말을 많이 들어왔을 것이다.

그러나 사실 객체를 설계하면서 책임을 구분하고 나눈는일 자체가 쉽지 않다.ㅠㅠ
계속 반복하는 얘기지만 이런부분은 경험으로 채워가는 방법밖에 없다. 항상 생각하며 일하라.
Posted by 상현달