可复用性和可维护性
前言
好的JAVA设计模式强调多个接口和类之间的联系和使用,能产生更宏观的、高于单独接口和类设计的可复用性与可维护性。其设计模式可以总结为三大类:创建型模式、结构型模式和行为类模式。每一种设计模式的基础都是委托、组合、继承,使用它们来实现各种程序的复用性和可维护性。
一、面向可维护性的设计模式和构造技术
Factory Method 工厂方法模式
当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
Abstract Factory 抽象工厂模式
提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体类。
创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的object,各产品创建过程对client可见,但“搭配”不能改变。
本质上,Abstract Factory是把多类产品的factory method组合在一起
例子
一个UI,包含多个窗口控件,这些控件在不同的OS中实现不同
一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异
Abstract Factory vs Factory Method
Abstract Factory | Factory Method |
---|---|
创建多个类型对象 | 创建一个对象 |
多个factory方法 | 一个factory方法 |
使用组合/委派 | 使用继承/子类型 |
Proxy 代理模式
某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,故设置proxy,在二者之间建立防火墙。
public class ProxyImage implements Image {
private Image realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName; // 不需要在构造的时候从文件装载
}
@Override
public void display() {
if(realImage == null) { // 如果display的时候发现没有装载,则再委派
realImage = new RealImage(fileName); // Delegate到原来的类来成具体装载
}
realImage.display();
}
}
客户端:
Image image = new ProxyImage("pic.jpg");
image.display();
image.display();
在new ProxyImage("pic.jpg")
时仅仅是将文件名保存下来,没有加载真正的图片RealImage
;
在第一次调用Image.display()
时,image
委派Rep
中的realImage
进行加载,并显示;
在第二次调用,因为已经加载过,因此直接委派realImage.display()
显示
Observer 观察者模式
本ADT随时获取另一ADT状态变化
一对多广播
“偶像”对“粉丝”广播
需要广播的ADT记录着所有观察自己的Observer,在自己状态变化时,对于每个Observer调用其Update()方法进行更新,即获取该ADT的状态变化
Java里已经实现了该模式,提供了Observable抽象类(直接派生子类即可,构造“偶像”)
Java提供了Observer接口,实现该接口,构造“粉丝”
//偶像类
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();//维护一个粉丝列表
private int state;
public int getState() { return state; }
public void attach(Observer observer){ observers.add(observer); } //粉丝关注偶像
public void setState(int state) {
this.state = state;
notifyAllObservers(); //状态有变化的时候广播给粉丝
}
private void notifyAllObservers(){
for (Observer observer : observers)
observer.update();
}
}
//粉丝类
public abstract class Observer { //粉丝的抽象接口
protected Subject subject;
public abstract void update();
}
public class BinaryObserver extends Observer{ //粉丝的具体类
public BinaryObserver(Subject subject){
this.subject = subject; // 关注偶像
this.subject.attach(this); // 告知偶像自己关注了他
}
@Override
public void update() { //被偶像回调,通知自己有新消息
//可能有不同的行为
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); //偶像状态变化,虽然没有直接调用粉丝行为的代码,但确实有对粉丝的delegation
System.out.println("Second state change: 10");
subject.setState(10);
}
}
Visitor 访问者模式
对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类
本质上:将数据和作用于数据上的某种/些特定操作分离开来
为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT
即:“我”(源ADT)允许(调用this.accept())“你”(visitor)来访问我的数据(在accept()方法内委派visitor.visit())——数据源主动允许访问
使得访问方法可以变化
可以为源ADT预留功能
/* Abstract element interface (visitable) */
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor); //埋下一个槽
}
/* Concrete element */
public class Book implements ItemElement{
private double price;
...
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);
}
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor { //一种实现
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;
}
}
/* Client */
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) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items)
sum = sum + item.accept(visitor);
return sum;
}
}
二、面向复用的设计模式
Adapter 适配器模式
将某个类/接口转换为client期望的其他形式
增加接口
通过增加一个接口,将已存在的子类封装起来
client面向接口编程,从而隐藏了具体子类。
适用场合:你已经有了一个类,但其方法与目前client的需求不一致。
根据OCP原则,不能改这个类,所以扩展一个adaptor和一个统一接口。
Decorator 装饰者模式
继承组合会引起组合爆炸/代码重复
为对象增加不同侧面的特性
对每一个特性构造子类,通过委派机制增加到对象上
客户端需要一个具有多种特性的object,通过逐层的装饰来实现
例子:
Stack
对应上图Component
接口
ArrayStack
对应ConcreteComponent
,基础类
StackDecorato
对应Decorator
,装饰类(可以是抽象类)
UndoStack
对应ConcreteDecoratorA
,装饰类的具体类
//Stack接口,定义了所有的Stack共性的基础的功能
interface Stack {
void push(Item e);
Item pop();
}
//最基础的类,啥个性也没有的Stack,只有共性的实现
public class ArrayStack implements Stack {
... //rep
public ArrayStack() {...}
public void push(Item e) {...}
public Item pop() { ... }
}
//装饰器类,可以是一个抽象类,用于扩展出有各个特性方面的各个子类
public abstract class StackDecorator implements Stack {
protected final Stack stack; //用来保存delegation关系的rep
public StackDecorator(Stack stack) {
this.stack = stack; //建立稳定的delegation关系
}
public void push(Item e) {
stack.push(e); //通过delegation完成任务
}
public Item pop() {
return stack.pop(); //通过delegation完成任务
}
}
//一个有撤销特性功能的子类
public class UndoStack extends StackDecorator implements Stack {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) {
super(stack); //调用父类的构造方法建立delegation关系
}
public void push(Item e) {
log.append(UndoLog.PUSH, e); //实现个性化的功能
super.push(e); //共性的功能通过调用父类的实现来完成
}
public void undo() {
//implement decorator behaviors on stack
}
...
}
使用装饰对象:层层嵌套初始化new Class1(new Class2(new Class3(...)))
// 先创建出一个基础类对象
Stack s = new ArrayStack();
// 利用UndoStack中继承到的自己到自己的委派建立起从UndoStack到ArrayStack的delegation关系
// 这样,UndoStack也就能够实现最基础的功能,并且自身也实现了个性化的功能
Stack us = new UndoStack(s);
// 通过一层层的装饰实现各个维度的不同功能
Stack ss = new SecureStack(new SynchronizedStack(us));
facade 外观模式
客户端需要通过一个简化的接口来访问复杂系统内的功能
提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统做了一个封装,简化客户端使用
便于客户端学习使用,解耦
Strategy 策略模式
有多种不同的算法来实现同一个任务
但需要client根据需要动态切换算法,而不是写死在代码里
为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例
Template Method 模板方法模式
框架:白盒框架
做事情的步骤一样,但具体方法不同
共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现
使用继承和重写实现模板模式
适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”
Iterator 迭代器模式
客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型
也就是说,不管对象被放进哪里,都应该提供同样的遍历方式
实现方式是在ADT类中实现Iterable
接口,该接口内部只有一个返回一个迭代器的方法,然后创建一个迭代器类实现Iterator
接口,实现hasnext()、next()、remove()
这三个方法。
总结
创建型模式中最经典的成员当属“工厂方法模式”。该模式又被称作“虚拟构造器”,大体思想是在构建一个对象时,不去调用其具体的构造方法(比如不清楚该实例化哪个对象时),而是先创造一个接口,通过调用该接口的子类去决定实例化哪一个类。就好像我向某一个工厂提出了请求,工厂给了我产品,但是我完全不知道这个产品是如何生产出来的。在创建对象的时候,先新建一个“工厂”(工厂方法类),再调用其方法返回需要构建的类。如果工厂方法被设定成静态的,就可以不用创建新的工厂对象,直接使用ADT的静态方法即可。工厂方法模式满足了可维护性原则中的“OCP”原则(开放-封闭原则)。
结构型模式中包含了“适配器模式”与“装饰器模式”,两种模式的底层逻辑都是委托。适配器模式的主要思路是使用接口将client和具体类方法分隔开,尽量不接触。不使用适配器时,委托对象必须是具体的某个类。而设计适配器则是首先设计适配器接口,在接口中声明需要适配的方法,接着设计一个基础适配器的抽象类去继承接口,并在基础适配器的基础上延伸出许多适应不同情况的具体适配器,每一个具体适配器去实现其对应的具体委托。能看出来实际上工厂方法也是一种针对构造方法的适配器。
“装饰器模式”则是为了应对需要实现多种功能、进行大量子类继承而导致的组合爆炸。大体思想是创建一个接口,并完成两个实现:基础类实现和基础装饰器类实现。基础装饰器类是抽象的,其构造方法需要委托基础类来实现。再根据功能需要设计出一系列继承装饰器类的具体装饰器,每一个装饰器根据需要复写基础装饰器中的某些方法。这样,在构建一个对象时,就可以像“穿衣服一样”,通过一层一层调用不同的装饰器来给为对象添加不同的功能。相较于继承,装饰器是在代码运行时实现的功能组合。
行为类模式侧重于根据ADT需要实现的不同行为进行设计,包括策略模式、模板模式、迭代器和观察器。策略模式的大体思想是实现某个功能可能有不同的算法,那就将方法抽离出来,根据实际策略进行调用。创建策略接口,声明存在多种策略的方法,然后设计不同的子类去实现这个接口,代表不同策略的具体实现。在实际使用时,委托这些不同的策略去实现不同的方法。模板模式则是基于继承和重写的一种设计模式,针对那些有一定顺序和模板的任务,但是每一个环节的具体实现方法可能不同,则创建一个抽象模板类,其中包含着每一个环节的抽象方法和一些通用环节的具体实现,并设定好环节顺序。之后根据需要设计其子类,完成各环节的具体实现。迭代器模式则是让自己设计的集合类实现Iterable接口,并实现自己独特的迭代器,允许客户端使用迭代器去遍历这个集合类。观察器的实现基于方法重载,目的是将数据与操作分离开,方便针对不同的数据进行不同的操作。设计一个观察器接口,声明对不同数据的不同操作方法,并在具体类中实现这些方法。