-
Notifications
You must be signed in to change notification settings - Fork 36
29장 Modular Programming Using Objects
Changwoo Park edited this page Aug 2, 2014
·
1 revision
- 프로그램이 커지면 잘 나누자.
- 개발자 데스크톱, testing, staging, deployment 환경에서 각기 다른 설정을 적용할 수 있도록 잘 나누자.
이 앱의 모델은 아래와 같다:
abstract class Food(val name: String) {
override def toString = name
}
class Recipe(
val name: String,
val ingredients: List[Food],
val instructions: String) {
override def toString = name
}
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),
"Stir it all together.")
Database, Browser은 Mock 버전을 만들자:
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])
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)
}
}
이로써 Recipe 앱 완성!
추상화하면:
-
Browser
에Database
가 하드코딩되는 것도 분리하고 - Mock을 여러 개 만들 때 재사용 할 수 있다.
그래서 만든 코드는 아래와 같다:
abstract class Database {
def allFoods: List[Food]
def allRecipes: List[Recipe]
def foodNamed(name: String) = allFoods.find(f => f.name == name)
case class FoodCategory(name: String, foods: List[Food])
def allCategories: List[FoodCategory]
}
abstract class Browser {
// Database와 Browser를 분리시킴.
val database: Database
def recipesUsing(food: Food) =
database.allRecipes.filter(recipe => recipe.ingredients.contains(food))
def displayCategory(category: database.FoodCategory) = println(category)
}
object SimpleDatabase extends Database {
def allFoods = List(Apple, Orange, Cream, Sugar)
def allRecipes: List[Recipe] = List(FruitSalad)
private var categories = List(
FoodCategory("fruits", List(Apple, Orange)),
FoodCategory("misc", List(Cream, Sugar)))
def allCategories = categories
}
object SimpleBrowser extends Browser {
val database = SimpleDatabase
}
Scala다운 모듈화는 Trait과 self-type annotation으로:
trait SimpleFoods {
object Pear extends Food("Pear")
def allFoods = List(Apple, Pear)
def allCategories = Nil
}
trait SimpleRecipes {
// self-type
this: SimpleFoods =>
object FruitSalad extends Recipe(
"fruit salad",
ist(Apple, Pear), // Now Pear is in scope
"Mix it all together.")
def allRecipes = List(FruitSalad)
}
trait FoodCategories {
case class FoodCategory(name: String, foods: List[Food])
def allCategories: List[FoodCategory]
}
abstract class Database extends FoodCategories {
def allFoods: List[Food]
def allRecipes: List[Recipe]
def foodNamed(name: String) = allFoods.find(f => f.name == name)
}
object SimpleDatabase extends Database
with SimpleFoods with SimpleRecipes
SimpleDatabase
, StudentDatabase
을 Trait으로 다 만들었다고 치고 하나로 만들면 휘리릭:
object GotApples {
def main(args: Array[String]) {
val db: Database =
if (args(0) == "student")
StudentDatabase
else
SimpleDatabase
object browser extends Browser {
val database = db
}
val apple = SimpleDatabase.foodNamed("Apple").get
for (recipe <- browser.recipesUsing(apple))
println(recipe)
}
}
Browser
랑 Database
랑 분리할 수 있게 하였으니까 StudentDatabase의 FoodCategory를 SimpleBrowser에서 출력할 수 있어야 하는데 에러가 쫙~:
scala> val category = StudentDatabase.allCategories.head
category: StudentDatabase.FoodCategory =
FoodCategory(edible,List(FrozenFood))
scala> SimpleBrowser.displayCategory(category)
<console>:12: error: type mismatch;
found : StudentDatabase.FoodCategory
required: SimpleBrowser.database.FoodCategory
SimpleBrowser.displayCategory(category)
.type
을 사용해서 해결한다.:
object GotApples {
def main(args: Array[String]) {
...
object browser extends Browser {
val database: db.type = db
}
...
for (category <- db.allCategories) browser.displayCategory(category)
}
}
이 부분은 잘 이해가 안 되된다. .type
은 책에서 봤던 것도 같은데, 잘 기억이:)
FoodCategory
는 동일한 클래스에서 상속했지만, 써브클래스에서는 같은 타입이 아니다. 그래서 .type
을 넣어줘서 해결 할 수 있다는 건데, 이 메커니즘은 모르겠다.