本文的概念来自深入浅出设计模式一书
有两个饭店合并了,它们各自有自己的菜单.饭店合并之后要保留这两份菜单.
这两个菜单是这样的:
菜单项MenuItem的代码是这样的:
最初我们是这样设计的,这是第一份菜单:
这是第2份菜单:
问题就是多个菜单把事情变复杂了.例如:如果一个服务员需要使用两份菜单的话,那么她就无法很快的告诉客户有哪些菜是适合素食主义者的了.
服务员还有可能有这些需求:
打印菜单,打印早餐菜单,打印午餐菜单,打印素食菜单,判断某个菜是否是素食的.
首先我们尝试一下如何实现打印菜单:
1.调用两个菜单上面的getMenuItem()方法来获取各自的菜单项,由于它们的菜单不同,所以需要写两段代码:
2.打印两个菜单的菜单项,同样也是两套代码:
3.如果还有一份菜单,那么就需要写三套代码....
现在就很麻烦了.
如果能找到一种方式让这两个菜单同时实现一个接口就好了.我们已经知道,要把变化的部分封装起来.
什么是变化的部分由于不同对象集合引起的遍历操作.
那我们试试;
1.想要遍历早餐项,我们使用ArrayList的size()和get()方法:
2.想要遍历午餐项,我们需要使用Array的length成员变量以及通过索引访问数组:
3.如果我们创建一个对象,把它叫做迭代器,让它来封装我们遍历集合的方式怎么样
这里,我们需要早餐菜单创建一个迭代器,如果还有剩余的菜单项没有遍历完,就获取下一个菜单项.
4.让我们在Array上试试:
首先你需要知道这种模式依赖于一个迭代器接口.例如这个:
hasNext()方法告诉我们集合中是否还有剩余的条目没有遍历到.
next()方法返回下一个条目.
有了这个接口,我们可以在任何一种集合上实现该接口.:
定义迭代器接口:
然后再DinerMenu上实现迭代器接口:
然后使用迭代器来修改DinerMenu菜单:
注意:不要直接返回集合,因为这样会暴露内部实现.
createIterator()方法返回的是迭代器的接口,客户并不需要知道DinerMenu是如何维护菜单项的,也不需要DinerMenu的迭代器是如何实现的.它只是用迭代器来遍历菜单里面的条目.
最后服务员的代码如下:
测试代码:
我们只是为菜单添加了createIterator()方法.
而现在,菜单的实现被封装了,服务员不知道菜单是如何保存菜单项的.
我们所需要的只是一个循环,它可以多态的处理实现了迭代器接口的集合.
而服务员使用的是迭代器接口.
现在呢,菜单还没有共同的接口,这意味着服务员仍然被绑定在两个具体的菜单类上,一会我们再说这个.
目前就是两个菜单实现了同一套方法,但是还没有实现同一个接口.
菜单项MenuItem:
namespaceIteratorPattern.Menus{publicclassMenuItem{publicstringName{get;}publicstringDescription{get;}publicboolVegetarian{get;}publicdoublePrice{get;}publicMenuItem(stringname,stringdescription,boolvegetarian,doubleprice){Name=name;Description=description;Vegetarian=vegetarian;Price=price;}}}迭代器接口IMyIterator:
namespaceIteratorPattern.Abstractions{publicinterfaceIMyIterator{boolHasNext();objectNext();}}两个菜单迭代器:
usingIteratorPattern.Abstractions;usingIteratorPattern.Menus;namespaceIteratorPattern.MenuIterators{publicclassMyDinerMenuIterator:IMyIterator{privatereadonlyMenuItem[]_menuItems;privateint_position;publicMyDinerMenuIterator(MenuItem[]menuItems){_menuItems=menuItems;}publicboolHasNext(){if(_position>=_menuItems.Length||_menuItems[_position]==null){returnfalse;}returntrue;}publicobjectNext(){varmenuItem=_menuItems[_position];_position++;returnmenuItem;}}}usingSystem.Collections;usingIteratorPattern.Abstractions;namespaceIteratorPattern.MenuIterators{publicclassMyPancakeHouseMenuIterator:IMyIterator{privatereadonlyArrayList_menuItems;privateint_position;publicMyPancakeHouseMenuIterator(ArrayListmenuItems){_menuItems=menuItems;}publicboolHasNext(){if(_position>=_menuItems.Count||_menuItems[_position]==null){returnfalse;}_position++;returntrue;}publicobjectNext(){varmenuItem=_menuItems[_position];_position++;returnmenuItem;}}}两个菜单:
usingSystem;usingIteratorPattern.Abstractions;usingIteratorPattern.Menus;namespaceIteratorPattern.Waitresses{publicclassMyWaitress{privatereadonlyMyPancakeHouseMenu_pancakeHouseMenu;privatereadonlyMyDinerMenu_dinerMenu;publicMyWaitress(MyPancakeHouseMenupancakeHouseMenu,MyDinerMenudinerMenu){_pancakeHouseMenu=pancakeHouseMenu;_dinerMenu=dinerMenu;}publicvoidPrintMenu(){varpancakeIterator=_pancakeHouseMenu.CreateIterator();vardinerIterator=_dinerMenu.CreateIterator();Console.WriteLine("MENU\n--------------\nBREAKFIRST");PrintMenu(pancakeIterator);Console.WriteLine("\nLUNCH");PrintMenu(dinerIterator);}privatevoidPrintMenu(IMyIteratoriterator){while(iterator.HasNext()){varmenuItem=iterator.Next()asMenuItem;Console.Write($"{menuItem.Name},");Console.Write($"{menuItem.Price}--");Console.WriteLine($"{menuItem.Description}");}}}}测试:
Java里面内置了Iterator接口,我们刚才是手写了一个Iterator迭代器接口.Java内置的定义如下:
注意里面这个remove()方法,我们可能不需要它.
remove()方法是可选实现的,如果你不想让集合有此功能的话,就应该抛出NotSupportedException(C#的).
由于PancakeHouseMenu使用的是ArrayList,而ArrayList已经实现了该接口,那么:这样简单改一下就可以:
针对DinerMe菜单,还是需要手动实现的:
最后别忘了给菜单规定一个统一的接口:
服务员Waitress类里面也使用Menu来代替具体的菜单,这样也减少了服务员对具体类的依赖(针对接口编程,而不是具体的实现):
最后看下改进后的设计类图:
迭代器模式提供了一种访问聚合对象(例如集合)元素的方式,而且又不暴露该对象的内部表示.
迭代器模式负责遍历该对象的元素,该项工作由迭代器负责而不是由聚合对象(集合)负责.
类图:
一个类应该只有一个变化发生的原因.
写代码的时候这个原则很容易被忽略掉,只能通过多检查设计来避免违反原则.
所谓的高内聚,就是只这个类是围绕一套关连的函数而设计的.
遵循该原则的类通常是高内聚的,并且可维护性要比那些多重职责或低内聚的类好.
还需要添加另一份菜单:
这个菜单使用的是HashTable.
首先修改该菜单,让它实现Menu接口:
注意看HashTable的不同之处:
首先通过values()方法获取HashTable的集合对象,这个对象正好实现了Iterator接口,直接调用iterator()方法即可.
最后修改服务员类:
测试:
我们给了服务员一种简单的方式来遍历菜单项,不同的菜单实现了同一个迭代器接口,服务员不需要知道菜单项的实现方法.
我们把服务员和菜单的实现解耦了
而且使服务员可以扩展:
现在有三个菜单,每次再添加一个菜单的时候,你都得相应的添加一套代码,这违反了"对修改关闭,对扩展开放原则".
那我们把这些菜单放到可迭代的集合即可:
菜单接口:
usingSystem.Collections;namespaceIteratorPattern.Abstractions{publicinterfaceIMenu{IEnumeratorCreateIEnumerator();}}
三个菜单:
usingSystem;usingSystem.Collections;usingIteratorPattern.Menus;namespaceIteratorPattern.MenuIterators{publicclassDinerMenuIterator:IEnumerator{privatereadonlyMenuItem[]_menuItems;privateint_position=-1;publicDinerMenuIterator(MenuItem[]menuItems){_menuItems=menuItems;}publicboolMoveNext(){_position++;if(_position>=_menuItems.Length||_menuItems[_position]==null){returnfalse;}returntrue;}publicvoidReset(){_position=-1;}publicobjectCurrent=>_menuItems[_position];}}usingSystem.Collections;usingSystem.Collections.Generic;namespaceIteratorPattern.MenuIterators{publicclassPancakeHouseMenuIterator:IEnumerator{privatereadonlyArrayList_menuItems;privateint_position=-1;publicPancakeHouseMenuIterator(ArrayListmenuItems){_menuItems=menuItems;}publicboolMoveNext(){_position++;if(_position>=_menuItems.Count||_menuItems[_position]==null){returnfalse;}returntrue;}publicvoidReset(){_position=-1;}publicobjectCurrent=>_menuItems[_position];}}服务员:
usingSystem;usingSystem.Collections;usingIteratorPattern.Abstractions;usingIteratorPattern.Menus;namespaceIteratorPattern.Waitresses{publicclassWaitress{privatereadonlyArrayList_menus;publicWaitress(ArrayListmenus){_menus=menus;}publicvoidPrintMenu(){varmenuIterator=_menus.GetEnumerator();while(menuIterator.MoveNext()){varmenu=menuIterator.CurrentasIMenu;PrintMenu(menu.CreateIEnumerator());}}privatevoidPrintMenu(IEnumeratoriterator){while(iterator.MoveNext()){if(iterator.Current!=null){MenuItemmenuItem;if(iterator.CurrentisMenuItemitem){menuItem=item;}else{menuItem=((DictionaryEntry)iterator.Current).ValueasMenuItem;}Console.Write($"{menuItem.Name},");Console.Write($"{menuItem.Price}--");Console.WriteLine($"{menuItem.Description}");}}Console.WriteLine();}}}测试:
staticvoidMenuTestDriveUsingIEnumerator(){varpancakeHouseMenu=newPancakeHouseMenu();vardinerMenu=newDinerMenu();varcafeMenu=newCafeMenu();varwaitress=newWaitress(newArrayList(3){pancakeHouseMenu,dinerMenu,cafeMenu});waitress.PrintMenu();}