kakke18’s blog

ゆるふわ学生エンジニア

Decorator-飾り枠と中身の同一視-

Java言語で学ぶデザインパターン入門 の第12章を参考にしました。

Decoratorパターンとは

本書には以下のように記載されています。

オブジェクトにどんどんデコレーションを施していくようなデザインパターン

ここでいう”デコレーション”とは、プログラムでいうと”機能”のことです。 つまり、Decoratorパターンとは、機能を一皮一皮被せていって、より目的となるオブジェクトを仕上げるデザインパターンです。

これは、前回の記事 で紹介したCompositeパターンのように中身と飾り枠(機能)を同一視することによって実現しています。

実装

こちらを参考にしてください。

登場人物

  • Component役(Displayクラス)

    機能を追加するときの核であり、APIを定義する

  • ConcreteComposite役(StringDisplayクラス)

    Component役で定義されたAPIを実装する

  • Decorator役(Borderクラス)

    Component役と同じAPIを持ち、自身が飾る対象であるComponent役を保持する。

  • ConcreteDecorator役(SideBorderクラス、FullBorderクラス)

    Decorator役で定義されたAPIを実装する

実装のポイント

個人的には、Displayクラスを継承したBorderクラスでは、Displayクラスの各メソッドを実装せず、 Borderクラスを継承したSideBorderクラスやFullBorderクラスでDisplayクラスの各メソッドを実装している点が ポイントかなと思いました。

Decoratorパターンのメリット

中身を変えずに機能を追加することができる

飾り枠を追加すればするほど機能が追加されます。 サンプルプログラムでは、SideBorderやFullBorderで包めば包むほどたくさん装飾されます。 これは、包まれる側(中身)を全く変更することなく、機能の追加をすることができることを表しています。

単純な品揃えでも、多様な機能を追加することができる

Decoratorパターンを使うと、ConcreteDecoratorをたくさん用意してだけで、 それらを自由に組み合わせて新しいオブジェクトを作成することができます。

まとめ

Decoratorパターンを使うと、透過的なAPIを保ったまま機能を追加することが可能。

Composite-容器と中身の同一視-

Java言語で学ぶデザインパターン入門 の第11章を参考にしました。

Compositeパターンとは

本書には以下のように記載されています。

容器と中身を同一視し、再帰的な構造を作るデザインパターン

Compositeパターンを説明するために、コンピュータのファイルシステムを例とします。

ファイルシステム

ファイルシステムには、「ディレクトリ」というものが存在します。 そのディレクトリの中には、ファイルや別のディレクトリ(サブディレクトリ)が入っています。 つまり、ディレクトリは「入れ子」のような再帰的な構造をしています。 このディレクトリとファイルは異なるものですが、どちらも「ディレクトリのなかに入れることができるもの」であるので、 ディレクトリとファイルを同じ種類のものと見なす(同一視する)ことができます。

上記の例では、ディレクトリは、サブディレクトリやファイルを入れる「容器」であり、 ファイルは「中身」として、「容器」と「中身」を同一視することで再帰的な構造を実現しています。 このようなデザインパターンをCompositeパターンといいます。

実装

こちらを参考にしてください。

登場人物

  • Leaf役(Fileクラス)

    「中身」を表す。

  • Composite役(Directoryクラス)

    「容器」を表す。

  • Component役(Entryクラス)

    Leaf役とComposite役を同一視するための役

  • Client役(Mainクラス)

    Compositeパターンの利用者

実装のポイント

  • Entryクラスには、引数なしのprintListメソッドと引数ありのprintListメソッドが2つあります。 これをprintListメソッドの多重定義といい、メソッドを呼び出すときの引数の型に応じて、適切なメソッドが実行されます。
  • FileクラスのprintListメソッドでは、System.out.println(prefix + "/" + this)という記述があります。 ここでのthisのように文字列としてオブジェクトを加えると、自動的にそのオブジェクトのtoStringメソッドが呼び出されます (Java言語の仕様)。

Compositeパターンのメリット

「容器」と「中身」を同一視するということは、単数と複数を同一視するということです。 これは、Client役のようなCompositeパターンを利用する役からしたら、 いちいち中身を意識する必要がないということです。

まとめ

