技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

Posts in the デザインパターン category

Effective Javaを読んでいたところ、勉強になったことがあったのでメモしておく。
 

コンポジションとは

コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。
特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
厳密なオブジェクト指向では、継承は「機能の継承」を目的とせず、「スーパークラスはサブクラスの一種である」といういわゆる「is-a」の関係を持たなければならない。そのため、単に機能を持たせたい場合には、継承ではなくコンポジションとすることが推奨される。
 

継承とコンポジションをどう使い分けるか

継承を使ったほうが良い場合とコンポジションを使ったほうが良い場合があるが、具体的に何を基準にして使い分ければ良いだろうか。
この問題を解決するために、一般的な物の考え方の一つに「分類」と「分割」がある。
 
分類とは、A is a B.のことであり、日本語だと「AはBである」と表現できる。これをis-aの関係と呼ぶ。
例えば、「犬は動物である」「猫は動物である」「人間は動物である」・・・など、これらは分類で表現することが出来る。
is-aの関係で表せるものは、継承で表現するのが都合がいい。
 
分割とは、A has a B.のことであり、日本語だと「AはBを含んでいる」と表現できる。これをhas-aの関係と呼ぶ。
例えば、「パソコンはCPUを含んでいる」「学校は先生を含んでいる」「自転車はサドルを含んでいる」・・・など、これらは分割で表現することが出来る。
has-aの関係で表せるものは、コンポジションで表現するのが都合がいい。
 

継承とコンポジションの違い

ここで改めて、継承とコンポジションの違いを明確にするために、コードを例に挙げて説明する。
まず、親となるPersonクラスを定義する。Personクラスは「氏名」と「年齢」を保持し、要求に応じてそれを返す。

public class Person {
    private String name;
    private int age;

