1
0

デザインパターン Factoryパターン

Last updated at Posted at 2023-10-13

Factoryパターン

Creational Design Pattern の一つで、クライアントに対してインスタンスの生成を隠蔽するためのパターン。

具象クラスのインスタンス化を隠蔽またはカプセル化することで、クライアントは直接クラスをnew演算子によってインスタンス化するのではなく、ファクトリーに生成を依頼するようになる。

これにより、クライアントコードから具象クラスがなくなり、「変更に対しては閉じられているが、拡張に対しては開かれている」状態となる。

Factoryパターンには以下の3種類が存在する。

  • Simple Factory
  • Factory Method
  • Abstract Factory

Simple Factory

厳密にはGoF(Gang of Four)によって定義されるデザインパターンではないが、クライアントから具象クラスを分離させるための一つのスタイルである。

インスタンスを返却するメソッドがコンストラクタの役割を果たすことにより、具象クラスのインスタンス化をカプセル化する。

どの具象クラスをインスタンス化するのかを、クライアントがメソッドの引数(パラメータ)によって決定する。

クラスが Factory (インスタンス生成)の役割を果たす。

生成する具象クラスを、クライアントが選択する
クラスが Factory

Simple Factoryの例

生成するオブジェクトは、現時点でJapaneseFoodFrenchFoodChineseFoodの3種類が存在し、それぞれFoodインターフェースを実装しているものとする。

これらのオブジェクトはResutaurantクラス(Foodのクライアントに相当)で利用されていて、注文に応じて3種類の中から1つを選択してcook()が実行される。
SimpleFactory.png

Food.java
// Factory で生成されるクラスのインターフェース
public interface Food {
    public void cut();
    public void addSalt();
    public void addSugar();
}
JapaneseFood.java
// Factory で生成される具象クラス①
public class JapaneseFood implements Food {

    @Override
    public void cut() {
        System.out.println("食材を千切りします。");
    }

    @Override
    public void addSalt() {
        System.out.println("塩を5g加えます。");
    }

    @Override
    public void addSugar() {
        System.out.println("砂糖を5g加えます。");
    }
}
FrenchFood.java
// Factory で生成される具象クラス②
public class FrenchFood implements Food {

    @Override
    public void cut() {
        System.out.println("食材を乱切りします。");
    }

    @Override
    public void addSalt() {
        System.out.println("塩を1g加えます。");
    }

    @Override
    public void addSugar() {
        System.out.println("砂糖を1g加えます。");
    }
}
ChineseFood.java
// Factory で生成される具象クラス③
public class ChineseFood implements Food {

    @Override
    public void cut() {
        System.out.println("食材を輪切りします。");
    }

    @Override
    public void addSalt() {
        System.out.println("塩を10g加えます。");
    }

    @Override
    public void addSugar() {
        System.out.println("砂糖を10g加えます。");
    }
}
Restaurant.java
// Factory を利用する、クライアントに当たるクラス
public class Restaurant {

    FoodFactory factory;

    public Restaurant(FoodFactory factory) {
        this.factory = factory;
    }

    public void cook() {
        // new が使用されていない
        // クライアントが具象クラスを選択している
        Food food = factory.createFood("japanese");
        food.cut();
        food.addSalt();
        food.addSugar();
    }
}
FoodFactory.java
// Factory クラス
// 具象クラスのインスタンス化がここに集約・カプセル化、隠蔽されている
public class FoodFactory {
    // static にしてもOK(ただし static の場合、継承による機能拡張ができない)
    public Food createFood(String type) {
        if (type.equals("japanese")) {
            return new JapaneseFood();
        } else if (type.equals("chinese")) {
            return new ChineseFood();
        } else if (type.equals("french")) {
            return new FrenchFood();
        } else {
            throw new IllegalArgumentException();
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Restaurant restaurant = new Restaurant(new FoodFactory());
        restaurant.cook();
        // >>食材を千切りします。
        // >>塩を5g加えます。
        // >>砂糖を5g加えます。
    }
}

Simple Factory を利用したことで、クライアントのプログラムコードから具象クラスが消え、クライアントが具象クラスにではなく、インターフェースに依存するようになった(具象クラスに依存するよりも、抽象クラスに依存するプログラミングが良いとされる)。

Factory Method

インスタンス生成するメソッド( Factory )をスーパークラスの抽象メソッドなどで定義し、継承したサブクラスが実際にどの具象クラスをインスタンス化するのかを実装する。ただし、どの Factory を利用するかの選択はクライアントが行う。

生成する具象クラスを、サブクラスが選択する
使用する Factory は、クライアントが選択する
メソッドが Factory(一つのメソッドが一つのオブジェクトを生成する)
継承を利用する

メソッドが Factory (インスタンス生成)の役割を果たす。

Factory Method の利用例

FactoryMethod.png

Foodインターフェースを実装した、以下の6つの具象クラスが存在するとする。

