LL脳がscalaの勉強を始めたよ その92


Scalaコップ本の27章をやっていきますよ。27章はモジュラープログラミングってことで大規模プログラミングに向けてのエトセトラをやっていきますよ(`・ω・´)

課題

まずはモジュール構成でプログラムを組むメリットを羅列してみますよ、まあいわゆるひとつのの写経です( ー`дー´)キリッ

  • プログラムを構成する個々のモジュール毎に別個にコンパイルできるようになる
    • 複数のチームが独立して仕事をすすめることができる
  • モジュールの実装を取り外して別の実装を組み込めるように出来る
    • 単体テスト、統合テスト等の各段階の状況に合わせてシステム構成を変えられる

上記メリットを実現するためには次のような要件が挙げられるみたいですね(´・ω・`)

  • インターフェースと実装を分離できるモジュールが必要
  • 同じインターフェースを持つモジュールを交換できる
    • 交換するモジュールに依存する別モジュールをコンパイルする必要がないこと
  • モジュールを配線できなければいけない
DI

上記のような要件を実現するためのアプローチの1つとしてDI(dependency injection)ってのがあるみたいです。JavaフレームワークであるSpringやGuiceなんかではJavaプラットフォーム上でDIをサポートしているとのことです(´・ω・`)例えばSpringではモジュールのインターフェースやJavaインターフェースとして、実装をJavaクラスとして表現出来るようにしているみたいで、モジュール依存関係やアプリケーションの配線はXMLの外部コンフィギュレーションファイルとして指定するみたいですね。

一応ScalaでもSpringを利用出来るみたいですが、Scalaフレームワークを使わずに言語自体でモジュール化を実現できるみたいなので、この章ではその方法を見ていきますよー

「レシピ」アプリケーション

とりあえずサンプルを使わないとイメージしづらいので、まずはレシピアプリケーションを例にしてモジュール実現を見ていきたいと思いますよ(`・ω・´)このソフトはWebアプリケーションとしてユーザのレシピを管理するもので、ドメイン層とアプリケーション層に分かれるみたいです。

ちなみにソレゾレの層の役割をザザッとまとめてやると

  • ドメイン
    • ビジネスコンセプト、ビジネスルールを実現
    • 外部RDBに永続化される状態情報をカプセル化するドメインオブジェクトを実現
  • アプリケーション層
    • アプリケーションがクライアントに提供するサービスに基づいて構成されたAPIを提供
    • ユーザインターフェース層を含む
    • ドメイン層のオブジェクトに仕事を移譲(デリゲート)しながら全体をコーディネートしてサービスを実現

MVCモデル的にModelがドメイン層、View・Controllerがアプリケーション層って解釈で良いかしら?(´・ω・`)

ちなみに各階層では特定のオブジェクトについては本物とモックの両方を接続出来るようにして、アプリケーションの単体テストがより簡単に書けるようにしますデス。そのためにはモックの接続を可能にするオブジェクトをモジュールとして利用できる必要があるみたいです(´・ω・`)ちなみにScalaではスケーラブルなのでオブジェクトは小規模・大規模関係なく構築できるので、モジュールのために別の構文を使うことなくオブジェクトをモジュールとして利用出来るとのことです。

例えばドメイン層でモックの利用を可能にするオブジェクトの1つとしてはRDBを表現するオブジェクトがあげられるのでこいつをモジュールとして扱ってやります。このモジュールを利用するレシピアプリケーションではDBにユーザが集めたすべてのレシピを格納して、DBブラウザがDBの検索と閲覧を支援しますデス。

とりあえず食品やレシピをモデリングしてやります(`・ω・´)まずは食品ですが、今回は簡易化のため食品は名前だけをもつものとして定義します

// アプリケーション向けパッケージ宣言です
package org.test.recipe
// 食品を表現する抽象クラスです
abstract class Food(val name:String){
  override def toString = name
}

次にレシピを名前、材料リスト、作り方で構成してやります

package org.test.recipe
// レシピを表現するクラスですネ(´・ω・`)
class Recipe(
  // 名前の定義です 
  val name:String,
  // 材料はFoodのリストで保持します
  val ingredients:List[Food],
  // 作り方は文字列で表現デス
  val instructions:String
){
  override def toString = name
}

とりあえず上記を使ってテスト用のサンプルをつくってみますかね

package org.test.recipe

