Kotlinでデザインパターン Factory Method編

# はじめに

本記事は[Java言語で学ぶデザインパターン入門](http://www.hyuki.com/dp/)を参考にしながら
JavaではなくKotlinで実装してみようというものです。

![Java言語で学ぶデザインパターン入門](http://www.hyuki.com/images/dp1.png)

# 調べてみる

**Factory Methodがやりたいことは、次に記載する内容です。**

* インスタンスとして生成されるクラスをProduct、インスタンスを生成するクラスをFactoryとして定義し、  
  Productのインスタンスを生成したい場合には、必ずFactoryでProductのインスタンスを生成する。
* インスタンスの生成をFactoryに任せるようにして、  
  利用者側がProductのインスタンス生成の具体的手順がわからなくても良いようにする。

# 実装してみる

次のコードのようにProduceを継承したiPhoneのインスタンスを生成するコードがあったとします
このコードはiPhoneクラスのコンストラクタを利用してインスタンスを生成しています。
またiPhoneのMACアドレスの重複確認もあわせて実施しているせいで見通しがとても悪くなっています。

```kotlin
    val products : MutableList<Product> = mutableListOf()
    
    val product1 : Product = iPhone("80-88-31-47-c9-0c")
    if (products.find { item -> item.mac == product1.mac } != null) {
        throw Exception("duplicated mac address")
    }
    products.add(product1)

    val product2 : Product = iPhone("13-54-01-93-16-bb")
    if (products.find { item -> item.mac == product2.mac } != null) {
        throw Exception("duplicated mac address")
    }
    products.add(product2)

    val product3 : Product = iPhone("b7-ea-3a-c3-b0-cb")
    if (products.find { item -> item.mac == product3.mac } != null) {
        throw Exception("duplicated mac address")
    }
    products.add(product3)
```

そこで次のコードのようにFactory Methodパターンを適応し、
factory.create()を呼び出すだけでインスタンスを生成できるようにしたいと思います。
このように実装できればインスタンス生成の具体的な処理は隠蔽化され見通しがとても良くなります。

```kotlin
    val factory : Factory = iPhoneFactory()
    val product1 : Product = factory.create("80-88-31-47-c9-0c")
    val product2 : Product = factory.create("13-54-01-93-16-bb")
    val product3 : Product = factory.create("b7-ea-3a-c3-b0-cb")
```

### Product

はじめにインスタンスを生成する対象のProduct(抽象クラス)を定義します。
今回はMACアドレスを所持し、起動、再起動、シャットダウンを制御できるProductクラスを定義しました。

```kotlin
interface Product {
    val mac: String
    fun boot()
    fun reboot()
    fun shutdown()
}
```

### iPhone

Product(抽象クラス)を継承したiPhone(具象クラス)を定義します。
iPhoneのメソッドにはあとで呼び出しが明確になるようにメッセージをPrintします。

```kotlin
class iPhone(mac : String) : Product {

    override val mac: String = mac

    init {
        println("MACアドレスが${mac}であるiPhoneを作成します。")
    }

    override fun boot() {
        println("MACアドレスが${mac}であるiPhoneのiOSを起動します。")
    }

    override fun reboot() {
        println("MACアドレスが${mac}であるiPhoneのiOSを再起動します。")
    }

    override fun shutdown() {
        println("MACアドレスが${mac}であるiPhoneのiOSをシャットダウンします。")
    }
}
```

### Factory 

Product(抽象クラス)を生成するFactory(抽象クラス)を定義します。
FactoryではTemplate Methodパターンを適応してインスタンス生成の手順のみ定義します。
手順のみ定義するので具体的な処理はサブクラスで定義します。

```kotlin
abstract class Factory {
    // Template Methodパターンを用いてインスタンス生成の手順を定義
    fun create(mac : String) : Product {
        val p : Product = createProduct(mac)
        registerProduct(p)
        return p
    }

    // 手順の具体的な処理はサブクラスで定義する。
    abstract protected fun createProduct(mac : String) : Product
    abstract protected fun registerProduct(product : Product)
}
```

### iPhoneFactory

Factory(抽象クラス)を継承したiPhoneFactory(具象クラス)を定義します。
Factoryにてインスタンス生成の手順は定義されているのでその手順にあわせて具体的な処理を実装します。

```kotlin
class iPhoneFactory : Factory() {
    // 作成したiPhoneの一覧
    val iphones = mutableListOf<Product>()

    override fun createProduct(mac: String): Product {
        // 作成するiPhoneのMACアドレスが重複していないか確認する。
        if (iphones.find { item -> item.mac == mac } != null) {
            throw Exception("duplicated mac address")
        }

        return iPhone(mac)
    }

    override fun registerProduct(product: Product) {
        // 登録するiPhoneのMACアドレスが重複していないか確認する。
        if (iphones.find { item -> item.mac == product.mac } != null) {
            throw Exception("duplicated mac address")
        }

        iphones.add(product)
    }
}
```

### Main
Factorクラスでインスタンスを生成し、Productのメソッドを呼び出してみます。

```kotlin
fun main(args: Array<String>) {
    val factory : Factory = iPhoneFactory()
    val product1 : Product = factory.create("80-88-31-47-c9-0c")
    val product2 : Product = factory.create("13-54-01-93-16-bb")
    val product3 : Product = factory.create("b7-ea-3a-c3-b0-cb")

    product1.apply {
        boot()
        reboot()
        shutdown()
    }

    product2.apply {
        boot()
        reboot()
        shutdown()
    }

    product3.apply {
        boot()
        reboot()
        shutdown()
    }
}

---------------------------------------------------------------
MACアドレスが80-88-31-47-c9-0cであるiPhoneを作成します。
MACアドレスが13-54-01-93-16-bbであるiPhoneを作成します。
MACアドレスがb7-ea-3a-c3-b0-cbであるiPhoneを作成します。
MACアドレスが80-88-31-47-c9-0cであるiPhoneのiOSを起動します。
MACアドレスが80-88-31-47-c9-0cであるiPhoneのiOSを再起動します。
MACアドレスが80-88-31-47-c9-0cであるiPhoneのiOSをシャットダウンします。
MACアドレスが13-54-01-93-16-bbであるiPhoneのiOSを起動します。
MACアドレスが13-54-01-93-16-bbであるiPhoneのiOSを再起動します。
MACアドレスが13-54-01-93-16-bbであるiPhoneのiOSをシャットダウンします。
MACアドレスがb7-ea-3a-c3-b0-cbであるiPhoneのiOSを起動します。
MACアドレスがb7-ea-3a-c3-b0-cbであるiPhoneのiOSを再起動します。
MACアドレスがb7-ea-3a-c3-b0-cbであるiPhoneのiOSをシャットダウンします。
```
# おわりに

**Facotry Methodパターンを適応してFactoryに、
インスタンス生成の処理を任せることで次のようなメリットが得られる。**

* 呼び出し側のインスタンス生成処理の見通しが良くなる。
* 呼び出し側がProductのサブクラスに依存しなくなるため、サブクラスの変更の影響を受けにくくなる