U E D R , A S I H C RSS

Gof/Factory Method


DeleteMe ) 현재 진행중 --상민

1. Factory Method

1.1. Intent : 의도

객체 생성을 위한 인터페이스를 정의하라, 그렇지만 sub 클래스들이 구현사항을 결정할수 있도록 하자.

Factory Method는 sub 클래스에 대해 구현 사항을 결정할수 있게 만든다.

1.2. Also Known As : 비슷한, 혹은 동일한 역할을 하는 용어

DeleteMe) 이건 어떠냐.

Virtual Constructor ( MoreEffectiveC++ Item 25 참고 )

1.3. Motivation : 동기

Framework(이하 Framework 그대로)는 객체사이의 관게를 정의하고, 유지하기 위하여 가상 클래스들을 사용한다. Framework는 종종 이러한 클래스들을 기반으로 객체의 생성에 책임을 진다.

여러 문서를 사용자에게 보여줄수 있는 어플리케이션에 대한 Framework에 대하여 생각해 보자. 이러한 Framework에서 두가지의 추상화에 대한 요점은, Application과 Document클래스 일것이다. 이 두 클래스다 추상적이고, 클라이언트는 그들의 Application에 알맞게 명세 사항을 구현해야 한다. 예를들어서 Drawing Application을 만들려면 우리는 DrawingApplicationDrawingDocument 클래스를 구현해야 한다. Application클래스는 Document 클래스를 관리한다. 그리고 사용자가 Open이나 New를 메뉴에서 선택하였을때 이들을 생성한다.

Application(클래스가 아님)만들때 요구되는 특별한 Document에 대한 Sub 클래스 구현때문에, Application 클래스는 Doment의 Sub 클래스에 대한 내용을 예측할수가 없다. Application 클래스는 오직 새로운 종류 Document가 만들어 질때가 아니라, 새로운 Document 클래스가 만들어 질때만 이를 다룰수 있는 것이다. 이런 생성은 딜레마이다.:Framework는 반드시 클래스에 관해서 명시해야 되지만, 실제의 쓰임을 표현할수 없고 오직 추상화된 내용 밖에 다를수 없다.

Factory Method 패턴은 이에 대한 해결책을 제시한다. 그것은 Document의 sub 클래스의 생성에 대한 정보를 캡슐화 시키고, Framework의 외부로 이런 정보를 이동 시키게 한다.

DeleteMe 그림추가
fac1.gif

Application의 Sub 클래스는 Application상에서 추상적인 CreateDocument 수행을 재정의 하고, Document sub클래스에게 접근할수 있게 한다. Aplication의 sub클래스는 한번 구현된다. 그런 다음 그것은 Application에 알맞은 Document에 대하여 그들에 클래스가 특별히 알 필요 없이 구현할수 있다. 우리는 CreateDocument를 호출한다. 왜냐하면 객체의 생성에 대하여 관여하기 위해서 이다.

1.4. Applicability : 적용

Fatory Method 패턴은 이럴때 사용한다.
  • 클래스가 그것이 생성하는 클래스에 관해서 예상할수 없을때
  • 클래스가 그것의 sub 클래스가 객체 생성에 최적화를 시키고 싶을때
  • 클래스가 몇개의 helper sub클래스에게 책임을 위임하거나, 당신이 helper sub클래스에 관한 정보를 특성화 시키기를 원할때

1.5. Structure


DeleteMe 구조에 관한 그림

1.6. Participants

  • Product (Document)
    • Factory Method가 생성하는 객체에 관한 인터페이스를 정의한다.

  • ConcreteProduct (MyDocument)
    • Product에 인터페이스에 대한 구현사항이다.

  • Creator (Application)
    • Procunt 형의 객체를 반환하는 Factory Method를 선언한다. Creator는 또한 기본 ConcreteProduct객체를 반환하는 factory method에 관한 기본 구현도 정의되어 있다.
    • Product객체를 만들기위한 factory method를 호출한다.

  • ConcreteCreator (MyApplication)
    • ConcreteProduct의 인스턴스를 반환하기 위한, factory method를 오버라이드(over ride) 한다.

1.7. Collaborations : 협력

Creator는 factor method가 정의되어 있는 Creator의 sub클래스에게 의지한다 그래서 Creator는 CooncreteProduct에 적합한 인스턴스들을 반환한다.

1.8. Consequences : 결론

