5.2 面向可维护性的设计模式
一. 关于如何创建类的新实例 的模式
1. Factory method pattern
工厂方法模式
当 client
不知道要创建哪个具体类的实例,或者不想在 client
代码中指明要具体创建的实例时,用工厂方法。
工厂方法:定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
例,如图所示:
Product p = new ProductTwo();//一般的构造方法
Product p = new ConcreteTwo.makeObject();//工厂方法
静态工厂方法:既可以在 ADT
内部实现,也可以构造单独的工厂类。可以直接调用,无需 new
。
Open-Closed Principle
(OCP
) 对扩展的开放,对修改已有代码的封闭。
2. Abstract factory pattern
抽象工厂方法
抽象工厂模式:提供接口以创建一组相关/相互依赖的对象但不需要指明其具体。
- 一个
UI
包含多个窗口控件,这些控件在不同的OS
中实现不同 - 一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异
例:
例:
//抽象产品接口和具体产品类
//AbstractProduct
public interface Window{
public void setTitle(String s);
public void repaint();
public void addScrollbar(...);
}
//ConcreteProductA1
public class PMWindow implements Window{
public void setTitle(){...}
public void repaint(){...}
}
//ConcreteProductA2
public class MotifWindow implements Window{
public void setTitle(){...}
public void repaint(){...}
}
//抽象工厂接口和具体工厂类
//AbstractFactory
public interface AbstractWidgetFactory{
public Window createWindow();
public Scrollbar createScrollbar();
}
//ConcreteFactory1
public class WidgetFactory1{
//第一个具体产品的工厂方法
public Window createWindow(){
return new MSWindow();
}
public Scrollbar createScrollbar(){A}
}
//ConcreteFactory2
public class WidgetFactory2{
//第二个具体产品的工厂方法
public Window createWindow(){
return new MotifWindow();
}
public Scrollbar createScrollbar(){B}
}
//辅助类
public class GUIBuilder{
//Delegate 到抽象工厂类
public void buildWindow(AbstractWidgetFactory widgetFactory){
//使用抽象工厂类分别创建两个具体产品
Window window = widgetFactory.createWindow();
Scrollbar scrollbar = widgetFactory.createScrollbar();
window.setTitle("New Window");
window.addScrollbar(scrollbar);//把创建的两个具体产品实例组合起来(模式之外)
}
}
//Client builder辅助类
GUIBuilder builder = new GUIBuilder();
AbstractWidgetFactory widgetFactory = null;//定义抽象工厂接口的实例
//根据要创建的“组合产品”的类型,构建不同的抽象工厂子类
if("motif")
widgetFactory = new WidgetFactory2();
else
widgetFactory = new WidgetFactory1();
builder.buildWindow(widgetFactory);//具体构建
抽象工厂方法创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object
,各产品创建过程对 client
可见,但“搭配”不能改变。
本质上, Abstract Factory
是把多类产品的 factory method
组合在一起。使用抽象工厂方法是防止 client
由于不知道搭配造成的失误。
二. 结构设计模式
1. Proxy
代理模式
某个对象比较“敏感”/“私密”/“贵重”,不希望被 client
直接访问到,故设置 proxy
,在二者之间建立防火墙。即先加载假的(引用),等到有用时再加载真的。
如图所示:
流程简化为:
例:
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {...}
//每次创建都要从磁盘装载,代价高
private void loadFromDisk(String fileName){...}
}
public class ProxyImage implements Image {
private Image realImage;
private String fileName;
//但不需要在构造的时候从文件装载
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
//如果display的时候发现没有装载,则再delegation
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();//Delegate到原来的类来完成具体装载
}
}
//Client:
Image image = new ProxyImage("pic.jpg");
image.display();
image.display();
Adaptor
与 Proxy
的不同:
Adaptor
消除不兼容,目的是B
以客户端期望的统一的方式与A
建立起联系。Proxy
目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”
三. 行为模式
1. 观察者模式 Observer
多对一需求(类似广播):
- “粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动
- 粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝(回调
callback
粉丝的特定功能)
时序图:
代码:
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();//维持一组“对自己感兴趣的”对象
private int state;
public int getState() {return state;}
public void setState(int state) {
this.state = state;
notifyAllObservers();//在自己状态变化时,通知所有“粉丝”
}
//允许“粉丝”调用该方法向自己注册,将其加入队列,即建立 delegation 关系
public void attach(Observer observer){observers.add(observer);}
private void notifyAllObservers(){
for (Observer observer : observers) {
//callback 调用“粉丝”的 update 操作,向粉丝“广播”自己的变化实际执行 delegation
observer.update();
}
}
}
//“粉丝”的抽象接口
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
//构造时,指定自己的“偶像”subject,把自己注册给它这是相反方向的 delegation
this.subject = subject;
this.subject.attach(this);
}
//注意:这个方法是被“偶像”回调的
@Override
public void update() {
//当“偶像”有状态变化时,调用 subject.getState() 获取最新信息
System.out.println( "Binary String: " + Integer.toBinaryString(subject.getState()));
//不同子类的“粉丝”,其行为可定制
}
}
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();//偶像一枚
//粉丝三枚
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
//偶像有新闻了
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
//并没有直接调用粉丝行为的代码!但其内部隐藏着对粉丝行为的 delegation
}
}
Java
里已经实现了该模式,提供了 Observable
抽象类(直接派生子类即可,构造“偶像”,观察对象)。Java
提供了 Observer
接口(观察者),实现该接口,构造“粉丝”。
2. Visitor
模式
对特定类型的 object
的特定操作 ( visit
),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被 visit
的类。
为 ADT
预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变 ADT
本身的情况下通过 delegation
接入 ADT
。
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
Visitor
——唯一的双向委托设计模式。
例:
/* Abstract element interface (visitable) */
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
/* Concrete element */
public class Book implements ItemElement{
private double price;
...
//将处理数据的功能 delegate 到外部传入的 visitor
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
public class Fruit implements ItemElement{
private double weight;
...
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
/* Abstract visitor interface */
public interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
//这里只列出了一种 visitor 实现
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
//这个 visit 操作的功能完全可以在 Book 类内实现为一个方法,但这就不可变了
public int visit(Book book) {
int cost=0;
if(book.getPrice () > 50){
cost = book.getPrice() - 5;
}else
cost = book.getPrice();
System.out.println("Book ISBN::" + book.getIsbnNumber() + " cost ="+cost);
return cost;
}
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg()*fruit.getWeight();
System.out.println(fruit.getName () + " cost = "+ cost);
return cost;
}
}
public class ShoppingCartClient {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{
new Book(20, "1234"),new Book(100, "5678"),
new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};
int total = calculatePrice(items);
System.out.println("Total Cost = "+total);
}
private static int calculatePrice ItemElement[] items) {
//只要更换 visitor 的具体实现,即可切换算法
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items)
sum = sum + item.accept(visitor);
return sum;
}
}
Visitor
与 Iterator
的不同:
- 迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能
delegate
到外部的iterator
对象。 Visitor
:在特定ADT
上执行某种特定操作,但该操作不在ADT
内部实现,而是delegate
到独立的visitor
对象,客户端可灵活扩展/改变visitor
的操作算法,而不影响ADT
Strategy
与 Visitor
的不同:
- 二者都是通过
delegation
建立两个对象的动态联系 Visitor
强调是的外部定义某种对ADT
的操作,该操作于ADT
自身关系不大(只是访问ADT
),故ADT
内部只需要开放accept(visitor)
即可,client
通过它设定visitor
操作并在外部调用。- 而
Strategy
则强调是对ADT
内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT
功能的重要组成部分,只不过是delegate
到外部strategy
类而已。 visitor
是站在外部client
的角度,灵活增加对ADT
的各种不同操作(哪怕ADT
没实现该操作),strategy
则是站在内部ADT
的角度,灵活变化对其内部功能的不同配置。
四. 设计模式的对比
1. 设计模式共性样式 1
- 只使用“继承”,不使用委托
- 核心思路:
OCP/DIP
- 依赖反转,客户端只依赖“抽象”,不能
- 依赖于“具体”
- 发生变化时最好是“扩展”而不是“修改”
单继承树如 Strategy
(传统型) 、 Proxy
、 Template
模式。
1.1 Adaptor
模式
适用场合:你已经有了一个类,但其方法与目前 client
的需求不一致。
根据 OCP
原则,不能改这个类,所以扩展一个 adaptor
和一个统一接口。
注:实际上Adaptor
和被适配的类可能不是同一个接口的继承。
1.2 Proxy
模式
适用场合:你已经有了一个类,但其方法与目前 client
的需求不一致。
根据 OCP
原则,不能改这个类,所以扩展一个 adaptor
和一个统一接口。
1.3 Template
(模板)模式
适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”
2. 设计模式共性样式 2
两棵“继承树”,两个层次的"delegation
"。
双继承树如设计模式:桥接模式、Iterator
、 Factory method
、 Abstract Factory
、 Obsever
、Visitor
等。