Compositeパターンとは、「容器」と「中身」を同一視し、再帰的な構造を形作るデザインパターンである。

Strategy-アルゴリズムをごっそり切り替える-

Java言語で学ぶデザインパターン入門 の第10章を参考にしました。

Strategyパターンとは

本書には以下のように記載されています。

アルゴリズムをカチッと切り替え、同じ問題を別の方法で解くのを容易にするデザインパターン

"strategy”には、"戦略"という意味があり、問題を解く戦略のことをアルゴリズムと表し、 それを自在に切り替えることのできるデザインパターンのことです。

実装

こちらを参考にしてください。

登場人物

  • Strategy役(Strategyインターフェース)

    戦略を利用するためのインターフェースを定める。

  • ConcreteStrategy役(WinningStrategyクラス等)

    Strategy役が定めたインターフェースを実装する。

  • Context役(Playerクラス)

    Strategy役を利用する。

実装のポイント

  • じゃんけんの勝敗判定は、グー(0)・チョキ(1)・パー(2)とし、 (this.handvalue + 1) % 3 == h.handvalue という式で行われます。
  • ProbStrategyクラスは、intの2次元配列であるhistory[一個前の手][次出す手]を過去の勝率とし、 次の手を決定します。

Strategyパターンのメリット

アルゴリズムの部分を分離しています。 つまり、委譲によってアルゴリズムを利用しているので、 プログラムの動作中にもアルゴリズムを切り替えうことが可能です。 よって、

などのようなことが可能となります。

まとめ

Strategyパターンは、アルゴリズムを委譲によって利用することで、 アルゴリズムを容易に切り替えることが可能である。

Bridge-機能の階層と実装の階層を分ける-

Java言語で学ぶデザインパターン入門 の第9章を参考にしました。

Bridgeパターンとは

本書には以下のように記載されています。

「機能のクラス階層」と「実装のクラス階層」を橋渡しする

まず、「機能のクラス階層」と「実装のクラス階層」の説明をしていきます。

機能のクラス階層

あるクラスSomethingがあったとします。 新しい機能(新しいメソッド)を追加するとき、Somethingクラスのサブクラスとして、 SomethingGoodクラスを作ります。 ここでSomethingとSomethingGoodによる小さなクラス階層ができました。 ここでは、スーパークラスは基本的な機能を持っており、サブクラスで新しい機能を追加します。 このように機能を追加するために作られた階層を「機能のクラス階層」といいます。

実装のクラス階層

あるスーパークラスAbstractClassの抽象メソッドを実装したサブクラスをConcreteClassとすると、 ここでも、この2つのクラスによる小さなクラス階層ができました。 ここでは、スーパークラスが抽象メソッドによって規定したインターフェースをサブクラスが具象メソッドによって実装します。 このように実装を追加するために作られた階層を「実装のクラス階層」といいます。

実装

こちらを参考にしてください。

クラス階層

このサンプルプログラムでの階層は以下のようになっています。

  • 機能のクラス階層
    • Displayクラス
    • CountDisplayクラス
  • 実装のクラス階層
    • DisplayImplクラス
    • StringDisplayImplクラス

登場人物

  • Abstraction役(Displayクラス)

    「機能のクラス階層」の最上位であり、Implementor役のメソッドを使って基本的な機能だけを記述する。

  • RefinedAbstraction役(CountDisplayクラス)

    Abstraction役に機能を追加する役

  • Implementor役(DisplayImplクラス)

    「実装のクラス階層」の最上位であり、Abstraction役のインターフェースを実装するためのメソッドを規定する。

  • ConcreteImplementor役(StringDisplayImplクラス)

    Implementor役のインターフェースを実装する。

実装のポイント

Displayクラスのコンストラクタには、実装を表すクラスのインスタンスを渡します。 これが2つのクラス階層の橋渡しをしているのです。

また、Displayクラスの3つのクラス(open, print, close)は、 実装を表すクラスに処理を委譲しています。

Abstract Factoryパターンのメリット

Bridgeパターンの特徴は、階層ごとに分けていることです。 これにより、それぞれを独立して拡張することができます。

