组件协作类设计模式
1 分类含义
现代软件专业分工之后的第一个结果是框架与应用程序的划分,组件协作模式通过晚期绑定,来实现框架与应用之间的松耦合,组件协作模式就是常用于这类场景的模式。
2 模板方法模式
2.1 动机
在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架于应用之间的关系)需求无法和任务的整体结构同时实现。
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的的变化或者晚期实现的需求?
2.2 场景
如某个业务需要步骤一、步骤二、步骤三、步骤四、步骤五 五个步骤完成,但是步骤一三四是框架编写的固定处理程序,步骤二五需要后期的应用程序编写人员来编写实现。
2.2.1实现方式一(客户端控制主流程)
方案一:接口(或抽象类)定义步骤一二三四五的方法,并实现一三四的方法,方法的实现和步骤的掌控交给实现类(或子类)
public class TemplateMethod {
/**
* 类库实现者开发
*/
interface Lib {
default void step1() {
//实现
}
/**
* 晚绑定
*/
void step2();
default void step3() {
//实现
}
default void step4() {
//实现
}
/**
* 晚绑定
*/
void step5();
}
/**
* 客户端
*/
class Client implements Lib {
@Override
public void step2() {
//实现
}
@Override
public void step5() {
//实现
}
/**
* 需求
*/
public void demand() {
//控制步骤
step1();
step2();
step3();
step3();
step4();
step5();
}
}
}
缺点: 客户端使用者把控稳定的步骤一至步骤五的操作,容易提高代码出错的概率;当变化多的时候,demand()方法复用率低(其次,你也可以抽方法,虽然有更好的复用方式)
2.2.2 实现方式二(模板方法模式)
public class TemplateMethod {
/**
* 类库实现者开发
*/
interface Lib {
default void step1() {
//实现
}
//变化-->多态
void step2();
default void step3() {
//实现
}
default void step4() {
//实现
}
/**
* 变化-->多态
*/
void step5();
/**
* 整体结构(模板方法模式)
*/
default void demand(){
step1();
step2();
step3();
step3();
step4();
step5();
}
}
public static void main(String[] args) {
Lib lib = new Lib() {
@Override
public void step2() {
//实现
}
@Override
public void step5() {
//实现
}
};
lib.demand();
}
}
2.3 概念
定义一个操作中的算法的骨架,而将一些变化的步骤延迟到子类中,模板方法模式使得子类可以复用一个算法的结构,并且同时可重写算法的某些特定步骤来应对这个步骤的变化。
2.4 要点总结
- 模板方法模式是一个非常基础性的设计模式,在面向对象系统种有着大量的应用,它用抽象方法的多态性为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构
- 除了可以灵活应对子步骤的变化外,不要调用我,让我来调用你的反向控制结构是模板方法模式的典型应用。
- 在具体实现方面,被模板方法调用的虚方法可以有具体实现,也可以没有任何实现。
3 策略模式
3.1 动机
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到一个对象中,将会使对象变得异常复杂且不易适应变化。
如何在运行时根据需要透明的更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
3.2 场景
现在要做一个税率计算软件,要支持很多国家的税率计算,而且在往后可能还需要支持更多的国家,怎么利用策略模式来实现这样的需求?
3.2.1 抽象策略
package com.example.demo.strategy;
import java.math.BigDecimal;
public interface TaxStrategy {
BigDecimal calculateTax(BigDecimal money);
}
3.2.2 中国税法实现
package com.example.demo.strategy.impl;
import com.example.demo.strategy.TaxStrategy;
import java.math.BigDecimal;
public class ChineseTaxStrategy implements TaxStrategy {
@Override
public BigDecimal calculateTax(BigDecimal money) {
// 按中国的税法计算应缴税额
return money.multiply(new BigDecimal("0.07"));
}
}
3.2.3 美国税法实现
package com.example.demo.strategy.impl;
import com.example.demo.strategy.TaxStrategy;
import java.math.BigDecimal;
public class USATaxStrategy implements TaxStrategy {
@Override
public BigDecimal calculateTax(BigDecimal money) {
return money.multiply(new BigDecimal("0.08"));
}
}
3.2.4 客户端调用
package com.example.demo.strategy;
import com.example.demo.strategy.impl.USATaxStrategy;
import java.math.BigDecimal;
public class PayTax {
public static void main(String[] args) {
// 这里的策略实现类一般由工厂生成
payTax(new USATaxStrategy(), new BigDecimal("10"));
}
public static void payTax(TaxStrategy taxStrategy, BigDecimal money){
// 保证了主流程不变,变化全部在工厂和新增的类里面
BigDecimal tax = taxStrategy.calculateTax(money);
System.out.println("缴纳税额:" + tax);
}
}
3.3 什么是复用【题外话】
策略模式往往也可以通过if else不同的分支来实现,但是这样做的坏处是没有做到代码的复用,我们通常认为,在代码编译时这个文件编程生成的字节码没有变化才算作复用,因为只有编译内容完全没变,才能保证不会影响之前所书写的代码。
3.4 模式定义
定义一系列算法,把他们一个个封装起来,并且是他们可以相互替换。该模式使得算法可以独立于使用它的客户程序而变化。
3.5 要点总结
- 策略模式及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换;
- 策略模式能够一定程度上消除条件判断语句
- 策略如果没有状态或实例变量的话,可以全局使用同一个对象。
4 观察者模式
4.1 动机
在软件构建过程中,我们需要为某些对象建立一种通知依赖关系,一个对象的状态发生改变,所有的依赖对象将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化。
4.2 场景
实现作者发布文章后订阅该作者的用户收到作者发布的通知
4.2.1 JDK观察者
JDK有提供一个观察者的简单抽象,先看看JDK提供的观察者
package java.util;
public interface Observer {
void update(Observable o, Object arg);
}
很简单的一个接口,没什么好说的。
4.2.2 JDK被观察者
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
大致就是设置了一个状态,和观察者数组,观察者数据里面维护了观察对象,调用notify…方法的时候,如果状态为true,则会循环调用被维护的观察对象的update方法。
4.2.3 实现User观察者
package com.example.demo.observer;
import java.util.Observable;
import java.util.Observer;
public class SubscribeUser implements Observer {
private final String nickName;
public SubscribeUser(String nickName) {
this.nickName = nickName;
}
@Override
public void update(Observable o, Object arg) {
Author author = (Author) o;
System.out.println(nickName + "接收到来自" + author.getAuthorName() + "的文章" + arg);
}
}
这个类仅仅是实现了Observer 接口,为了方便展示,我们添加了一个昵称字段,在update方法中打印了这条通知消息
4.2.4 实现作者被观察者
package com.example.demo.observer;
import java.util.Observable;
import java.util.Observer;
public class Author extends Observable {
private final String authorName;
public Author(String authorName) {
this.authorName = authorName;
}
public void setObserver(Observer[] observers){
for (Observer observer : observers) {
addObserver(observer);
}
}
public String getAuthorName() {
return authorName;
}
public void publishNewArticle(String articleTitle){
System.out.println(this.authorName + "发布了文章" + articleTitle);
setChanged();
notifyObservers(articleTitle);
clearChanged();
}
}
同样的,为了方便展示给了一个名称,天界了一个设置观察者的方法,发布新文章的方法,在新文章中调用了JDK被观察者的set状态,通知观察者后清除状态。
4.2.5 客户端调用
package com.example.demo.observer;
public class Main {
public static void main(String[] args) {
Author author = new Author("天蚕土豆");
SubscribeUser[] subscribeUsers = {new SubscribeUser("贺伟"),
new SubscribeUser("梁少波")};
author.setObserver(subscribeUsers);
author.publishNewArticle("《斗破苍穹》");
author.publishNewArticle("《大主宰》");
}
}
4.3 观察者模式要点
- 使用面向对象的抽象,观察者模式可以是我们可以独立的改变观察者与被观察者,从而使二者的依赖关系达到松耦合。
- 目标发送通知时,无需指定观察者,通知会自动传播
- 观察者模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一份重要组成部分。