  • CasualJapaneseFood
  • CasualChineseFood
  • CasualFrenchFood
  • LuxuriousJapaneseFood
  • LuxuriousChineseFood
  • LuxuriousFrenchFood

これらの具象クラスを生成するメソッドはcreateFood()だが、japaneseが指定された時にCasualJapaneseFoodを生成する Factory と、LuxuriousJapaneseFoodを生成する Factory が必要になる。

そこで、Factory (インスタンス生成を行うメソッド)をスーパークラスの抽象メソッド(createFood())として用意して、サブクラスCasualRestaurantとサブクラスLuxuriousRestaurantで実装する。

クライアントはまず、以下の2つの選択を行う。

  • japanese or chinese or french (この選択は必須ではない)
  • CasualRestaurant or LuxuriousRestaurant (Factoryの選択)

選択された Restaurant は、具象クラス6つの中から一つを選択して、インスタンスの生成を行う。例えば、japaneseCasualRestaurantが選択された場合、CasualJapaneseFoodの生成を行う。

ここで重要なのは、クライアントは Simple Factory と同様に、具象クラスの選択をしていないということ。あくまでも具象クラスの選択はサブクラスが行なっていて、クライアントは Factory の選択をしている

Food.java
public interface Food {
    public void cut();
    public void addSalt();
    public void addSugar();
}
CasualJapaneseFood.java
// 具象クラス①
public class CasualJapaneseFood implements Food {
    @Override
    public void cut() { System.out.println("普通の包丁で、食材を千切りします。"); }
    @Override
    public void addSalt() { System.out.println("普通の塩を、5g加えます。"); }
    @Override
    public void addSugar() { System.out.println("普通の砂糖を、5g加えます。"); }
}
CasualChineseFood.java
// 具象クラス②
public class CasualChineseFood implements Food {
    @Override
    public void cut() { System.out.println("普通の包丁で、食材を千切りします。"); }
    @Override
    public void addSalt() { System.out.println("普通の塩を、5g加えます。"); }
    @Override
    public void addSugar() { System.out.println("普通の砂糖を、5g加えます。"); }
}
CasualFrenchFood.java
// 具象クラス③
public class CasualFrenchFood implements Food {
    @Override
    public void cut() { System.out.println("普通の包丁で、食材を乱切りします。"); }
    @Override
    public void addSalt() { System.out.println("普通の塩を、1g加えます。"); }
    @Override
    public void addSugar() { System.out.println("普通の砂糖を、1g加えます。"); }
}
LuxuriousJapaneseFood.java
// 具象クラス④
public class LuxuriousJapaneseFood implements Food {
    @Override
    public void cut() { System.out.println("高級な包丁で、食材を千切りします。"); }
    @Override
    public void addSalt() { System.out.println("高級な塩を、5g加えます。"); }
    @Override
    public void addSugar() { System.out.println("高級な砂糖を、5g加えます。"); }
}
LuxuriousChineseFood.java
// 具象クラス⑤
public class LuxuriousChineseFood implements Food {
    @Override
    public void cut() { System.out.println("高級な包丁で、食材を輪切りします。"); }
    @Override
    public void addSalt() { System.out.println("高級な塩を、10g加えます。"); }
    @Override
    public void addSugar() { System.out.println("高級な砂糖を、10g加えます。"); }
}
LuxuriousFrenchFood.java
// 具象クラス⑥
public class LuxuriousFrenchFood implements Food {
    @Override
    public void cut() { System.out.println("高級な包丁で、食材を乱切りします。"); }
    @Override
    public void addSalt() { System.out.println("高級な塩を、1g加えます。"); }
    @Override
    public void addSugar() { System.out.println("高級な砂糖を、1g加えます。"); }
}
Restaurant.java
// スーパークラス
public abstract class Restaurant {

    public void cook(String type) {
        Food food = createFood(type);
        food.cut();
        food.addSalt();
        food.addSugar();
    }