「継承」は、クラスを拡張する便利な方法だが、クラス間を密結合にしてしまう。 必要に応じて、クラス間の関係を切り替えたい場合、「継承」では、 ソースコードを書き換えなければいけません。 そのような時に、「委譲」を使います。 「委譲」により処理を”たらい回し”することで、クラス間が「継承」に比べて疎結合となります。

疎結合であるので、StringDisplayImplクラス以外のConcreteImplementor役があったとしても、 Mainクラスだけ切り替えの修正を行えば、実装できます。 つまり、疎結合である方が、修正しやすいということです。

まとめ

Bridgeパターンは、機能のクラス階層と実装のクラス階層に分け、それらを委譲によって橋渡しすることによって、 クラス間を疎結合にしている。

Abstract Factory-関連する部品を組み合わせて製品を作る-

Java言語で学ぶデザインパターン入門 の第9章を参考にしました。

Abstract Factoryパターンとは

本書には以下のように記載されています。

部品の具体的な実装には注目せず、インターフェースに注目し、 そのインターフェースのみで部品を組立て、製品にまとめる

つまり、抽象的な部品を組み合わせて抽象的な製品を作る抽象的な工場を用いたデザインパターンです。

実装

こちらを参考にしてください。

登場人物

  • AbstractProduct役(Linkクラス、Trayクラス等)

    AbstractFactory役によって作り出される抽象的な部品や製品のインターフェースを定める

  • AbstractFactory役(Factoryクラス)

    AbstractProduct役のインスタンスを作り出すためのインターフェースを定める

  • Client役(Mainクラス)

    AbstractProduct役とAbstractFactory役のインターフェースだけを使って仕事を行う

  • ConcreteProduct役(ListLinkクラス、ListTrayクラス等)

    AbstractProduct役のインターフェースを実装する

  • ConcreteFactory役(ListFactory)

    AbstractFactory役のインターフェースを実装する

実装のポイント

このサンプルプログラムは、以下の3つのパッケージに分かれている。

  • factoryパッケージ

    抽象的な工場・部品・製品を含む

  • 無名パッケージ

    Mainクラスを含む

  • listfactoryパッケージ

    具体的な工場・部品・製品を含む

LinkやTrayは抽象的な「部品」であり、Pageは抽象的な「製品」であります(部品を組み合わせて製品になる)。 なので。LinkクラスとTrayクラスはItemクラスを継承しており、PageクラスのaddメソッドではItem型の変数を引数に持ちます。 そして、それらを作ることができるFactoryクラスが実装されています。

Abstract Factoryパターンのメリット

Mainクラスでは、具体的な部品、製品、工場のインスタンス(ListLink等)を使っていないので、 今回使用したlistfactory以外の具体的な工場を追加することが容易です。 しかし、部品を追加するとその他の工場にもその部品を追加するメソッドを追加しなければならないので、 部品を新たに追加するのは困難です。

まとめ

Abstract Factoryパターンは、抽象的な部品を組み合わせて抽象的な製品を作る抽象的な工場を用いることで、 抽象度の高い実装を行うことが可能である。

Builder-複雑なインスタンスを組み上げる-

Java言語で学ぶデザインパターン入門 の第7章を参考にしました。

Builderパターンとは

本書には以下のように記載されています。

構造を持ったインスタンスを組み上げていくデザインパターン

つまり、一気に完成品を作成するのではなく、全体を構成している各部品を作り、 段階を踏んで組み上げていくことです。

実装

こちらを参考にしてください。

登場人物

  • Builder役(Builderクラス)

    インスタンスを生成するためのインターフェースを定める

  • ConcreteBuilder役(TextBuilderクラス、HTMLBuilderクラス)

    Builde役のインターフェースを実装する

  • Director役(Directorクラス)

    Builder役のインターフェースを使ってインスタンスを生成する

  • Client役(Mainクラス)

    Builderパターンを利用する

実装のポイント

  • Directorクラスのコンストラクタの引数は、Builder型だが、Builderクラスは抽象クラスなので、 Bulder型のインスタンスが引数として与えられることはない
  • インスタンス生成を行うDirector役は、Builderのメソッドのみを使うので、実際に動いているのが TextBuilderなのかHTMLBuilderなのかを意識しない

