设计模式入门学习之迭代器与组合模式(2)
关键字: 设计模式如果我们允许我们的聚合实现他们内部的集合,以及相关的操作和遍历的方法,又会如何?我们已经知道这会增加聚合中的方法个数,但又怎样呢?为什么这么做不好?
我们要清楚:当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(如遍历)时,我们就给了这个类2个变化的原因。没错,就是2个:如果这个集合改变的话,这个类也必须改变;如果我们遍历的方式改变的话,这个类也要跟着改变。所以我们要知道有这个设计原则:
单一责任设计原则:一个类应该只有一个引起变化的原因。
这个原则告诉我们,尽量让每个类保持单一责任,这听起来很容易,但其实做起来并不简单:区分设计中的责任,是最困难的事情。想要成功的唯一方法,就是努力不懈地检查你的设计,随着系统的成长,随时观察有没有迹象显示某个类改变的原因超出一个。
上一篇中讲到煎饼店和中餐厅的例子,这里在此基础上合并咖啡厅,供应晚餐,我们还是看代码方便:
//原有的咖啡厅菜单
public class CafeMenu{
//菜单项是用散列表存储的,不知道这是否支持迭代器,等下研究
Hashtable menuItems = new Hashtable();
public CafeMenu() {
addItem("Veggie Burger and Air Fries",
"Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
true, 3.99);
addItem("Soup of the day",
"A cup of the soup of the day, with a side salad",
false, 3.69);
}
public void addItem(String name, String description,
boolean vegetarian, double price)
{
//我们在这里创建新的菜单
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
//key就是项目名称,这个值就是菜单项对象
menuItems.put(menuItem.getName(), menuItem);
}
}
//重做咖啡厅菜单,整合进我们的框架是很容易的,因为hashtable本来就支持java内置的迭代器
public class CafeMenu implements Menu {
Hashtable menuItems = new Hashtable();
public CafeMenu() {
...
}
public void addItem(String name, String description,
boolean vegetarian, double price)
{
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
//我们这里实现了createIterator方法,
//注意,我们不是取得整个hashtable的迭代器,而是取得值的部分迭代器
public Iterator createIterator() {
return menuItems.values().iterator();
}
}
//修改java版的服务员,让她认识新的咖啡厅菜单
import java.util.Iterator;
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
Menu cafeMenu; //咖啡厅菜单和其他菜单一起被传入构造器中,然后记录在实例变量里
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinerIterator);
System.out.println("\nDINNER");
printMenu(cafeIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
//other
}
//测试
public class MenuTestDrive {
public static void main(String args[]) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
CafeMenu cafeMenu = new CafeMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);
//这里打印时应该可以看到所有的3个菜单了:)
waitress.printMenu();
}
}
我们花了很多时间来把服务员接耦出来,但还是得承认,程序中调用3次printMenu(),看起来实在有点丑。看清现实,每次我们一有新菜单加入,就必须打开服务员实现并加入更多的代码,这算不算是“违反开放-关闭原则”?我们还需要一种一起管理他们的方法,来试试看:
public class Waitress {
ArrayList menus; //现在我们只需要一个菜单项ArrayList
public Waitress(ArrayList menus) {
this.menus = memus;
}
//遍历菜单,把每个菜单的迭代器传给重载的printMenu()方法
public void printMenu() {
Iterator menuIterator = menus.iterator();
while(menuIterator.hasNext()){
Menu menu = (Menu)menuIterator.next();
printMenu(menu.createIterator());
}
}
private void printMenu(Iterator iterator) {//这里不需要改动
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
//other
}当我们认为这做的很好很安全的时候,新的需求来了,希望能够加上一份餐后甜点的“子菜单”,现在怎么办?我们不仅要支持多个菜单,还要支持菜单中的菜单。如果我们能让甜点菜单变成餐厅菜单集合中的一个元素,那该多好,但是根据现在的实现方式,根本做不到。该是做决策来改写厨师的实现以符合所有菜单的需求的时候了,重新实现他们的菜单已经是不可避免的了,如果现在不重新设计,就无法容纳未来增加的菜单或子菜单等需求。
补充:
现在java5中,所有的集合都已经新增了对遍历的支持,所以你甚至不再需要请求迭代器了。java5包含一种新形式的for语句,称为for/in。这可以让你在一个集合或者一个数组中遍历,而且不需要显示创建迭代器。
for/in语法是这样的:for(Object obj : collection){}
下面是利用for/in遍历ArrayList的例子:
ArrayList items = new ArrayList();
items.add(new MenuItem("Pancakes","delicious pancakes",true,1.58));
items.add(new MenuItem("Waffles","yummy waffles",true,5.2));
items.add(new MenuItem("Toast","perfect toast",true,2.36));
for(MenuItem item : items){
System.out.println("Breakfase item :" + item);
}
发表评论
- 浏览: 20859 次
- 性别:

- 来自: 北京

- 详细资料
搜索本博客
我的相册
共 30 张
最近加入圈子
最新评论
-
使用iBatis的自动化代码生 ...
有没有根据类生产数据库和xml的?
-- by aninfeel -
使用iBatis的自动化代码生 ...
IBatis去除注释版 http://hugh-lin.javaeye.com/ ...
-- by hugh-lin -
使用iBatis的自动化代码生 ...
我的不能用啊,郁闷掉了,真的是很黄很暴力。
-- by horror -
搭建Cloud Computing测试 ...
to fredzhang : 呵呵,我只是说用hadoop/hbase来搭 ...
-- by blank -
搭建Cloud Computing测试 ...
1. gfs的论文你仔细阅读过了吗?它的思想决定了基于它应当如何来实现,如何才能 ...
-- by fredzhang






评论排行榜