Scalaでデザインパターン ~Factory Methodパターン~


はじめに

こんにちは。
インターン生の鍋島です。

前回はBuilderパターンの記事を投稿しました。
今回もデザインパターンの解説をしていきたいと思います。

FactoryMethodパターン

今回は、FactoryMethodの解説をしていきます。
FactoryMethodとは、インスタンスを作成するための処理をFactoryクラスで行うようにする、デザインパターンです。

例えば、インスタンスを作成する際に関連する別のインスタンスが必要な場合や、インスタンス作成時に渡すデータを整形する必要があるなど処理が複雑になってしまう場合があると思います。
これらのようにインスタンス作成が複雑になると、このような問題が起こることがあります。

  • 別のところで使いまわす時に、その都度複雑な初期化処理を書かなくてはいけない
  • 初期化の方法が変わった時に、使用しているところすべてを書き換える必要がある

FactoryMethodを使うことでこれらの問題を解決することが出来ます。
インスタンス作成時の複雑な処理をFactoryクラスに集約することで複雑な初期処理を意識する必要がなくなり、使い回しやすくなります。

それではサンプルコードを見ていきましょう。

サンプルコード

今回のサンプルコードは、洋食レストランとイタリアンレストランのメニューを表示するものです。
レストランのインスタンスを、FactoryMethodパターンを活用して作ります。

import scala.math._

object Main extends App {
  execution(new ItalianRestaurantFactory)

  // レストランのインスタンスを作成し、メニューを表示する関数
  def execution(restaurantFactory: RestaurantFactory): Unit = {
    val restaurant = restaurantFactory.create(10)
    restaurant.printMenu()
  }
}

// レストランの抽象クラス。今回はメニューを表示するだけ
trait Restaurant {
  val menu: Seq[Menu]
  def printMenu(): Unit = println(menu)
}

// イタリアンレストラン
class ItalianRestaurant(val menu: Seq[Menu]) extends Restaurant

// 洋食レストラン
class WesternFoodRestaurant(val menu: Seq[Menu]) extends Restaurant

// レストランのメニュー
case class Menu(name: String, price: Int)

// レストランのFactoryクラス
trait RestaurantFactory {
  // この関数を呼ぶことで、レストランを作成することができる
  def create(menuNum: Int): Restaurant
}

// イタリアンレストランの抽象クラス
class ItalianRestaurantFactory() extends RestaurantFactory {
  // イタリアンレストランを作成するための初期処理を行っている
  override def create(menuNum: Int): ItalianRestaurant = {
    val menuSuffix = Seq("Pasta", "Pizza", "Soup")
    val menu = (0 to menuNum).map(i => {
      Menu(s"Italian${menuSuffix(i % 3)}$i", (random() * 2000).toInt)
    })
    new ItalianRestaurant(menu)
  }
}

// 洋食レストランの抽象クラス
class WesternFoodRestaurantFactory() extends RestaurantFactory {
  // 洋食レストランを作成するための初期処理を行っている
  override def create(menuNum: Int): WesternFoodRestaurant = {
    val menuSuffix = Seq("Steak", "Croquette", "Hamburger")
    val menu = (0 to menuNum).map(i => {
      Menu(s"WesternFood${menuSuffix(i % 3)}$i", (random() * 2000).toInt)
    })
    new WesternFoodRestaurant(menu)
  }
}



解説

それではサンプルコードの解説をしていきます。
まずは、クラスの定義からです。

  • Restaurant レストランの抽象クラスです
  • ItalianRestaurant 今回作成するイタリアンレストランです
  • WesternFoodRestaurant 今回制作する洋食レストランです
  • Menu レストランのメニューです
  • RestaurantFactory レストランを作成するためのFactoryクラスです
  • ItalianRestaurantFactory イタリアンレストランを作成するためのFactoryクラスです
  • WesternFoodRestaurantFactory イタリアンレストランを作成するためのFactoryクラスです

次に、FactoryMethodを適用している部分の解説をしていきます。
それぞれのレストランを作成するために必要な処理を、それぞれのFactoryクラスで行っています。
そして、Factoryクラスのcreate関数を呼ぶことでレストランのインスタンスを取得できます。

今回Restaurantインスタンスは、ItalianRestaurantとWesternFoodRestaurantの2種類ありますが、インスタンスを作成しているexecution関数では、Restaurantインスタンスの実体を意識する必要はありません。

レストランを作成するかは、ItalianRestaurantFactoryとWesternFoodRestaurantFactoryといったどのFactoryクラスを使用するかによって決められます。
そしてそのFactoryクラス自体、「Restaurantのインスタンスを作成する」という責務をもったRestaurantFactoryで定義されており、これがItalianRestaurantFactoryなのかWesternFoodRestaurantFactoryなのかは、execution関数は知りません。
これで、execution関数では完全に実体を隠蔽された状態で、インスタンスの作成及び処理をすることができます。

このように、適切に隠蔽し、責務を分割することで、コードの見通しがよくなります。

最後に

最後まで読んでくださりありがとうございます。
FactoryMethodパターンのメリットが伝わりましたでしょうか?

次回は、Abstract Factoryパターンの解説をしていきます。