Builderパターンのメリット

  • MainクラスからBuilderクラスのメソッドは知らない
    • MainクラスはDirectorクラスのconstructメソッドを呼び出しただけでDirectorクラスが文書を生成する
    • 具体的な文書の作成手順はMainクラスが知る必要がない
  • Directorクラスは、自分が実際に利用しているクラスが何であるかを知らない
    • Builderクラスを継承したクラスであれば良い

この2点のように、オブジェクト指向プログラミングでは、「誰が何を知っているか」ということは非常に重要です。 Builderパターンでは、知らないからこそ交換可能であります (サンプルプログラムでは、Text or HTML)。 つまり、部品として機能しているということです。

まとめ

Builderパターンは、構造を持ったインスタンスを組み上げていくデザインパターンであり、 その組み上げていく過程はDirectorクラスにより隠蔽されている。

Prototype-コピーしてインスタンスを作る-

Java言語で学ぶデザインパターン入門 の第6章を参考にしました。

つまり、new Hoge();のようにクラス名を指定してインスタンス生成を行うのではなく、 クラス名を指定せずにインスタンスを複製して新しいインスタンスを作るということです。 それは、以下のような場合に使われます。

  • 種類が多すぎてクラスにまとめられない

    扱うオブジェクトの種類が多すぎて、1つ1つを別のクラスにするとソースファイルを多数作成する必要が生じる場合

  • クラスからのインスタンス生成が困難

    ユーザの操作によって作成されるインスタンスのように複雑な過程でインスタンスが生成される場合

  • フレームワークをクラスに依存させない

    インスタンスを生成するフレームワークを特定のクラスに依存しないように作る場合

このような場面において、Prototypeパターンは有効であります。 "prototype"には、”原型”という意味があり、 原型となるインスタンスを元に新たなインスタンスを作り出します。 これらを実現するためにJavaのcloneメソッドやCloneableインターフェースを用います。

実装

こちらを参考にしてください。

登場人物

  • Prototype役(Prototypeインターフェース)

    インスタンスを複製して新しいインスタンスを作るためのメソッドを定める

  • ConcretePrototype役(MessageBoxクラス、UnderlinePenクラス)

    Prototypeで定められたメソッドを実装する

  • Client役(Manegerクラス)

    インスタンスをコピーするメソッドを利用して、新しいインスタンスを作る

実装のポイント

  • Productインターフェースのようにjava.util.Cloneableインターフェースを継承させたインターフェースを実装したクラス (ConcretePrototype役)のインスタンスは、 cloneメソッドを用いることで自動的に複製を行うことが可能である。
  • java.util.HashMapを用いて、”名前”と”インスタンス”の関係を表現(Managerクラス)

Prototypeパターンのメリット

なぜnew Hoge();のように素直にインスタンス生成をしないのでしょうか。 それは、前述した通り3つの場合において、クラス名を指定せずにインスタンス生成させるほうが都合が良いからです。

種類が多すぎてクラスにまとめられない

今回のサンプルプログラムでは、以下の3つの雛形が登場しました。

  • '~'を使って文字列に下線を引くもの
  • '*'を使って文字列に枠をつけるもの
  • '/'を使って文字列に枠をつけるもの

この例では3つだけでしたが、このようなものは作ろうと思えば大量に作ることができます。 それら全てを別々のクラスにしていては、クラス数が多くなりすぎて管理コストがかかってしまいます。

クラスからのインスタンス生成が困難

今回の例では、実感ができません。

フレームワークをクラスに依存させない

サンプルプログラムでは、インスタンスの複製を行う部分をframeworkパッケージの中に閉じ込めています。 そして、Managerクラスのcreateメソッドによって、クラス名の代わりに文字列をインスタンス生成のための名前として与えています。 つまり、frameworksパッケージの中では、特定のクラス名が使用されていないので、クラスに依存していないことになります。

ここで、「クラス名に依存することは悪いことなのか」という疑問が浮かぶと思います。 ソースの中に利用するクラス名を書くことが常に悪いことではありませんが、 そのソースとクラスは切り離して再利用することが不可能となります。 つまり、オブジェクト指向プログラミングの目標の一つである「部品としての再利用」が困難になってしまいます。

まとめ

Prototypeパターンは、生成するべきインスタンスが大量の場合などインスタンスからインスタンス生成を行いたい場合に有効なデザインパターンである。