    // ⭐️ Factory Method (インスタンスを生成するメソッド) ⭐️
    // ただし、抽象メソッドのため、実装はサブクラスが行う(デフォルトメソッドとしてもOK)
    // Restaurant は実際にどの具象クラスが生成されるかを知らない。Restaurant を利用するクライアントも知らない。
    protected abstract Food createFood(String type);
}
CasualRestaurant.java
// サブクラス①
public class CasualRestaurant extends Restaurant {
    // サブクラスが生成する具象クラスを選択する
    @Override
    protected Food createFood(String type) {
        if (type.equals("japanese")) {
            return new CasualJapaneseFood(); // 具象クラス①
        } else if (type.equals("chinese")) {
            return new CasualChineseFood(); // 具象クラス②
        } else if (type.equals("french")) {
            return new CasualFrenchFood(); // 具象クラス③
        } else {
            throw new IllegalArgumentException();
        }
    }
}
LuxuriousRestaurant.java
// サブクラス②
public class LuxuriousRestaurant extends Restaurant {
    // サブクラスが生成する具象クラスを選択する
    @Override
    protected Food createFood(String type) {
        if (type.equals("japanese")) {
            return new LuxuriousJapaneseFood(); // 具象クラス④
        } else if (type.equals("chinese")) {
            return new LuxuriousChineseFood(); // 具象クラス⑤
        } else if (type.equals("french")) {
            return new LuxuriousFrenchFood(); // 具象クラス⑥
        } else {
            throw new IllegalArgumentException();
        }
    }
}
Main.java
// クライアント
public class Main {
    public static void main(String[] args){
        // クライアントは Factory の選択をするが、どの具象クラスが生成されるかを知らない
        Restaurant restaurant = new CasualRestaurant();
        restaurant.cook("japanese");
        // >>普通の包丁で、食材を千切りします。
        // >>普通の塩を、5g加えます。
        // >>普通の砂糖を、5g加えます。
    }
}

Abstract Factory

複数の関連するオブジェクト(オブジェクト群、Family)を、コンポジションによって保持された Factory がまとめて生成をする。

Factory (インスタンス生成のメソッドが集約されたクラス)は、インターフェースで定義する。複数の Factory が存在し、インターフェースを実装したそれぞれの Factory が関連するオブジェクトをまとめて生成する。

Abstract Factory の内部では、Factory Method が利用されている。

Factory of Factoriesとも呼ばれることもある。

使用する Factory は、クライアントが選択する
メソッドが Factory(一つのメソッドが複数のオブジェクトを生成する)
コンポジションを利用する

Abstract Factory の利用例

SugarSaltから構成されるオブジェクト群が存在するとして、

  • CasualSugarCasualSalt というオブジェクト群
  • LuxuriousSugarLuxuriousSalt というオブジェクト群

の2種類があるものとする。

Casualと名前がつくオブジェクト群と、Luxuriousと名前がつくオブジェクト群は、それぞれ別でまとめて生成したい。

このような場合に、メソッド prepare()CondimentShopクラスに用意して、保持(コンポジション)している Factory クラスを利用して、まとめてオブジェクト群の生成が行われる
AbstractFactory.png

Sugar.java
// オブジェクト群の構成要素①
public interface Sugar {
    public void description();
}
Salt.java
// オブジェクト群の構成要素②
public interface Salt {
    public void description();
}
CasualSugar.java
// オブジェクト群の構成要素①
public class CasualSugar implements Sugar {
    public void description() {
        System.out.println("普通の砂糖です。");
    }
}
CasualSalt.java
// オブジェクト群の構成要素②
public class CasualSalt implements Salt {
    @Override
    public void description() {
        System.out.println("普通の塩です。");
    }
}
LuxuriousSugar.java
// オブジェクト群の構成要素①
public class LuxuriousSugar implements Sugar {
    public void description() {
        System.out.println("高級な砂糖です。");
    }
}
LuxuriousSalt.java
// オブジェクト群の構成要素②
public class LuxuriousSalt implements Salt {
    public void description() {
        System.out.println("高級な塩です。");
    }
}
CondimentFactory.java
// Factory インターフェース
public interface CondimentFactory {
    public Salt createSalt();
    public Sugar createSugar();
}
.java
// オブジェクト群①を生成するための Factory
public class CasualCondimentFactory implements CondimentFactory {
    @Override
    public Salt createSalt() {
        return new CasualSalt();
    }

    @Override
    public Sugar createSugar() {
        return new CasualSugar();
    }
}
.java
// オブジェクト群②を生成するための Factory
public class LuxuriousCondimentFactory implements CondimentFactory {
    @Override
    public Salt createSalt() {
        return new LuxuriousSalt();
    }

    @Override
    public Sugar createSugar() {
        return new LuxuriousSugar();
    }
}
.java
// Factory を保持するクラス
public class CondimentShop {

    CondimentFactory factory; // Factory が保持されている
    Sugar sugar;
    Salt salt;

    public CondimentShop(CondimentFactory factory) {
        this.factory = factory;
    }

    // オブジェクト群を生成するためのメソッド
    public void prepare() {
        sugar = factory.createSugar();
        salt = factory.createSalt();
    }

    public void description() {
        sugar.description();
        salt.description();
    }
}
Main.java
// クライアント
public class Main {
    public static void main(String[] args) {
        // クライアントは Factory を選択するが、Salt、Sugarの具象クラスについては何も知らない(依存関係がない)
        CondimentFactory casualFactory = new CasualCondimentFactory();
        CondimentShop casualShop = new CondimentShop(casualFactory);
        casualShop.prepare(); // 具象クラスのオブジェクト群を生成するメソッド(new が登場しない = 具象クラスに依存しない)
        casualShop.description();
        // >>普通の砂糖です。
        // >>普通の塩です。

        // クライアントは Factory を選択するが、Salt、Sugarの具象クラスについては何も知らない(依存関係がない)
        CondimentFactory luxuriousFactory = new LuxuriousCondimentFactory();
        CondimentShop luxuriousShop = new CondimentShop(luxuriousFactory);
        luxuriousShop.prepare(); // 具象クラスのオブジェクト群を生成するメソッド(new が登場しない = 具象クラスに依存しない)
        luxuriousShop.description();
        // >>高級な砂糖です。
        // >>高級な塩です。
    }
}

参考

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0