    public Person(String n, int a) {
        name = n;
        age  = a;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

 
ここでPersonクラスを元にして、新たに「学年(grade)」を保持し、要求に応じてそれを返すことが出来るクラスを作りたい場合を考えてみる。
以下より、継承とコンポジション、それぞれを使用した場合の違いをコードで説明する。
 

継承を使った場合のコード

public class Student extends Person {
    private int grade;

    public Student(String n, int a, int g) {
        super(n, a);
        grade = g;
    }

    public int getGrade() {
        return grade;
    }
}
Student yamada = new Student("山田", 20, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 

コンポジションを使った場合のコード

public class Student {
    private Person person;
    private int grade;

    public Student(Person p, int g) {
        person = p;
        grade  = g;
    }

    public String getName() {
        return person.getName();
    }

    public int getAge() {
        return person.getAge();
    }

    public int getGrade() {
        return grade;
    }
}
Person y       = new Person("山田", 20, 2);
Student yamada = new Student(y, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 
上記の例では、継承とコンポジション、どちらを使っても自然に記述することが可能だが「継承とコンポジションをどう使い分けるか」の項目で説明した通り、どちらか一方では上手く記述できない場合がある。そういったときは、一度立ち止まってis-aとhas-aのどちらに当てはまるかを考えてみることが必要だ。
 
書籍「Effective Java」の「項目16 継承よりコンポジションを選ぶ」には、今回載せたコンポジションのコードよりも頑強な設計のコードが載っている。しかし、少々理解し辛い部分があったので、富士通さんの記事に載っていたコードを引用させていただいて説明した。気になった方はEffective Javaを読んでみると良いと思う。
 
 
以上
 
 
参考
書籍 Effective Java
コンポジションとは : JavaA2Z
オブジェクト指向プログラミングへの道 5日目:オブジェクトコンポジション : 富士通
オブジェクト思考: is-a関係とhas-a関係: 継承と包含
is-a – Wikipedia
has-a – Wikipedia

デザインパターンのうちのBuilderパターンを勉強したのでメモしておく。
 

Builderパターンとは

Builderとは、建築者や建築業者などを意味する単語である。
このパターンは複数のコンストラクタを用いて初期化を行いたいような場合に複雑さを抑えることが出来る。
 

Builderパターンを使わない場合の初期化処理

伝統的には、複数のコンストラクタで初期化を行うときには以下のように記述する。

public class foo {
    private int a;
    private int b;
    private int c;

    public foo(a) {
        ……
    }

    public foo(a, b) {
        ……
    }

    public foo(a, b, c) {
        ……
    }
}

 
これはテレスコーピングコンストラクタパターンと呼ばれ、パラメータが増えれば、それに伴ってコンストラクタも増えていくことになる。たった3個のパラメータでは、テレスコーピングコンストラクタパターンはそれほど悪く見えないが、パラメータの数が増えるとすぐに手に負えなくなる。
テレスコーピングコンストラクタパターンは、機能こそするが、多くのパラメータがある場合にはクライアントのコードを書くのが困難になり、そのコードを読むのは更に困難になる。読み手は、パラメータの値が何を意味するかを考えさせられる上に、意味を知るために注意深くパラメータ数を数えなければならない。
 

Builderパターンを使った初期化処理

GoFデザインパターンのBuilderパターン

以下はGoFデザインパターンのBuilderパターンである。

class Dog {
    private String name;
    private Integer age;
    private String hobby;

    String hello() {
        //...
    }
}

class Chihuahua {
    private Builder builder;

    Chihuahua(Builder builder) {
        this.builder = builder;
    }

    void construct() {
        builder.name("Chibi");
        builder.age(3);
        builder.hobby("Dance");
    }
}

interface Builder {
    void name(String name);
    void age(Integer age);
    void hobby(String hobby);

    Dog getResult();
}

class DogBuilder implements Builder {
    private Dog dog;

    DogBuilder() {
        this.dog = new Dog();
    }

    @Override
    public void name(String name) {
        dog.setName(name);
    }
    @Override
    public void age(Integer age) {
        dog.setAge(age);
    }
    @Override
    public void hobby(String hobby) {
        dog.setHobby(hobby);
    }
    @Override
    public Dog getResult() {
        if (dog.getName() == null || dog.getAge() == null) {
            throw new NullPointerException();
        }
        return this.dog;
    }
}

 

Builder builder = new DogBuilder();
Chihuahua chihuahua = new Chihuahua(builder);
chihuahua.construct();
builder.getResult().hello();

 
interfaceや実装など、記述量が多くなるが、Builderの実装クラス(Chihuahua)を切り替えることで、生成するDogの制御が出来る。
 

Effective JavaのBuilderパターン

こちらは、書籍 Effective Javaに載っているBuilderパターンである。

public class Dog {
    private final String  name;
    private final Integer age;
    private final String  hobby;

    public static class Builder {
        //必須パラメータ
        private final String name;

        //オプションパラメータ
        private Integer age;
        private String  hobby;

        public Builder(String name) {
            this.name = name;
        }

        public Builder age(int age) {
            this.age = age;
        }

        public Builder hobby(String hobby) {
            this.hobby = hobby;
        }

        public Dog build() {
            return new Dog(this);
        }
    }

    private Dog(Builder builder) {
        name  = builder.name;
        age   = builder.age;
        hobby = builder.hobby;
    }
}

 

Dog chihuahua = new Dog.Builder("Chibi").age(3).hobby("Dance").build();

 
Builderのセッターメソッドが自身を返すので、呼び出しを連鎖出来る。GoFのBuilderパターンと比べると、記述量が少なく、クライアントからの呼び出しを綺麗に書くことが出来る。
 
 
以上
 
 
参考
書籍 Effective Java
Javaで書くBuilderパターンのパターン – Qiita
7. Builder パターン | TECHSCORE(テックスコア)
10年間Javaを書いていた僕が Effective Java 第2版を読み返して新人に薦められるのかを考えてみた | susumuis Info

GoFデザインパターンのStrategyパターンについて勉強したのでメモ。
 
Strategyパターンでは、「インターフェースに対するプログラミング」を行うので事前知識として、こちらの記事に目を通しておくと良い。
 

Strategyパターンとは

Strategyパターンは一連のアルゴリズムを定義し、それぞれをカプセル化してそれらを交換可能にする。Strategyパターンによって、アルゴリズムを使用するクライアントとは独立して、アルゴリズムを変更出来る。
 

Strategyパターンの適用例

ここでは動物の鴨(Duck)の振る舞いを実装するクラスを例にとってStrategyパターンの適用例を考えてみる。
 
鴨を実装する時は鴨の共通動作がまとめられたDuckクラスを継承して行う。鴨の種類には普通の鴨とオモチャの鴨が存在する。普通の鴨の場合は「飛ぶ」ことが出来るのでFlyWithWingsクラスを使用し、オモチャの鴨の場合は「飛ぶ」ことが出来ないのでFlyNoWayクラスを使用する。当たり前だが、鴨の「飛ぶ」振る舞いは普通の鴨とオモチャの鴨で異なるので、Duckクラスに持たせることはできない。
 
以下では、Duck抽象クラスの一部の機能(振る舞い)をカプセル化して、FlyBehaviorインターフェースとFlyWithWingsクラス,FlyNoWayクラスに「飛ぶ」振る舞いとして抽出している。
 

Duckクラス

public abstract class Duck {
    //インターフェース型の変数を宣言する。サブクラスはこれを継承する。
    FlyBehavior flyBehavior; 

    public Duck(){
    }

    //鴨の表示はサブクラスで実装される。
    public abstract void display();

    public void performFly(){
        //「飛ぶ」振る舞いは他のクラスに委譲する。
        flyBehavior.fly();
    }

    public void swim(){
        System.out.println("全ての鴨は浮かぶ。オモチャの鴨でも。");
    }
}

 

FlyBehaviorインターフェース

public interface FlyBehavior {
    public void fly();
}

 

FlyWithWingsクラス

public class FlyWithWings implements FlyBehavior {
    public void fly(){
        System.out.println("飛んでいます。");
    }
}

 

FlyNoWayクラス

public class FlyNoWay implements FlyBehavior {
    public void fly(){
        System.out.println("飛べません。");
    }
}

 
上記を組み込んだクラスが以下のMallardDuckクラスとなる。
 

MallardDuckクラス

public class MallardDuck extends Duck {
    public MallardDuck(){
        //Duckクラスから継承したflyBehavior変数が使用される。
        flyBehavior = new FlyWithWings();
    }
    public void display(){
        System.out.println("本物のマガモです。");
    }
}

 
上記で書いたコードを実際に使ってテストしてみる。
 

テストクラス

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        mallard.performFly();
    }
}

 

Duckクラスを改良する

MallardDuckクラスの「飛ぶ」振る舞いの型をMallardDuckクラスのコンストラクタでインスタンス化するのではなく、Duckのサブクラスの設定メソッドを使用して設定したい場合を考えてみる。これはGoFデザインパターンの設計原則「インターフェースに対してプログラミングする」に沿ったものとなる。こう設計することで、「飛ぶ」振る舞いを動的に変更可能となる。
 
Duckクラスに新しいsetFlyBehaviorメソッドを追加する。
 

public abstract class Duck {

    FlyBehavior flyBehavior; 

    public Duck(){
    }

    public abstract void display();

    public void performFly(){
        flyBehavior.fly();
    }

    public void swim(){
        System.out.println("全ての鴨は浮かぶ。オモチャの鴨でも。");
    }

    //追加
    public void setFlyBehavior(FlyBehavior fb){
        flyBehavior = fb;
    }
}

 
上記で書いたコードを実際に使ってテストしてみる。
 

テストクラス

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();

        //MallardDuckのコンストラクタで指定された通り、
        //鴨は普通に飛ぶ。
        mallard.performFly();

        //setterで「飛ぶ」振る舞いを変更。
        mallard.setFlyBehavior(new FlyNoWay());

        //飛ぶことができなくなった。
        mallard.performFly();
    }
}

 
 
以上
 
 
参考
書籍 HeadFirstデザインパターン

少しずつデザインパターンを覚えていこうと思う。デザインパターンのうちのSingletonパターンを覚えたのでメモ。
 

Singletonパターンとは

Singletonは一枚札を意味する。一枚札とは、トランプの一組に含まれる唯一のカードである。
このパターンは、クラスのインスタンスが1つしか生成されないことを保証する。
 

Singletonパターンの適用例

以下にJavaによるSingletonパターンの例を示す。
 

public class RegisterNote{
    private static RegisterNote registerNote = new RegisterNote();
    private RegisterNote(){}
    public static RegisterNote getInstance(){
        return registerNote;
    }
}

 
注目すべき点は、コンストラクタの識別子が private となっていること。コンストラクタの識別子が private になっていることで、RegisterNoteのインスタンスを外部から生成することができない。では、どうするのかと言うと以下のように取得せざるを得なくなっている。
 

RegisterNote note = RegisterNote.getInstance();

 
 
以上
 
 
参考
5. Singleton パターン | TECHSCORE(テックスコア)
書籍 Java言語で学ぶ デザインパターン入門