DeleteMe) '결과' 나 '결론' 이나 그게 그거지만. --; '패턴적용결과' 정도.
DeleteMe) 왜 결과지. 결과는 적용후에 얻을수 있는 이익이지만, 현재 이것은 패턴을 적용한 코드를 구현하기 전에 이론적 바탕에 대하여 결론 짓는 것이라고 생각해서 결론이라고 했음. 그냥 결과는 부족한것 같고, "패턴 적용 결과"보다는 "패턴 적용 결과 고찰" 이라는 의미가 강한거 같은데, 그냥 결론으로 쿨럭 --;

Factory method는 당신의 코드에서 만들어야한 Application이 요구하는 클래스에 대한 기능과 Framework가 묶여야할 필요성을 제거한다. 그 코드는 오직 Product의 인터페이스 만을 정의한다.; 그래서 어떠한 ConcreteProduct의 클래스라도 정의할수 있게 하여 준다.

DeleteMe 모호
factory method의 잠재적인 단점이라고 한다면 클라이언트가 아마도 단지 특별한 ConcreteProduct객체를 만들기위해서 Creator클래스의 sub클래스를 가지고 있어야 한다는 것일꺼다. 클라이언트가 어떤 식으로든 Creator의 sub클래스를 만들때의, sub클래스를 만드는 것자체는 좋다. 하지만 클라이언트는 이런것에 신경쓸 필요없이 로직 구현에 신경을 써야 한다.

A potential disadvantage of factory methods is that clients might have to subclass the Creator class just to create a particular ConcreteProduct object. Subclassing is fine when the client has to subclass the Creator class anyway, but otherwise the client now must deal with another point of evolution.

자, 역시 Factory Method 패턴에 관련한 두가지의 결론을 이야기 한다.:
Here are two additional consequences of the Factory Method pattern:

  1. 서브 클래스와 소통 통로 제공(Provides hooks for subclasses.) Factory Method를 적용한 클래스에서 객체의 생성은 항상 직접 만들어지는 객체에 비하여 유연하다. Factory Method는 객체의 상속된 버전의 제공을 위하여, sub클래스와 연결될수 있다.(hook의 의미인데, 연결로 해석했고, 그림을 보고 이해해야 한다.)

    Ducument에제에서 Document클래스는 factory method에 해당하는, 자료를 열람하기 위한 기본 파일 다이얼로그를 생성하는 CreateFileDialog이 호출을 정의할수 있다. 그리고 Document sub클래스는 이러한 factory method를 오버 라이딩해서 만들고자 하는 application에 특화된 파일 다이얼로그를 정의할수 있다. 이러한 경우에 factory method는 추상적이지 않다. 하지만 올바른 기본 구현을 제공한다.

  2. 클래스 상속 관게에 수평적인(병렬적인) 연결 제공(Connects parallel class hierarchies.) 여태까지 factory method는 오직 Creator에서만 불리는걸 생각해 왔다. 그렇지만 이번에는 그러한 경우를 따지는 것이 아니다.; 클라이언트는 수평적(병렬적)인 클래스간 상속 관계에서 factory method의 유용함을 찾을수 있다.

    병렬 클래스 상속은 클래스가 어떠한 문제의 책임에 관해서 다른 클래스로 분리하고, 책임을 위임하는 결과를 초례한다. 조정할수 있는 그림 도형(graphical figures)들에 관해서 생각해 보자.;그것은 마우스에 의하여 뻗을수 있고, 옮겨지고, 회정도 한다. 그러한 상호작용에 대한 구현은 언제나 쉬운것만은 아니다. 그것은 자주 늘어나는 해당 도형의 상태 정보의 보관과 업데이트를 요구한다. 그래서 이런 정보는 상호 작용하는, 객체에다가 보관 할수만은 없다. 게다가 서로다른 객체의 경우 서로다른 상태의 정보를 보관해야 할텐데 말이다. 예를들자면, text 모양이 바뀌면 그것의 공백을 변화시키지만, Line 모양을 늘릴때는 끝점의 이동으로 모양을 바꿀수 있다.

    이러한 제한에서는 모양에따라, 각 상테에 따라 객체를 분리하는 것이 더 좋을 것이고, 각자 필요로하는 상태를 유지 해야한다. 서로 다른 모양은 서로다른 Manipulator의 sub클래스를 사용해서 움직여야 한다.이러한 Manipulator클래스와 상속은 병렬적으로 이루어 진다. 다음과 같은 모양 같이 말이다.

DeleteMe 그림

