Help us understand the problem. What is going on with this article?

デザインパターンをkotlinで書いてみた Factory編

More than 1 year has passed since last update.

オブジェクト指向で大切になるInterfaceの考え方やオブジェクトの再利用性を学ぶために「Java言語で学ぶデザインパターン入門」について学び、Javaとついでにkotlinで書いてみることにしました。
今回はFactoryについて書いてみます。

Factoryとは

Templateパターンだとスーパークラスで処理の枠組み(似たような共有の処理)を定め、サブクラスで具体的な内容を定めるようなデザインパターンだったのに対し、インスタンス生成のための枠組み(フレームワーク)を定めるデザインパターン。
下記はインスタンス生成の枠組みと、実際に「IDカード(インスタンス)を作る」サブクラスについて実装していきます。

Productクラス

このクラスでは「製品」を定義した抽象クラスで、製品は「使う」事を前提としたuseメソッドが定義されている。
また、抽象化することで製品としての役割として「使用」できることを強制することができる。(という意図を感じました。)

kotlinだとvoidとしてのUnitを指定せずとも、戻り値を指定していない関数の型はUnitになるとの事です。またIDEでも冗長だと指摘されました。

参考:Kotlinの型を知る ~前編~

Product.java
abstract class Product {
    public abstract void use();
}
Product.kt
abstract class Product {
    abstract fun use()
}

Factoryクラス

ここではインスタンス生成の枠組みを実装していきます。
ポイントは「製品を作る」事と「製品と登録する」事はサブクラスの実装に任せているが、createメソッドでの手順はFactoryクラスでfinalとして「定義が決まっている」意図を読み取ることができます。

Kotlinとjavaのprotectedの違いはJavaだとサブクラスor同じパッケージのクラスからアクセスできるのに対し、Kotlinだとサブクラスからのみアクセスとの事。
ここではサブクラスからのみアクセスできるkotlinのprotectedを使用する。

参考:kotlinとjavaのアクセス修飾子の関係性

また、Javaだとpublic finalなcreateメソッドだが、kotlinはdefaultでpublic finalなメソッドのため指定しない。

Factory.java
abstract class Factory {
    public final Product create(String owner) {
        Product p = createProduct(owner);
        registerProduct(p);
        return p;
    }
    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product product);
}
Factory.kt
abstract class Factory {
    fun create(owner: String): Product {
        val p = createProduct(owner)
        registerProduct(p)
        return  p
    }
    protected abstract fun createProduct(owner: String): Product
    protected abstract fun registerProduct(product: Product)
}

IDCardクラス

Productクラスを継承した実際の製品としてのサブクラスを定義していきます。

kotlinでコンストラクタの初期化処理をする場合はinitを定義する。

参考:【Kotlin】コンストラクタの書き方

IDCard.java
class IDCard extends Product {
    private String owner;
    public IDCard(String owner) {
        System.out.println(owner + "のカードを作ります。");
        this.owner = owner;
    }
    public void use() {
        System.out.println(owner + "のカードを使います。");
    }
    public String getOwner() {
        return owner;
    }
}
IDCard.kt
class IDCard (private val owner: String): Product() {
    init { println(owner + "のカードを作ります。") }
    override fun use() = println(owner + "のカードを使います。")
    fun getOwner() = owner
}

IDCardFactoryクラス

インスタンス生成の枠組みであるFactoryクラスを継承し具体的な処理を実装します。
インスタンスを生成し、製品を実際に作るcreateProductメソッドと、ownersフィールドで登録を実現しているregisterProductメソッドが実装されています。

KotlinだとListはread onlyらしく、追加可能なmutableListを使用します。

参考:KotlinとList

また、Kotlinはインスタンス生成時にnewを使用しない。

参考:Kotlin文法 - クラス、継承、プロパティ

また、キャストにおいてinstanceofの代わりにコンパイラがよしなに対応してくれるsmart castを実装しました。

参考:JavaプログラマがKotlinでつまづきがちなところ

IDCardFactory.java
class IDCardFactory extends Factory {
    private List<String> owners = new ArrayList<String>();

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void registerProduct(Product product) {
        owners.add(((IDCard)product).getOwner());
    }

    public List<String> getOwners() {
        return owners;
    }
}
IDCardFactory.kt
class IDCardFactory: Factory() {
    private var owners: MutableList<String> = mutableListOf()
    override fun createProduct(owner: String) = IDCard(owner)
    override fun registerProduct(product: Product) {
        if(product is IDCard) owners.add(product.getOwner()) //smart cast
    }
    fun getOwners() = owners
}

Mainクラス

IDカードを作成していきます。

FactorySample.java
public class FactorySample {
    public static void main(String[] args) {
        Factory factory = new IDCardFactory();
        Product card1 = factory.create("佐藤");
        Product card2 = factory.create("鈴木");
        Product card3 = factory.create("田中");
        card1.use();
        card2.use();
        card3.use();
        ((IDCardFactory)factory).getOwners().stream().forEach(System.out::println);
    }
}
FactorySample.kt
fun main(args: Array<String>){
    val factory = IDCardFactory()
    val card1 = factory.create("佐藤")
    val card2 = factory.create("鈴木")
    val card3 = factory.create("田中")
    card1.use()
    card2.use()
    card3.use()
    factory.getOwners().forEach(System.out::println)
}
実行結果
佐藤のカードを作ります。
鈴木のカードを作ります。
田中のカードを作ります。
佐藤のカードを使います。
鈴木のカードを使います。
田中のカードを使います。
佐藤
鈴木
田中

クラス図

image.png

所感

  • Factoryクラスは実際に生成するIDCardクラスについての記述はなく、あくまでProductとインスタンス生成のメソッドを呼び出せばProductが生成されるので、具体的なクラス名による束縛がない事がメリットである事を学んだ。
  • 上記であまり触れてなかったのですが、FactoryとProductを同じパッケージにし、IDCardとIDCardFactoryを別パッケージ(例えばframeworkパッケージとidcardパッケージ)で定義した場合、具体的なクラスやパッケージによる束縛がないことを「framworkパッケージはidcardパッケージに依存していない」と表現する事を学んだ。

  • Kotlinについては下記の点を学ぶことができた

  1. 戻り値を指定しない場合はvoidであるUnitがデフォルトで指定されているので定義する必要はない
  2. protectedの仕様がJavaとは異なる
  3. メソッドの定義はデフォルトでpublic final
  4. コンストラクタの初期化処理をする場合はinitを使用する
  5. Listはread only、mutableListは追加可能
  6. インスタンス生成時にnewを使用しない
  7. キャストはsmart castが便利

参考

下記を参考にさせて頂き、大変読みやすく、理解しやすかったです。

Kotlinの型を知る ~前編~
kotlinとjavaのアクセス修飾子の関係性
【Kotlin】コンストラクタの書き方
KotlinとList
Kotlin文法 - クラス、継承、プロパティ
JavaプログラマがKotlinでつまづきがちなところ

Takunawa
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away