评论区
雨帆 2017-01-11 16:14:04
对于设计模式,我个人认为,其实代码和设计原则才是最好的老师。理解了 SOLID,如何 SOLID,自然而然地就用起来设计模式了。Github 上有一个 tdd-training,里面就是教你如何设计重构的。换句话说,此书可能不是很推荐。
设计模式的学习还是要靠 kata 练习
一、Iterator模式:一个一个遍历
示例程序类图
java
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println(book.getName());
}
}
角色
-
Iterator(迭代器)
定义按顺序逐个遍历元素的接口(APl)。
在示例程序中,Iterator接口扮演这个角色,它定义了两个方法:hasNext(判断是否存在下一个元素)和next(获取该元素)。
-
Concretelterator(具体的迭代器)
负责实现Iterator角色所定义的接口(API)。
在示例程序中,BookShelfIterator类扮演这个角色。该角色中包含了遍历集合所必需的信息。
在示例程序中,BookShelf类的实例保存在bookShelf字段中,被指向的书的下标保存在index字段中。
-
Aggregate(集合)
负责定义创建Iterator角色的接口(API)。这个接口(API)是一个方法,会创建出"按顺序访问保存在我内部元素的人"。
在示例程序中,Aggregate接口扮演这个角色,它里面定义了iterator方法。
-
ConcreteAggregate(具体的集合)
负责实现Aggregate角色所定义的接口(API)。它会创建出具体的Iterator角色,即Concretelterator角色。
在示例程序中,由BookShelf类扮演这个角色,它实现了iterator方法
扩展思路的要点
不管实现如何变化,都可以使用lterator
不用for循环,而使用Iterator模式的一个重要的理由:引入Iterator后可以将遍历与实现分离开来。
请看下面的代码。
java
while (it.hasNext()) {
Book book =(Book)it.next();
System.out.println(book.getName());
}
这里只使用了Iterator
的hasNext()
和next()
,并没有调用BookShelf
的方法。
即:这里的while循环并不依赖于BookShelf的实现。
那么管理书本就可以不用数组,可以换成java.util.Vector
和ArrayList
等别的形式。
不管BookShelf如何变化,只要BookShelf的iterator方法能正确地返回Iterator的实例就行,对于BookShelf的调用者很友好。
设计模式的作用就是帮助我们编写可复用的类。
所谓"可复用",就是指将类实现为"组件",当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。
难以理解抽象类和接口
不要只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。
为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。
Aggregate 和 Iterator 的对应
如何把BookShelfIterator类定义为BookShelf类的Concretelterator角色的:BookShelfIterator类知道BookShelf是如何实现的。因此,我们才能调用用来获取下一本书的getBookAt方法。
也就是说,如果BookShelf的实现发生了改变,即getBookAt方法这个接口(API)发生变化时,必须修改BookShelfIterator类。
正如Aggregate和Iterator这两个接口对应的一样,concreteAggregate和ConcreteIterator这两个类也是对应的。
多个 Iterator
"将遍历功能置于Aggregate角色之外"是Iterator模式的一个特征。根据这个特征,可以针对一个ConcreteAggregate角色编写多个Concretelterator角色。
迭代器的种类多种多样
在示例程序中展示的Iterator类只是很简单地从前向后遍历集合。可以改成从后向前、双向遍历、根据条件跳跃式遍历等。
不需要 deletelterator
在Java中,没有被使用的对象实例将会自动被删除(垃圾回收,GC)。因此,在iterator中不需要与其对应的deleteIterator方法。
相关的设计模式
Visitor模式(第13章)
Iterator模式是从集合中逐个取出元素进行遍历,但并没有在Iterator接口中声明对取出的元素进行何种处理。
Visitor模式则是在遍历元素集合的过程中,对元素进行相同的处理。
Composite模式(第11章)
Composite模式是具有递归结构的模式,在其中使用Iterator模式比较困难。
Factory Method模式(第4章)
在iterator方法中生成Iterator的实例时可能会使用Factory Method模式。
二、Adapter模式:加个"适配器"以便于复用
示例程序类图
继承的方式
委托的方式
java
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
角色
-
Target(对象)
负责定义所需的方法。
类比让笔记本电脑正常工作所需的直流12伏特电源。
在示例程序中,由Print接口(使用继承时)和Print类(使用委托时)扮演此角色。
-
Client(请求者)
负责使用Target 角色所定义的方法进行具体处理。
类比直流12伏特电源所驱动的笔记本电脑。
在示例程序中,由Main类扮演此角色。
-
Adaptee(被适配)
注意不是Adapt-er(适配)角色,而是Adapt-ee(被适配)角色。
Adaptee是一个持有既定方法的角色。
类比交流220伏特电源。
在示例程序中,由Banner类扮演此角色。
如果Adaptee角色中的方法与Target角色的方法相同(也就是说家庭使用的电压就是12伏特直流电压),就不需要接下来的Adapter角色了。
-
Adapter(适配)
Adapter模式的主人公。使用Adaptee角色的方法来满足Target角色的需求,这是Adapter模式的目的,也是Adapter角色的作用。
类比将交流100伏特电压转换为直流12伏特电压的适配器。
在示例程序中,由PrintBanner类扮演这个角色。
在类适配器模式中,Adapter角色通过继承来使用Adaptee角色,而在对象适配器模式中,Adapter角色通过委托来使用Adaptee角色。
拓展思路的要点
什么时候使用Adapter模式
如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了吗?为什么还要考虑使用Adapter模式呢?
很多时候,我们并非从零开始编程,经常会用到现有的类。特别是当现有的类已经被充分测试过了,Bug很少,而且已经被用于其他软件之中时,我们更愿意将这些类作为组件重复利用。
Adapter模式会对现有的类进行适配,生成新的类。通过该模式可以很方便地创建我们需要的方法群。
当出现Bug时,由于我们很明确地知道Bug不在现有的类(Adaptee角色)中,所以只需调查扮演Adapter角色的类即可,方便排查代码问题。
如果没有现成的代码
使用Adapter模式可以在完全不改变现有代码的前提下使现有代码适配于新的接口(API)。
此外,在Adapter模式中,并非一定需要现成的代码。只要知道现有类的功能,就可以编写出新的类。
版本升级与兼容性
版本升级时常会出现"与旧版本的兼容性"问题。现实中往往很难完全抛弃旧版本。
这时,可以使用Adapter模式使新旧版本兼容,以便同时维护新版本和旧版本。
例如,假设我们今后只想维护新版本。这时可以让新版本扮演Adaptee角色,旧版本扮演Target角色。接着编写一个扮演Adapter角色的类,让它使用新版本的类来实现旧版本的类中的方法。
功能完全不同的类
当然,当Adaptee角色和Target角色的功能完全不同时,Adapter模式是无法使用的。就如同我们无法用交流100伏特电压让自来水管出水一样。
相关的设计模式
Bridge模式(第9章)
Adapter模式用于连接接口(API)不同的类,而Bridge模式则用于连接类的功能层次结构与实现层次结构。
Decorator模式(第12章)
Adapter模式用于填补不同接口(API)之间的缝隙,而 Decorator模式则是在不改变接口(API)的前提下增加功能。