好了,这期开始介绍迭代器模式,组合模式和剩下的结构类模式,
首先介绍迭代器+组合模式,虽然前者属于行为类模式,后者属于结构模式,但Head First将这两种模式放在一起进行介绍,足见二者关系紧密。没有结构,何来迭代?这里正好一并介绍,正好为结构类设计模式起头。
一、迭代器模式Iterator:
迭代器模式Iterator:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
在Head First中,迭代器模式用于统一不同参观菜单数据结构的输出方式,只要我们提供统一的迭代器接口,那么菜单的遍历对服务员来说就是透明的:
我们先一步到位实现统一menu接口的迭代器模式,假设menu1为数组形式,menu2为ArrayList形式,那么前者需要自己定义迭代器的方法,而后者可以直接返回java.util.Iterator格式的迭代器,我们首先定义数组迭代器:
迭代器模式Iterator:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
在Head First中,迭代器模式用于统一不同参观菜单数据结构的输出方式,只要我们提供统一的迭代器接口,那么菜单的遍历对服务员来说就是透明的:
我们先一步到位实现统一menu接口的迭代器模式,假设menu1为数组形式,menu2为ArrayList形式,那么前者需要自己定义迭代器的方法,而后者可以直接返回java.util.Iterator格式的迭代器,我们首先定义数组迭代器:
定义一个统一的menu接口,可以调用createIterator返回统一接口迭代器
public interface Menu { //具备返回迭代器的菜单接口
public abstract Iterator<String> createIterator();
}
因为数组没有迭代器,所以我们要创建一个:
public class Menu1Iterator implements Iterator<String>{
Menu1 menu1;
int i;
public Menu1Iterator(Menu1 menu1){
this.menu1 = menu1;
i=0;
}
public boolean hasNext() {
if(i<=menu1.itemnum)
return true;
return false;
}
public String next() {
i++;
return menu1.menu[i-1];
}
public void remove() {
for(int j = i;j<menu1.itemnum;j++){
menu1.menu[j-1]=menu1.menu[j];
}
menu1.itemnum--;
}
}
继承接口的菜单menu1
public class Menu1 implements Menu {
static final int MAX_ITEM = 2;
int itemnum = 0;
String[] menu = new String[MAX_ITEM];
public Menu1(){
menu[0]="menu1item1";
itemnum++;
menu[1]="menu1item2";
itemnum++;
}
public Iterator<String> createIterator() {
return new Menu1Iterator(this);
}
}
菜单menu2
public class Menu2 implements Menu {
private ArrayList<String> menu = new ArrayList<String>();
public Menu2(){
menu.add("menu2item1");
menu.add("menu2item2");
}
public Iterator<String> createIterator() {
return menu.iterator();
}
}
我们可以看到,通过继承统一接口menu,我们能够使用统一的迭代器方法对数据进行遍历和其它操作了,但是这个数据结构的可扩展性并不尽如人意,如果要添加菜单,就需要打开遍历代码,重新加入,如果要增加子菜单就更是不可能的,因此,就引出了下面的组合模式
二、组合模式Composite:
组合模式Composite:允许你将对象组合成树形结构来表现“整体/部分”层次结构,组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式Composite:允许你将对象组合成树形结构来表现“整体/部分”层次结构,组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式有一个很有用的特性,通过封装,组合模式能够使叶子节点和树节点具备相同的接口,使得遍历变成了相对简单的树遍历问题,可扩展性大大增强,这里我们仍然采用简化的方法,先定义好基本类型MenuComp:
public interface MenuComp {
public abstract void add(MenuComp mc); //添加节点,只对菜单类有用
public abstract void remove(MenuComp mc); //删除节点,只对菜单类有用
public abstract Iterator<MenuComp> createIterator(); //创建迭代器,只对菜单类有用
}
定义NullIterator,其作用是用于返回统一的Iterator接口,这样就不需要针对Null指针进行大量的校验了(因为我们要对菜单项类能够调用相同的接口,方式程序崩溃):
public class NullIterator implements Iterator<MenuComp> {
public boolean hasNext() {return false;} //没有下一个
public MenuComp next() {return null;} //下一个是空的
public void remove() {} //什么也不做
}
定义Menu类,即菜单类,需要实现所有功能:
public class Menu implements MenuComp {
private String menu;
private ArrayList<MenuComp> menulist = new ArrayList<MenuComp>(); //ArrayList列表,其实任何格式都可以
public Menu(String s){ menu = s; } //弄个列表名,以后要打印可以调用
public void print(){ System.out.println(menu); } //列表名打印
public void add(MenuComp mc) { menulist.add(mc); } //往列表里直接加元素
public void remove(MenuComp mc) { menulist.remove(mc); } //往列表里删除元素
public Iterator<MenuComp> createIterator() { return new CompositeIterator(menulist.iterator()); } //组合迭代器生成,后面介绍
}
MenuItem类:
public class MenuItem implements MenuComp {
private String menuitem;
public MenuItem(String s){ menuitem = s; }
public void print(){ System.out.println(menuitem); }
public void add(MenuComp mc) { } //什么都不做
public void remove(MenuComp mc) { } //什么都不做
public Iterator<MenuComp> createIterator(){ return new NullIterator(); } //返回之前定义的空指针
}
定义CompositeIterator类,这个类通过树形结构,实现Menu的前序遍历,这段代码逻辑可说是Head First书本中最为抽象的代码之一(要是有指针那真是方便太多啦。。。)
public class CompositeIterator implements Iterator<MenuComp> {
Stack<Iterator<MenuComp>> s = new Stack<Iterator<MenuComp>>(); //记录遍历深度及每一个层次的遍历位置
public CompositeIterator(Iterator<MenuComp> iterator){ s.push(iterator); } //创建迭代器,存储此menu的迭代器
public boolean hasNext() {
Iterator<MenuComp> iterator = s.peek(); //取出此层迭代器
if(iterator == null) return false; //没有迭代器了,遍历结束
else{
if(!iterator.hasNext()){ //此层没有记录了
s.pop(); //此层迭代器出栈,准备迭代
return hasNext(); //迭代取上一层迭代器,确认上层是否有记录
}
else return true; //此层找到记录,直接返回成功
}}
public MenuComp next() {
if(hasNext()){ //有下条记录
Iterator<MenuComp> iterator = s.peek(); //取用此层迭代器
MenuComp mc = iterator.next(); //此层迭代器取下一个记录
if(mc instanceof Menu) s.push(mc.createIterator()); //下个记录是一个menu,把这个menu的迭代器放入栈,以进一步深层遍历
}
return null; //没元素了,直接退出
}
public void remove() {} //什么也不做
}
CompositeIterator的定义和书中有些差别,除了适度的简化之外,还将Iterator进行了显化,这是因为,根据Effective Java23条,应尽量避免使用原生态模型,这样可以有效避免数据格式问题导致的各种运行时问题,在编译时就能够发现数据格式的错误。当然,我们也可以用Iterator<?>来代替MenuComp,这里只是为了显示方便,代码还是有点抽象的,需要认真理解,不知道递归有没有成为你的朋友^_^
下面几个设计模式本来想单独开贴进行介绍,但是写出来发现实在比较简单,所以就直接写在后面了:
三、装饰者模式Decorator:
装饰者模式Decorator:动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案
装饰者模式Decorator:动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案
假设我们开了一个饮料店,为了能够有效扩展描述和费用,我们需要这么一个基本类,作为作料和主体饮料的基类:
public abstract class Item {
public abstract double cost(); //消费金额
public abstract String description(); //产品描述
}
好了,我们来定义一个5块钱的饮料:
public class Beverage extends Item {
public double cost(){ return 5; } //五块钱
public String description() { return "Beverage"; } //一个饮料
}
下面我们需要定义一个作料,为了能保有原功能,我们将通过装饰着模式对饮料进行扩展:
public class Cordiment extends Item {
Item it; //在作料中封装饮料对象
public Cordiment(Item it){ this.it = it; }
public double cost() { return it.cost()+1; } //原饮料费用+1
public String description() { return it.description()+"+Cordiment"; } //原描述补上作料
}
直接执行以下代码,我们就实现了将作料加入饮料的过程:
public static void main(String[] args) {
Beverage b = new Beverage();
Item it = new Cordiment(b); //此步最为关键,通过传入原饮料对象,实现了cost和description的装饰功能
System.out.println(it.cost());
System.out.println(it.description());
}
可以看到,通过装饰者模式,可以在不修改原基础类的情况下,快速的实现功能的叠加,这种设计模式在JAVA的IO类库种应用十分广泛
四、适配器模式Adapter:
适配器模式Adapter:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
适配器模式Adapter:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
适配器用于解决接口格式不一致的问题,考虑Head First中的例子,我们想要将土鸡放到鸭子类中,该怎么做呢,只需要给土鸡包一个鸭子皮儿~~~
先看看鸭子接口(鸭子皮):
public interface Duck {
public abstract void quack();
public abstract void fly();
}
再来看看土鸡接口:
public interface Turkey {
public abstract void gobble(); //土鸡叫换了个名字
public abstract void fly();
}
一个土鸡的实现:
public class WildTurkey implements Turkey {
public void gobble() { System.out.println("gogogo!"); }
public void fly() {System.out.println("weak fly!");}
}
让我们把土鸡塞到鸭子接口中~~~
public class TurkeyAdapter implements Duck {
Turkey tk; //里面有一只土鸡!!
public TurkeyAdapter(Turkey tk) { this.tk = tk;} //土鸡传入鸭子接口
public void quack() { tk.gobble(); } //实际上执行土鸡的叫法
public void fly() { tk.fly(); } //执行土鸡的飞翔
}
适配器模式非常直观,当我们要转换接口的时候,只需要做两步,包皮儿,新接口调用旧接口~~~
五、外观模式Facade:
外观模式Facade:提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。这个模式结构十分简单,我们可以理解成通过一个函数接口,我们能够调用多个功能,在Head First中甚至只是放在适配器模式章节的内部,我甚至认为这根本就不能称为一个模式。。。下面看个例子
外观模式Facade:提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。这个模式结构十分简单,我们可以理解成通过一个函数接口,我们能够调用多个功能,在Head First中甚至只是放在适配器模式章节的内部,我甚至认为这根本就不能称为一个模式。。。下面看个例子
public class WildTurkey implements Turkey {
public void gobble() { System.out.println("gogogo!"); }
public void fly() {System.out.println("weak fly!");}
public void turkeymethod(){ //好了,外观模式实现了╮(╯▽╰)╭
gobble();
fly();
}
}
好吧,没什么好说得,这个模式真是水出了新境界。。。
设计模式是程序设计的基础,也是编写高质量代码的前提,由于篇幅及能力限制,目前只能介绍几种基本的设计类型,在以后的学习中,有机会会继续总结更高级的复合设计模式,现阶段就先到此为止了~~~
完结,撒花╮(╯▽╰)╭