Figure클래스는 CreateManipulator라는, 서로 작용하는 객체를 생성해 주는 factory method이다. Figure의 sub클래스는 이 메소드를 오버라이드(override)해서 그들에게 알맞는 Manipulator sub클래스의 인스턴스를 (만들어, )반환한다. Figure 클래스는 아마도 기본 Manipulator인스턴스를 (만들어,) 반한하기 위한 기본 CreateManipulator를 구현했을 것이다. 그리고 Figure의 sub클래스는 간단히 이러한 기본값들을 상속하였다. Figure클래스 들은 자신과 관계없는 Manipulator들에 대하여 신경 쓸필요가 없다. 그러므로 이들의 관계는 병렬적이 된다.

Factory Method가 두게의 클래스의 상속 관계에서 어떻게 이러한 연결과정을 정의하는지 주목하라. 그것은 서로 고유의 기능을 부여시키는 작업을 한다.

1.9. Implementation : 구현, 적용

Factory Method패턴이 적용될때 발생할수 있는 문제에 관해서 생각해 보자.:

  1. 두가지의 커다란 변수. Factory Method 패턴에서 두가지의 중요한 변수는 첫번째 Creator 클래스가 가상 클래스이고, 그것의 선언을 하지만 구현이 안될때의 경이 두번째로 Creator가 concrete 클래스이고, factor method를 위한 기본 구현을 제공해야 하는 경우. 기본 구현이 정의되어 있는 가상 클래스를 가지는건 가능하지만 이건 일반적이지 못하다.

    첫번째 경우는 코드가 구현된 sub클래스를 요구한다. 왜냐하면, 적당한 기본 구현 사항이 없기때문이다. 예상할수 없는 클래스에 관한 코드를 구현한다는 것은 딜레마이다. 두번째경우에는 유연성을 위해서 concrete Creator가 factory method 먼저 사용해야 하는 경우이다. 다음과 같은 규칙을 이야기 힌다."서로 분리된 수행 방법으로, 객체를 생성하라, 그렇게 해서 sub클래스들은 그들이 생성될수 있는 방법을 오버라이드(override)할수 있다." 이 규칙은 sub클래스의 디자이너들이 필요하다면, 그들 고유의 객체에 관련한 기능으로 sub클래스 단에게 바꿀수 있을음 의미한다.

  2. Parameterized factory methods(그대로 쓴다.) Factory Method패턴에서 또 다른 변수라면 다양한 종류의 product를 사용할때 이다. factory method는 생성된 객체의 종류를 확인하는 인자를 가지고 있다. 모든 객체에 대하여 factory method는 아마 Product 인터페이스를 공유할 것이다. Document예제에서, Application은 아마도 다양한 종류의 Document를 지원해야 한다. 당신은 CreateDocument에게 document생성시 종류를 판별하는 인자 하나를 넘긴다.

    Unidraw 그래픽 에디터 Framework는 디스크에 저장된 객체의 재 생성을 위하여 이러한 접근법을 사용하고 있다. Unidraw는 factory method를 이용한 Creator 클래스가 식별자를 가지고 있도록 정의해 두었다. 이 클래스 식별자는 적합한 클래스를 기술하고 있다. Unidraw가 객체를 디스크에 저장할때 이 클래스 식별자가 인스턴스 변수의 어떤것 보다도 앞에 기록 되는 것이다. 그리고 디스크에서 다시 객체들이 생성될때 이 식별자를 역시 가장 먼저 읽는다.

    클래스 식별자를 읽고서 Framework는 식별자를 Create에게 넘기면서 호출한다. Create는 적당한 클래스를 찾고, 그것을 객체의 생성에 사용한다. 마지막으로 Create는 객체의 Read 수행을 호출하여 디스크에 정보를 저장하고, 인스턴스 변수들을 초기화 한다.

    Parameterized factory method는 Product를 상속받은 MyProductYourProduct상에서일반적으로 다음과 같은 형태를 가진다.
    ~cpp 
    class Creator {
    public:
        virtual Product* Create(ProductId);
    };
    
    Product* Creator::Create (ProductId id) {
        if (id == MINE)  return new MyProduct;
        if (id == YOURS) return new YourProduct;
        // 남은 product의 종류의 반복    
        return 0;
    }
    

    Parameterized factory method 는 Creator가 생성하는 product를 선택적으로 확장하고, 변화시키는데, 쉽게 만들어 준다. 그리고 새로운 식별자로 새로운 종류의 product를 도입하고나, 서로다른 product의 식별자를 연관시킬수도 있다.

    예를 들어서 sub클래스 MyCreatorMyProductYouProduct를 바꾸고, 새로운 TheirProduct Sub클래스를 지원할수 있다.
    ~cpp 
    Product* MyCreator::Create (ProductId id) {
        if (id == YOURS)  return new MyProduct;
        if (id == MINE)   return new YourProduct;
            // YouProduct와 MyProduct 교환
        if (id == THEIRS) return new TheirProduct;  // 새로운 product 추가
    
        return Creator::Create(id); // 만약 모두가 실패하면 기본 생성자를 부른다.
    }
    
    DeleteMe 모호)마지막 부분에서 부모 클래스의 인자를 수행하는 것을 주목해라. MyCreator::Create는 오직 YOURS, MINE, THEIRS를 잡을수 있고, 부모클래스는 잡지 못한다. 다른 클래스는 이를 수행하지 못한다. 그러므로, MyCreator는 생성된 product의 한 종류를 확장하고, 그것은 생성에 대한 책임을 연기한다. 하지만, product가 그것의 부모인것은 적다.

