如果我们允许我们的聚合实现他们内部的集合,以及相关的操作和遍历的方法,又会如何?我们已经知道这会增加聚合中的方法个数,但又怎样呢?为什么这么做不好?
我们要清楚:当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(如遍历)时,我们就给了这个类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);
}

 

评论
发表评论

您还没有登录,请登录后发表评论

blank
搜索本博客
我的相册
5c2c51d9-1a15-3713-8084-e1742c808801-thumb
P1080256
共 30 张
存档
最新评论