オブジェクト指向プログラミングとデザインパターン

前回は、オブジェクト指向プログラミングの基本である、オブジェクトの定義、継承、可視化制御について説明した。今回の資料は、Gamma, Helm, Johnson, Vlissides「オブジェクト指向における再利用のためのデザインパターンDesign Patterns Elements of Reusable Object-oriented Software」(本位田、吉田監訳、ソフトバンク、ISBN4-7973-1112-6)である。

オブジェクト指向に基づいてソフトウエアを設計することは簡単なことではない。オブジェクト指向プログラムのひとつの目標に再利用可能なソフトウエアを設計することがある。しかし、はじめから再利用が可能なソフトウエアを設計することは経験を要する。「デザインパターン」とは、再利用可能な「よいオブジェクト指向設計」をするためにどのような設計をすればいいのかの経験をオブジェクトの関連というパターンにまとめて、それをカタログにしたものである。

Compositeパターン

部分−全体構造を表現するために、オブジェクトを木構造に組み立てる。Compositeパターンにより、クライアントは個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができる。

この構造は、全体として木構造にオブジェクトを組み立てるためにつかう。

class Composite; /./先行宣言

class Component { // Composite内のオブジェクトのインタフェースを宣言する。

public: virtual Composite *GetCompsite() { return NULL; }

… };

class Composite: public Component {// 子を持つオブジェクトの振る舞い定義

public:

   void add(Component *){ …}

   virtual Composite *GetComposite() { return this; }

… }

class Leaf: public Component { …. };

Componentには、すべてのクラスに共通なインターフェースのデフォールトの振る舞いを定義する。この例では、内部のComponentをとるGetCompositeを定義して、ダウンキャストを避けている。

Strategyパターン

アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。このパターンを利用することによって、アルゴリズムをそれを利用するクライアントからは独立に変更することができるようになる。

class Strategy { //アルゴリズムに共通なインタフェースを定義

pubic:

    virtual void doAlgorithm(…);= 0;

…}

class ConcreateStrategyA { //Strategyクラスのインタフェースを利用して

public: vitural void doAlgorthm(…) { …}; // アルゴリズムを実装

…}

class ConcreateStrategyB {

public: vitural void doAlgorthm(…) { …};

…}

class Context { //アルゴリズムを利用する側のオブジェクト

public: Context(Strategy *); //具体的なアルゴリズムを与える。

    void callAlgorithm() { _strategy->doAlgorthm(…); … };

   // 実際のアルゴリズムを呼び出す。具体的な内容はしらなくてもよい。

private:

   Strategy* _strategy; // Strategyのオブジェクトへの参照を保持

   … };

これを利用するコードは、

  Context *aContext = new Context(new ConcreateStrategyA);

  Context *aContext = new Context(new ConcreateStrategyB);

上の例では、呼び出す側Contextと使うアルゴリズムは任意に組み合わせることができることに注意しよう。

Decoratorパターン(Wrapper)

オブジェクトに責任をトランスペアレントに追加する。サブクラス化よりも柔軟な機能を提供する。

class Component {

//責任を追加できるようにするオブジェクトのインタフェース定義

public: virtual void Operation(); …}

class ConcrateComponet : public Component { …}; //実際のクラス

class Decorator : public Component {

// Componentクラスと一致したインタフェースを提供する。

public: virtual void operation() { _component->operation(); }

//要求をWrapしているオブジェクトへ転送

private: Component* _component; // Wrapしているオブジェクト

…}

public ConcreateDecorator: public Decorator { //実際のDecorator

public: virtual void Operation() {

 …; Decorator::operation(); … } //ここで、追加するオペレーションを定義

…};

サブクラス化だけでは、機能を一階層だけは付け加えることができるが、付け加える機能を組み合わせることができないことに注意。

AbstractFactoryパターン

お互いに関連したり依存しあうオブジェクト群を、その具象クラスを明確にせず、生成するためのインタフェースを提供する。

class AbsractFactory {

//AbstractProductのオブジェクトを生成するオペレーションのインタフェースの定義

 virtual AbstractProductA *CreateProductA()=0;

 virtual AbstractProductB *CreateProductB()=0;

