Design Pattern #9: Iterator and Composite Patterns
These are my notes about the book Head First Design Patterns.
Starting example:
Merge two implementation with ArrayList
and Array
.
case class MenuItem ( name:String, description:String, vegetarian:Boolean, price:Double){ } class DinerMenu{ // dinermenu use array var menuItems:Array[MenuItem] = null var numberOfItems = 0 def addItem(item: MenuItem):Unit = { menuItems(numberOfItems) = item numberOfItems += 1 } def getMenu = menuItems } class PancakeMenu{ // pancakeMenu use arraylist var menuItems:util.ArrayList[MenuItem] = null def addItem(item: MenuItem):Unit = menuItems.add(item) def getMenu = menuItems }
We want to iterate two menus together, without specifying the data type.
Solution: use Iterator
interface, and define createIterator
method on both classes
abstract class Iterator[T]{ def hasNext:Boolean def next:T } class DinerMenuIterator(items:Array[MenuItem]) extends Iterator[MenuItem]{ var i:Int = 0 def hasNext = i <= items.length def next = { val menu:MenuItem = items(i) i += 1 menu } } class PancakeMenuIterator(items:util.ArrayList[MenuItem]) extends Iterator[MenuItem]{ var i:Int = 0 def hasNext = i <= items.size() def next = { val menu:MenuItem = items.get(i) i += 1 menu } } // add createIterator method: class DinerMenu{ // dinermenu use array var menuItems:Array[MenuItem] = null var numberOfItems = 0 def addItem(item: MenuItem):Unit = { menuItems(numberOfItems) = item numberOfItems += 1 } // createIterator replace getMenu def createIterator = new DinerMenuIterator(menuItems) } class PancakeMenu{ // pancakeMenu use arraylist var menuItems:util.ArrayList[MenuItem] = null def addItem(item: MenuItem):Unit = menuItems.add(item) def createIterator = new PancakeMenuIterator(menuItems) }
Now we can use the iterator
interface.
class Waitress(pancakeMenu: PancakeMenu, dinerMenu: DinerMenu){ def printMenu = { val pancakeIterator = pancakeMenu.createIterator val dinerIterator = dinerMenu.createIterator printMenu(pancakeIterator) printMenu(dinerIterator) } def printMenu(iter:Iterator) = { while(iter.hasNext){ println(iter.next) } } }
However, we're still bound to two concrete menu classes --> create a common interface for menu:
abstract class Menu{ def createIterator:Iterator } class PancakeMenu extends Menu{ ...} class DinerMenu extends Menu{ ...} class Waitress(menus:Seq[Menu]){ def printMenu = { menus.map(_.createIterator).foreach(printMenu(_)) } def printMenu(iter:Iterator) = { while(iter.hasNext){ println(iter.next) } } }
Composite pattern
Now what if we want to have a dessert "sub menu"? i.e menus within menus.
abstract class MenuComponent{ def isVegetarian:Boolean = false def print:Unit def getChild(i:Int):MenuComponent = throw new Exception("unsupported") } // "Leaf" class MenuItem(name:String, vegetarian:Boolean) extends MenuComponent{ override def isVegetarian = vegetarian def print = println(s"menu item $name") } // "Composite" class Menu(name:String, menus:Seq[MenuComponent]) extends MenuComponent{ // Menu holds MenuComponents --> recursive override def getChild(i:Int) = menus(i) def print = println(s"menu component $name ${menus.foreach(_.print)}") // print all children } class Waitress(menuComponent: MenuComponent){ def print = menuComponent.print }
Definition
Iterator:
-
Encapsulate iteration
-
Allow access elements of an aggregate (aka
Menu
) sequentially without exposing its underlying representation. -
Also place the task of traversal on the iterator, not on the aggregate
Composite
-
Compose objects into tree structures
-
Treat individual objects and compositions uniformly --> Transparency
-
Class diagram:
Component
->Leaf
orComposite
-
Composite
: holdsComponent
s.addComponent
,getChild
,operation
-
Leaf
:operation
-
Design Principle
- A class should be given single responsibility --> have only one reason to change. (eg. aggregate and iterate are two different responsibilities)
Others
- Use a null iterator can be helpful. (always return false for
hasNext
) - Can create an external iterator to iterate through Component, eg, find veggie menuItems.
- in Scala, can use pattern matching and flatMap
class Menu(menus:Seq[MenuComponent]) extends MenuComponent{ ... def getVeggie :Seq[MenuItem] = menus.flatMap { case m: MenuItem => if (m.isVegetarian) Seq(m) else Nil case m: Menu => m.getVeggie } }
Comments
Comments powered by Disqus