MyCreator::Create handles only YOURS, MINE, and THEIRS differently than the parent class. It isn't interested in other classes. Hence MyCreator extends the kinds of products created, and it defers responsibility for creating all but a few products to its parent.

  1. 언어 규칙에서의 변수와 이슈(Language-specific variants and issues) 다른 언어사에서는 좀더 다른 방식으로 다른 절차로 구현될 것이다.

    Smalltalk프로그래머들은 종종 구체적으로 구현되어 있는 객체의 클래스를 반환하는 메소드를 사용한다. Creator의 factory method는 이러한 값을 사용하여 product를 만들어 내고, ConcreteCreator는 아마 저장하거나, 이러한 값으 계산을 수행한다. 이러한 결과는 구현되어 있는 ConcreteProduct의 형을 위하여 바인딩 조차 나중에 하게 된다.

    Smalltalk 버전의 Document 예제는 documentClass 메소드를 Application상에 정의할수 있다. documentClass 메소드는 자료를 표현하기 위한 적당한 Document 클래스를 반환한다. MyApplication에서 documentClass의 구현은 MyDocument 클래스를 반환하는 것이다. 그래서 Application상의 클래스는 이렇게 생겼고
    ~cpp 
        clientMethod
            document := self documentClass new.
        
        documentClass
            self subclassResponsibility
    

MyApplication 클래스는 MyDocument클래스를 반환하는 것으로 구현된다.

~cpp 
    documentClass
        ^ MyDocument

더 유연한 접근 방식은 비슷하게 parameterized factory method Application의 클래스의 변수들과 같이 생성되어지는 클래스를 보관하는 것이다.그러한 방식은 product를 변경하는 Application을 반드시 감싸야 한다.

C++에서 Factory method는 항상 가상함수이고, 종종 순수 가상 함수이다. Creator의 생성자 내부에서 factory methods의 호출은 결코 좋지 못한것 일까? 그러한 factory method는 ConcreteCreator안에 존재하고, 아직 활성화 되지 않았다.

You can avoid this by being careful to access products solely through accessor operations that create the product on demand. Instead of creating the concrete product in the constructor, the constructor merely initializes it to 0. The accessor returns the product. But first it checks to make sure the product exists, and if it doesn't, the accessor creates it. This technique is sometimes called lazy initialization. The following code shows a typical implementation:

~cpp 
    class Creator {
    public:
        Product* GetProduct();
    protected:
        virtual Product* CreateProduct();
    private:
        Product* _product;
    };
    
    Product* Creator::GetProduct () {
        if (_product == 0) {
            _product = CreateProduct();
        }
        return _product;
    }


  1. Using templates to avoid subclassing. As we've mentioned, another potential problem with factory methods is that they might force you to subclass just to create the appropriate Product objects. Another way to get around this in C++ is to provide a template subclass of Creator that's parameterized by the Product
    ~cpp 
    class: 
        class Creator {
        public:
            virtual Product* CreateProduct() = 0;
        };
        
        template 
        class StandardCreator: public Creator {
        public:
            virtual Product* CreateProduct();
        };
        
        template 
        Product* StandardCreator::CreateProduct () {
            return new TheProduct;
        }
    

With this template, the client supplies just the product classno subclassing of Creator is required.