 … };

class ConcreateFactory: public AbstractFactory { 

//実際のConcreateProductを生成するオペレーションを定義

  virtual AbstractProductA *CreateProductA(){ …}

  virtual AbstractProductB *CreateProductB() { … };

…}

class AbstractProductA { // 部品ごとのインタフェースを定義

  virtual void operationA();

…}

class ConcreateProductA {

//ConcreateFactoryで生成される部品オブジェクトの定義

  virtual void operationA() { … };

….}

このパターンを使うことによって、お互いに関連しあう部品のセットを使う場合、その実装を独立に作ることができる。たとえば、様々なWindowシステムで同じような部品セット(スクロールバー、ボタン、など)を統一的に提供できるようになる。

Bridgeパターン

抽出したクラスと実装を分離して、それらを独立に変更できるようにする。

class Abstraction { //抽出されたクラスのインタフェースを定義

 virtual void Operation() { _imp->OperationImp(); }

private:

  Implementor* _imp; //実装するオブジェクトへの参照

…};

class RedefinedAbstraction : public Abstraction { 

 // Abstractionのインタフェースを使って、機能を拡張する。

  …. }

class Implementor { // 実装を行うクラスのインタフェースを定義

  virtual void OperationImp() ;

 … }

class ConcreateImplementor: public Implementor { 

// Implementorクラスの実装

  virtual void OperationImp();

}

このパターンでは、論理的な拡張(RedefinedAbstraction)と実際の実装の変更を分離する。たとえば、Windowシステムでは、単純なWindowオブジェクトからIconWindowを作ることは論理的な拡張だが、どのようなWindowシステムで実装するのかについてはいろいろな選択がありえるので、Implementorクラスを取り替えることによってそれぞれのWindowシステムに対応できることになる。

Iteratorパターン

オブジェクトが元にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する。

class Iterator { //要素にアクセスするためのインタフェースの定義をする

  virtual void First();

virtual void Next() ;

virtual Boolean IsDone();

virtual Component* CrrentItem();

….};

class ConcreateIterator: public Iterator { // Iteratorの実装

   virtual void First() { …}

virtual void Next() { … }

virtual Boolean IsDone() { …}

virtual Component* CrrentItem() { … }

private:

  ConcrateAggregate *_current_item; // どこを操作しているかを記録

…}

class Aggregate { //Iteratorを生成するためのインタフェースを定義

virtual Iterator *CreateIterator();

…}

class ConcreateAggregate : public Aggregate {

   virtual Iterator *CreateIterator() // 適切なiteratorを返す

{ return new ConcreateIterator(this); }

  

}

このクラスの使い方は、たとえばfor文で以下のように書くことができる。

Aggregate *a;

Iterator *_i = a.CreateIterator();

for(_i.First(); !_i.isDone(); _i.Next()){

    … _i.CurrentItem(); … }

このコードでは、作るIteratorの種類によって、どのようにAggreateの中を操作するのかがきまっており、ユーザは適切なIteratorさええらべば、その内部構造がどうなっているのかについて気にする必要はない。

Visitorパターン

あるオブジェクト構造上の要素で実行されるオペレーションを表現する。このパターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに新しいオペレーションを定義することができるようになる。

class Visitor { // visitorのインタフェースを定義

 virtual void VisitConcreteElementA(ConcrateElementA)

 virtual void VisitConcreteElementB(ConcrateElementB)

 … }

class ConcreateVisitor: public Visitor { // 実装

 virtual void VisitConcreteElementA(ConcrateElementA)

 virtual void VisitConcreteElementB(ConcrateElementB)

 … }

class ObjectClass { 

private:  Element *_elem;

…}

class Element {

  virtual void Accept(Vistor) = 0;

 

}

class ConcreateElementA: public Element {

  virtual void Accept(Vistor v){ v.VisitElementA(this); }

  …}

これとIteratorを組み合わせて、

 Itaretor *_i; Visitor v;

  for(_i.First();!_i.IsDone();_i.Next()) _i.Crrent_Item()->Accept(v);

Visitorでは、vistor側にオブジェクトのdispatchを任せる。

 

次回は、デザインパターン(その2)。