// テスト用の食品を定義
object Apple extends Food("Apple")
object Orange extends Food("Orange")
object Cream extends Food("Cream")
object Sugar extends Food("Sugar")

// テスト用のレシピを定義しますね
object FruitSalad extends Recipe(
  "fruit salad",
  List(Apple, Orange, Cream, Sugar),
  "全てをまぜます"
)

これらは先ほど定義したFoodクラスとRecipeクラスのシングルトンインスタンスを示しているみたいです(´・ω・`)Scalaはオブジェクトをモジュールとして使用するので、テスト中のデータモジュールのモック実装としてシングルトンオブジェクトを使うようにすればプログラムのモジュール化を開始できるみたいYO!とのことなので、とりあえずモック実装のデータベースモジュールとブラウザモジュールを書いてみますよ(`・ω・´)

package org.test.recipe

// モックベースのデータベースを定義しますね
object SimpleDatabase{
  // 食品一覧を格納します 
  def allFoods = List(Apple, Orange, Cream, Sugar)
  // 食品がDB内にあるか検索して、一致すれば食品オブジェクトを返します
  def foodNamed(name:String):Option[Food] = 
    allFoods.find(_.name == name)
  // レシピ一覧を格納します
  def allRecipes:List[Recipe] = List(FruitSalad)
}

// データベースブラウザを定義します
object SimpleBrowser {
  // 指定した食品を利用するレシピを検索します
  def recipesUsing(food:Food) =
    // containsを利用して食材一覧に含まれるかを判定しますネ(´・ω・`)
    SimpleDatabase.allRecipes.filter(recipe => 
      recipe.ingredients.contains(food))
}

とりあえず実行してみますよ

// まずは食材を名前検索します
// 戻り値がOptionなのでgetメソッドで中身だけ取り出しますよ  
scala> val apple = SimpleDatabase.foodNamed("Apple").get
apple: Food = Apple
// 取得した食品オブジェクトを利用してレシピを検索しますね
scala> SimpleBrowser.recipesUsing(apple)                
res0: List[Recipe] = List(fruit salad)
データベースでの食品の分類

上記サンプルデータベースを改良して食品の分類を行えるようにします。食品分類を実装するには次のようにFoodCategoryクラスをカテゴリーリストをデータベースモジュールに追加してやります。

package org.test.recipe

object SimpleDatabase{
  def allFoods = List(Apple, Orange, Cream, Sugar)
  def foodNamed(name:String):Option[Food] = 
    allFoods.find(_.name == name)
  def allRecipes:List[Recipe] = List(FruitSalad)
  // 食品分類を表現するケースクラスを定義しますよ
  case class FoodCategory(name:String, foods:List[Food])
  // privete宣言によって他のプログラムに影響を出さずに
  // 変更できるように食品分類一覧を定義します
  private var categories = List(
    FoodCategory("fruits", List(Apple, Orange)),
    FoodCategory("misc", List(Cream, Sugar)))
  // 全ての食品分類を返しますね
  def allCategories = categories
}

object SimpleBrowser {
  def recipesUsing(food:Food) =
    SimpleDatabase.allRecipes.filter(recipe => 
      recipe.ingredients.contains(food))
  // 食品分類を返します 
  def displayCategory(category:SimpleDatabase.FoodCategory){
    println(category)
  }
}

とりあえず試し実行してみますかね

scala> SimpleDatabase.allCategories                                        
res7: List[SimpleDatabase.FoodCategory] = List(FoodCategory(fruits,List(Apple, Orange)), FoodCategory(misc,List(Cream, Sugar)))

scala> for(category <- SimpleDatabase.allCategories)                            
|   SimpleBrowser.displayCategory(category)                           
FoodCategory(fruits,List(Apple, Orange))
FoodCategory(misc,List(Cream, Sugar))


おお、とりあえず分類項目まではできました(`・ω・´)また、コップ本が主張することとしてはプログラムはシングルトンオブジェクトに分割することができて、ソレゾレがモジュールと考えられるそうです。(´ε`;)ウーン…イマイチイメージがつかないからとりあえず先に進みますかね。

いじょうですよー

とりあえず時間切れのため今回は以上です。今回は写経メインだったので、まだモジュール化のイメージが固まらないので次回に進んでからですかね(´・ω・`)とりあえず次回は抽象化からやっていきますよー