适应设计模式并非具有真正的字面含义:只是根据作者的用意,用最容易理解的设计模式,为我们入门
这里给出了其中两种设计模式:Iterator & Adapter
一、Iterator 模式
正如我在 设计模式前言中所述,java.util.Iterator 下面有实现,但是我们要做的是摒弃已经有的实现,自己从头理解设计这个模式的含义以及作用。
1.分析需求背景
程序为了实现将 书 (Book) 放置到书架 (BookShelf) 中,并将书的名字按照顺序 (书架中存放的顺序) 展示出来
2.UML
3.Code
3.1 角色:Iterator 迭代器
/**
* 该角色负责定义按顺序逐个遍历元素的 接口(API)
* @author CSZ
*/
public interface Iterator {
/**
* @return 用于判断是否存在下一个
*/
public abstract boolean hasNext();
/**
* @return 下一个元素
*/
public abstract Object next();
}
3.2 角色:ConcreteIterator 具体迭代器
/**
* 这里我们实现接口,自然会重写两个方法:
* 该角色负责实现 Iterator 角色定义的接口(API)
* @author CSZ
*/
public class BookShelfIterator implements Iterator{
private final BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf){
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public Object next() {
Book book = bookShelf.getBookAt(index);
index ++;
return book;
}
}
3.3 角色:Aggregate 集合
/**
* 角色:Aggregate 集合
* 该角色负责定义创建 Iterator 角色的接口(API)
* 接口(API) 是一个方法:创建一个迭代器
* @author CSZ
*/
public interface Aggregate {
/**
* @return 返回一个 Iterator 接口
*/
public abstract Iterator iterator();
}
3.4 角色:ConcreteAggregate 具体集合
/**
* 通过实现 Aggregate 来重写 iterator() 来获取具体的迭代器
* @author CSZ
*/
public class BookShelf implements Aggregate{
private final Book[] books;
private int last = 0;
public BookShelf(int maxSize){
this.books = new Book[maxSize];
}
public Book getBookAt(int index){
return books[index];
}
public void appendBook(Book book){
this.books[last] = book;
last++;
}
public int getLength(){
return last;
}
@Override
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
3.5 普通的 pojo Book
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.6 测试代码
public class IteratorTest {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("A"));
bookShelf.appendBook(new Book("B"));
bookShelf.appendBook(new Book("C"));
bookShelf.appendBook(new Book("D"));
// 核心业务代码
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()){
Book book = (Book) iterator.next();
System.out.println(book.getName());
}
}
}
4. 我们再来看看这个类图关系并思考问题:
- 我们为什么要使用 Iterator?
首先我们要分析需求,我们希望以调用者的角度去遍历每个元素。
在业务代码中,如果我们直接拿到容器 Books[],然后用 for 循环获取元素。这种方法完全可以实现。
可是假设,有一天我们换了新的书架,改用 List<Book> Books。我们该怎么处理呢?我们只需要修改,BookShelf 和 BookShelfIterator 就达成目的了。而我们的核心业务代码部分,没有做任何的修改。
这说明 这里的 while 循环并不依赖于 BookShelf 的实现。
- 在我看这个设计模式的时候,我存在一个疑问,为什么还要准备一个 Aggregate 接口?
我们可以想这样一个问题,在 BookShelf 类是实现了 Aggregate 接口,然后重写 iterator() 的方式获取到的遍历器。
如果我们的 BookShelf 类 不实现 Aggregate 接口,并且方法直接写成public getBookShelfIterator(){ return new BookShelfIterator(this); }
这样大家直接可以看出这种写法太过于耦合了,因为我们在核心业务代码中,只能使用
Iterator iterator = bookShelf.getBookShelfIterator();
同样的道理,我们让 BookShelf 类实现 Aggregate 接口,表示了 BookShelf 类具备了一种能力。
一种我们可以拿到 iterator 并用 hasNext() 以及 next() 的方法进行遍历的能力。
此外,当我们 需要使用其他遍历器来完成操作时,只需要 写一个新的遍历器实现类,并在重写方法内部并创建并用于返回即可,核心代码的逻辑还是没有改变。
二、Adapter 模式
对于包装器的模式,在 Spring Framework 的底层有大量的使用,
A) 类适配器模式(使用继承手段)
1. 场景
目前我们的固定插座是没有办法改变的,而我买回来的电脑充电线也是没有办法改变的,总不能直接换个电脑吧,那我想一个折中的办法,中间加一个组件,使两者联系起来。
2. UML
3. Code
3.1 Print 接口也就是我们的需求,对应于图中固定的插座(他规定了使用的条件,100伏特)
/**
* @author CSZ
*/
public interface Print {
void printWeak();
void printStrong();
}
3.2 Banner 类对应我们的电脑插头,也是固定的(12伏特,可以使用)
/**
* @author CSZ
*/
public class Banner {
private String string;
public Banner(String string){
this.string = string;
}
public void showWithParen(){
System.out.println("( " + string +" )");
}
public void showWithAster(){
System.out.println("* " + string + " *");
}
}
3.3 PrintBanner 作为两者的调和者,继承了 Banner 的能力,并且针对 Print 接口要求,进行了适配
public class PrintBanner extends Banner implements Print{
public PrintBanner(String string){
super(string);
}
@Override
public void printWeak() {
showWithParen();
}
@Override
public void printStrong() {
showWithAster();
}
}
3.4 测试代码
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
// 对于 Print 是功能的声明者,他用于指明要使用的方法
// 对于Banner 来说,他是功能的完成者
// 但是由于 Banner 不符合 Print 的要求,PrintBanner 继承了Banner的功能,有为了符合 Print 要求而做了调整。
// 所以其中的奥妙就在于继承 Banner 并调用了 Banner 中的方法,获得了Banner的能力
Print banner = new PrintBanner("Hello");
banner.printWeak();
banner.printStrong();
}
}
B) 对象适配器模式(使用委托)
1. 场景没有变化 唯一变化的是现在 Print 也成了类
java 中,不允许多继承,这样的情况又该怎样处理
2. UML
其实如果 我们熟悉 UML 可能已经看明白了,很简单吗,作为 PrintBanner 我不能多继承,这里就干脆只继承 Print 类,Banner 呢,从 UML 中我们可以看到,这是一种聚合关系,即使Banner 变成我的组成部分之一,也就是 has -a 的关系。
3. Code
其中,测试方法和 Banner 类没有代码调整
3.1 Print 变成了类
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
3.2 PrintBanner 也做调整
/**
* @author CSZ
*/
public class PrintBanner extends Print{
private Banner banner;
public PrintBanner(String string){
this.banner = new Banner(string);
}
@Override
public void printWeak() {
banner.showWithParen();
}
@Override
public void printStrong() {
banner.showWithAster();
}
}
4. 再次回顾UML 总结与思考:
- 一个很本质的问题,无论是A方式还是B方式,前提是两者是具有一定的联系(类似的功能),比如一个是用100伏特的电,一个是用 0.07mpa 的水压。你再怎么适配也是没有价值,因为两个完全没有联系。
- 我们已经正式上线的代码,经过了时间的检测,是没有质量问题的,而此刻我们新的开发中如果需要其中的功能,我们应该使用适配器模式创建一个新的类,将原有的类继承或者实现,并在新的类中完成对新的接口的适配。这样我们原有的代码就可以保持不变,而且如果新的代码出现了问题,我们使用新的适配器类,也可以更容易定位问题。
- 最后让我们看一下 spring 源码叭:
在AbstractApplicationContext
类中的如下方法:用来创建后置处理器工厂
根据继承关系我们找到了ResourceAdapterApplicationContext
,他到底做了怎样的 wapper呢?
通过重写 postProcessBeanFactory 方法,来适配关于 Resource 的后置处理器工厂的加载创建。