【软件构造】--可复用性和可维护性


前言

好的JAVA设计模式强调多个接口和类之间的联系和使用,能产生更宏观的、高于单独接口和类设计的可复用性与可维护性。其设计模式可以总结为三大类:创建型模式、结构型模式和行为类模式。每一种设计模式的基础都是委托、组合、继承,使用它们来实现各种程序的复用性和可维护性。


一、面向可维护性的设计模式和构造技术

Factory Method 工厂方法模式

当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

Abstract Factory 抽象工厂模式

提供接口以创建一组相关/相互依赖的对象,但不需要指明其具体类。
创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的object,各产品创建过程对client可见,但“搭配”不能改变。
本质上,Abstract Factory是把多类产品的factory method组合在一起

例子
一个UI,包含多个窗口控件,这些控件在不同的OS中实现不同
一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异

Abstract Factory vs Factory Method

Abstract FactoryFactory 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接口,并实现自己独特的迭代器,允许客户端使用迭代器去遍历这个集合类。观察器的实现基于方法重载,目的是将数据与操作分离开,方便针对不同的数据进行不同的操作。设计一个观察器接口,声明对不同数据的不同操作方法,并在具体类中实现这些方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值