~cpp 
    class MyProduct : public Product {
    public:
        MyProduct();
        // ...
    };
    
    StandardCreator myCreator;

  1. Naming conventions. It's good practice to use naming conventions that make it clear you're using factory methods. For example, the MacApp Macintosh application framework App89 always declares the abstract operation that defines the factory method as Class* DoMakeClass(), where Class is the Product class.

1.10. Sample Code

The function CreateMaze (page 84) builds and returns a maze. One problem with this function is that it hard-codes the classes of maze, rooms, doors, and walls. We'll introduce factory methods to let subclasses choose these components.

First we'll define factory methods in MazeGame for creating the maze, room, wall, and door objects:

~cpp 
    class MazeGame {
    public:
        Maze* CreateMaze();
    
    // factory methods:
    
        virtual Maze* MakeMaze() const
            { return new Maze; }
        virtual Room* MakeRoom(int n) const
            { return new Room(n); }
        virtual Wall* MakeWall() const
            { return new Wall; }
        virtual Door* MakeDoor(Room* r1, Room* r2) const
            { return new Door(r1, r2); }
    };

Each factory method returns a maze component of a given type. MazeGame provides default implementations that return the simplest kinds of maze, rooms, walls, and doors.

Now we can rewrite CreateMaze to use these factory methods:

~cpp 
    Maze* MazeGame::CreateMaze () {
        Maze* aMaze = MakeMaze();
    
        Room* r1 = MakeRoom(1);
        Room* r2 = MakeRoom(2);
        Door* theDoor = MakeDoor(r1, r2);
    
        aMaze->AddRoom(r1);
        aMaze->AddRoom(r2);
    
        r1->SetSide(North, MakeWall());
        r1->SetSide(East, theDoor);
        r1->SetSide(South, MakeWall());
        r1->SetSide(West, MakeWall());
    
        r2->SetSide(North, MakeWall());
        r2->SetSide(East, MakeWall());
        r2->SetSide(South, MakeWall());
        r2->SetSide(West, theDoor);
    
        return aMaze;
    }

Different games can subclass MazeGame to specialize parts of the maze. MazeGame subclasses can redefine some or all of the factory methods to specify variations in products. For example, a BombedMazeGame can redefine the Room and Wall products to return the bombed varieties:

~cpp 
    class BombedMazeGame : public MazeGame {
    public:
        BombedMazeGame();
    
        virtual Wall* MakeWall() const
            { return new BombedWall; }
    
        virtual Room* MakeRoom(int n) const
            { return new RoomWithABomb(n); }
    };

An EnchantedMazeGame variant might be defined like this:

~cpp 
    class EnchantedMazeGame : public MazeGame {
    public:
        EnchantedMazeGame();
    
        virtual Room* MakeRoom(int n) const
            { return new EnchantedRoom(n, CastSpell()); }
    
        virtual Door* MakeDoor(Room* r1, Room* r2) const
            { return new DoorNeedingSpell(r1, r2); }
    protected:
        Spell* CastSpell() const;
    };

1.11. Known Uses

Factory methods pervade toolkits and frameworks. The preceding document example is a typical use in MacApp and ET++ WGM88. The manipulator example is from Unidraw.

Class View in the Smalltalk-80 Model/View/Controller framework has a method defaultController that creates a controller, and this might appear to be a factory method Par90. But subclasses of View specify the class of their default controller by defining defaultControllerClass, which returns the class from which defaultController creates instances. So defaultControllerClass is the real factory method, that is, the method that subclasses should override.

A more esoteric example in Smalltalk-80 is the factory method parserClass defined by Behavior (a superclass of all objects representing classes). This enables a class to use a customized parser for its source code. For example, a client can define a class SQLParser to analyze the source code of a class with embedded SQL statements. The Behavior class implements parserClass to return the standard Smalltalk Parser class. A class that includes embedded SQL statements overrides this method (as a class method) and returns the SQLParser class.

The Orbix ORB system from IONA Technologies ION94 uses Factory Method to generate an appropriate type of proxy (see Proxy (207)) when an object requests a reference to a remote object. Factory Method makes it easy to replace the default proxy with one that uses client-side caching, for example.

1.12. Related Patterns

Abstract Factory (87) is often implemented with factory methods. The Motivation example in the Abstract Factory pattern illustrates Factory Method as well.

Factory methods are usually called within Template Methods (325). In the document example above, NewDocument is a template method.

Prototypes (117) don't require subclassing Creator. However, they often require an Initialize operation on the Product class. Creator uses Initialize to initialize the object. Factory Method doesn't require such an operation.

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2009-05-27 07:09:19
Processing time 0.1920 sec