1、目标
本文的主要目标是学习设计模式的行为型模式并举例说明
2、行为型模式
2.1 观察者模式(Observer)
观察者模式是对象之间存在一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新,这里的对象指的是Observable目标,依赖目标的对象是Observer观察者,Observer观察者必须调用addObserver方法将Observer观察者注册到Observable目标的容器中,Observable目标监听到事件发生会调用notify方法通知所有的Observer观察者自动更新,观察者模式又叫做发布订阅模式,它的优点是如果新增一个功能监听事件发生,可以创建一个新的Observer观察者,不需要修改Observable目标,耦合性小
需求:用户点击页面会先查询消费者的地址然后展示欢迎页
分析:在点击页面的代码前后加上这两个逻辑,硬编码,如果不同公司的用户点击页面的行为不一样呢
优化思路:采用观察者模式,定义Observer接口,每次添加一个逻辑会创建一个观察者对象,观察者对象需要先注册到Customer对象中的容器,然后调用notify方法会发布消息会循环调用每个观察者对象的update方法,Java提供了Observer观察者对象和Observable目标
程序:
public class ObservableFactory {
public void clickPage(Customer customer, List<Observer> observerList) {
for (Observer observer : observerList) {
customer.addObserver(observer);
}
customer.clickPage();
}
public class Customer extends Observable {
// 用户点击页面,会查询消费者的地址和展示欢迎邮件
public void clickPage() {
// 必须先设置changed=true才会循环调用Observer的update方法
// Observable类的setChanged方法是protected只能在这个包下或者子类调用setChanged方法,
// 每个业务对变化的定义不同,因此它是由子类判断是否变化
super.setChanged();
super.notifyObservers();
}
}
public class WelcomeLetter implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("观察者Observer WelcomLetter向目标 " + o + " 发送一个欢迎信");
}
}
public class AddVerify implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("观察者Observer AddVerify向目标 " + o + " 发送一个验证消费者的地址");
}
}
}
Java包含了观察者模式的类,Observer类是观察者,Observable类是目标
创建WelcomeLetter、AddVerify两个Observer观察者对象,重写update方法
创建Customer类,继承Observable类,创建clickPage方法,当用户点击页面会先调用setChanged方法然后调用notifyObservers方法通知所有的观察者对象
public interface Observer {
void update(Observable o, Object arg);
}
Observer接口包含update方法,入参是Observable目标和参数arg
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
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) {
Object[] arrLocal;
synchronized (this) {
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();
}
}
Observable类包含changed表示是否变化的标志和obs这个Vector容器,它保存了Observer集合,addObserver方法可以将Observer观察者对象添加到Vector容器中
notifyObservers方法会先判断changed必须是true才会复制Vector容器的元素到一个数组中,然后从后向前逆序遍历数组并调用Observer观察者对象的update方法,逆序是因为防止并发场景,如果正序遍历在循环遍历的时候某个观察者对象从Vector容器中取消了会导致数组的索引变化了,导致并发修改异常
setChanged方法是protected类型的,因为每个业务都变化的定义都不相同,因此可以创建一个子类继承Observable类自己定义什么时候需要调用setChanged方法表示数据变化了
public class TaskController {
@Test
public void f2() {
ObservableFactory observableFactory = new ObservableFactory();
ObservableFactory.Customer customer = observableFactory.new Customer();
ObservableFactory.WelcomeLetter welcomeLetter = observableFactory.new WelcomeLetter();
ObservableFactory.AddVerify addVerify = observableFactory.new AddVerify();
List<Observer> list = new ArrayList<>(2);
list.add(welcomeLetter);
list.add(addVerify);
observableFactory.clickPage(customer, list);
}
}
TaskController是创建Customer对象、WelcomeLetter、AddVerify,调用clickPage方法可以发布并且观察者可以得到通知并自动更新
测试结果:
目标对象发布消息,观察者会得到通知并自动更新
2.2 状态模式(State)
状态模式是对象有多种状态,不同状态之间的变化封装在不同的状态类中,思路是定义一个状态接口和多个状态实现类,每个状态实现类都重写转换到其他状态的方法,最后定义一个活动类,通过map.get得到对应的状态类并调用方法
程序:
public class StateFactory {
public class Activity {
private String state;
private Map<String, ActivityState> map = new HashMap<>();
public Activity() {
state = "结束";
map.put("启动", new ActivityStartedState());
map.put("暂停", new ActivityResumedState());
map.put("结束", new ActivityEndedState());
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void startActivity() {
map.get(state).startActivity(this);
}
public void resumeActivity() {
map.get(state).resumeActivity(this);
}
public void endActivity() {
map.get(state).endActivity(this);
}
}
public interface ActivityState {
public abstract void startActivity(Activity activity);
public abstract void resumeActivity(Activity activity);
public abstract void endActivity(Activity activity);
}
public class ActivityStartedState implements ActivityState {
@Override
public void startActivity(Activity activity) {
System.out.println("活动已经启动了,不能重复启动");
}
@Override
public void resumeActivity(Activity activity) {
System.out.println("活动暂停");
activity.setState("暂停");
System.out.println("state = " + activity.getState());
}
@Override
public void endActivity(Activity activity) {
System.out.println("活动结束");
activity.setState("结束");
System.out.println("state = " + activity.getState());
}
}
public class ActivityResumedState implements ActivityState {
@Override
public void startActivity(Activity activity) {
System.out.println("活动重新启动");
activity.setState("启动");
System.out.println("state = " + activity.getState());
}
@Override
public void resumeActivity(Activity activity) {
System.out.println("活动已经暂停了,不能重复暂停");
}
@Override
public void endActivity(Activity activity) {
System.out.println("活动结束");
activity.setState("结束");
System.out.println("state = " + activity.getState());
}
}
public class ActivityEndedState implements ActivityState {
@Override
public void startActivity(Activity activity) {
System.out.println("活动启动");
activity.setState("启动");
System.out.println("state = " + activity.getState());
}
@Override
public void resumeActivity(Activity activity) {
System.out.println("活动已经结束,不能暂停");
}
@Override
public void endActivity(Activity activity) {
System.out.println("活动已经结束,不能重复结束");
}
}
}
定义一个状态接口和多个状态实现类,每个状态类都重写转换到其他状态的方法,还定义了一个活动类,封装了state状态表示活动的当前状态,封装了一个Map可以存放不同的state和对应的状态对象,通过map.get方法得到状态对象并调用状态对象的方法
@Test
public void f18() {
StateFactory stateFactory = new StateFactory();
StateFactory.Activity activity = stateFactory.new Activity();
activity.startActivity();
activity.resumeActivity();
activity.endActivity();
}
创建一个活动对象,活动对象的状态初始化是结束状态,先调用活动对象的start方法可以从结束状态转换到启动状态,然后调用活动对象的resume方法可以从启动状态转换到暂停状态,最后调用活动对象的end方法可以从暂停状态转换到结束状态
测试结果:
测试结果表明状态模式应用成功,可以正常切换活动状态
2.3 责任链模式(Chain Of Responsibility)
责任链模式是允许多个对象都可以处理请求,会将这些对象连成一个链,直到有一个对象可以处理请求为止,思路是定义一个抽象类,组合一个对象因为每个对象都持有下一个对象的引用
程序:
public class LoggerFactory {
abstract public class Logger {
protected Logger nextLogger;
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
abstract public void log(int logLevel);
}
public class DebugLogger extends Logger {
@Override
public void log(int logLevel) {
if(logLevel == 0) {
System.out.println("打印debug日志");
} else {
nextLogger.log(logLevel);
}
}
}
public class InfoLogger extends Logger {
@Override
public void log(int logLevel) {
if(logLevel == 1) {
System.out.println("打印info日志");
} else {
nextLogger.log(logLevel);
}
}
}
public class ErrorLogger extends Logger {
@Override
public void log(int logLevel) {
if(logLevel == 2) {
System.out.println("打印error日志");
} else {
nextLogger.log(logLevel);
}
}
}
}
定义一个抽象类,它组合了下一个对象,抽象方法log方法用来打印日志,多个继承类可以重写log方法,如果条件不满足就调用下一个对象的log方法
@Test
public void f19() {
LoggerFactory loggerFactory = new LoggerFactory();
LoggerFactory.DebugLogger debugLogger = loggerFactory.new DebugLogger();
LoggerFactory.InfoLogger infoLogger = loggerFactory.new InfoLogger();
LoggerFactory.ErrorLogger errorLogger = loggerFactory.new ErrorLogger();
debugLogger.setNextLogger(infoLogger);
infoLogger.setNextLogger(errorLogger);
debugLogger.log(2);
debugLogger.log(1);
debugLogger.log(0);
}
创建一个对象链,如果这个对象满足条件会打印日志,如果不满足条件会沿着对象链调用下一个对象的打印日志方法
输出结果:
第一个方法打印日志是error,第二个方法打印日志是info,第三个方法打印日志是debug
2.4 策略模式(Strategy)
策略模式是定义多个算法,它们可以相互替换,定义一个抽象类和多个实现类,寻找可变的参数并封装到这个抽象类中
需求:处理订单计算不同国家的税
分析:直接if else,硬编码,如果情况很多会有很多if else
优化思路:计算税用一个抽象类,定义一个抽象方法,多个实现类重写这个方法,就可以计算不同国家的税
程序:
public class SalesOrder {
// CalcTax作为入参
public void process(CalcTax calcTax, int num, double price) {
double total = calcTax.taxAmount(num, price);
System.out.println(calcTax.getClass() + " total = " + total);
}
// 成员内部类
public abstract class CalcTax {
abstract public double taxAmount(int num, double price);
}
// 成员内部类
public class USTax extends CalcTax {
@Override
public double taxAmount(int num, double price) {
return num * price * 2;
}
}
// 成员内部类
public class CanTax extends CalcTax {
@Override
public double taxAmount(int num, double price) {
return num * price * 1.5;
}
}
}
CalcTax抽象类和继承类都封装在SalesOrder类中,CalcTax抽象类作为SalesOrder类的process方法的入参
public class TaskController {
@Test
public void f1() {
SalesOrder salesOrder = new SalesOrder();
SalesOrder.USTax usTax = salesOrder.new USTax();
salesOrder.process(usTax, 2, 15);
SalesOrder.CanTax canTax = salesOrder.new CanTax();
salesOrder.process(canTax, 2, 15);
}
}
TaskController类的f1方法创建SalesOrder类的对象和成员内部类的对象USTax和CanTax,最后SalesOrder对象调用process方法,入参传入成员内部类的对象USTax和CanTax
测试结果是:
2.5 模板方法模式(Template Method)
模板方法模式是定义算法的骨架,将一些步骤推迟到子类中实现,父类定义多个抽象方法,子类实现这些抽象方法,父类还定义了一个普通方法,这个普通方法封装了这些抽象方法
需求:不同数据库连接数据库并且查询数据库的数据
分析:对每个数据库都创建一个方法,重复代码多
优化思路:创建一个抽象类,封装了连接数据库的抽象方法和查询数据库的抽象方法,还封装了一个普通方法,这个普通方法封装了这些抽象方法,每个数据库都创建一个实现类,重写连接数据库的抽象方法和查询数据库的抽象方法
程序:
public class QueryDBFactory {
public void doQuery(QueryDBTemplate queryDBTemplate) {
queryDBTemplate.doQuery();
}
abstract public class QueryDBTemplate {
abstract public void getConnection();
abstract public void selectFromDB();
public void doQuery() {
getConnection();
selectFromDB();
}
}
public class MysqlDB extends QueryDBTemplate {
@Override
public void getConnection() {
System.out.println("MysqlDB获取连接");
}
@Override
public void selectFromDB() {
System.out.println("MysqlDB查询数据库的数据");
}
}
public class OracleDB extends QueryDBTemplate {
@Override
public void getConnection() {
System.out.println("OracleDB获取连接");
}
@Override
public void selectFromDB() {
System.out.println("OracleDB查询数据库的数据");
}
}
}
定义一个抽象类QueryDBTemplate,定义抽象方法getConnection和selectFromDB,抽象类中还定义了一个普通方法doQuery,doQuery方法封装了这两个抽象方法,可以减少重复代码
创建两个子类分别表示不同的数据库,每个子类都重写这两个抽象方法
public class TaskController {
@Test
public void f3() {
QueryDBFactory queryDBFactory = new QueryDBFactory();
QueryDBFactory.QueryDBTemplate queryDBTemplate = queryDBFactory.new MysqlDB();
queryDBFactory.doQuery(queryDBTemplate);
System.out.println("========================================");
queryDBTemplate = queryDBFactory.new OracleDB();
queryDBFactory.doQuery(queryDBTemplate);
}
}
分别采用Mysql和Oracle的子类查询数据库
测试结果:
模板模式在抽象类中定义普通方法是每个数据库都先获取连接然后查询数据库的数据
2.6 命令模式(Command)
命令模式是将请求和执行解耦合,使得请求不知道执行的具体实现,用于封装操作并延迟执行这些操作,思路是创建一个命令接口,创建多个命令类实现这个接口,命令类中组合了请求对象
程序:
public class CommandFactory {
public interface Command {
void execute();
}
public class Person {
public void work() {
System.out.println("工作");
}
public void sport() {
System.out.println("运动");
}
public void sleep() {
System.out.println("睡觉");
}
}
public class WorkCommand implements Command {
private Person person;
public WorkCommand(Person person) {
this.person = person;
}
@Override
public void execute() {
person.work();
}
}
public class SportCommand implements Command {
private Person person;
public SportCommand(Person person) {
this.person = person;
}
@Override
public void execute() {
person.sport();
}
}
public class SleepCommand implements Command {
private Person person;
public SleepCommand(Person person) {
this.person = person;
}
@Override
public void execute() {
person.sleep();
}
}
}
创建一个命令接口Command,创建多个实现类作为命令,组合了Person对象
@Test
public void f13() {
CommandFactory commandFactory = new CommandFactory();
CommandFactory.Person person = commandFactory.new Person();
CommandFactory.WorkCommand workCommand = commandFactory.new WorkCommand(person);
CommandFactory.SportCommand sportCommand = commandFactory.new SportCommand(person);
CommandFactory.SleepCommand sleepCommand = commandFactory.new SleepCommand(person);
workCommand.execute();
sportCommand.execute();
sleepCommand.execute();
}
测试结果:
命令模式会创建多个对象,然后执行
2.7 迭代器模式(Iterator)
迭代器模式是顺序遍历数组的所有元素,不用关心数组的实现,可以将遍历和数组的实现解耦合
程序:
public class IteratorFactory {
public interface Iterator {
public boolean hasNext();
public Object next();
}
public class Hobbies implements Iterator {
private String[] arr;
private int index;
public Hobbies(String[] arr) {
this.arr = arr;
index = 0;
}
@Override
public boolean hasNext() {
return index < arr.length;
}
@Override
public Object next() {
return arr[index++];
}
}
}
通过迭代器遍历数组,需要先创建迭代器接口,创建一个类实现迭代器接口
@Test
public void f14() {
IteratorFactory iteratorFactory = new IteratorFactory();
String[] arr = new String[]{"a","b","c","d","e"};
IteratorFactory.Hobbies hobbies = iteratorFactory.new Hobbies(arr);
while (hobbies.hasNext()) {
System.out.println(hobbies.next());
}
}
通过迭代器对象遍历数组,可以将遍历和数组的实现解耦合
测试结果:
迭代器模式可以顺序遍历数组中的所有元素
2.8 中介者模式(Mediator)
中介者模式是用一个中介者对象封装对象之间的交互,可以降低对象之间的耦合
程序:
public class MediatorFactory {
// 中介者
public class QQ {
public void sendMsg(User sender, User receiver, String msg) {
System.out.println(sender.getName() + " send msg: " + msg + " to " + receiver.getName());
}
}
public class User {
private String name;
private QQ qq;
public User(String name, QQ qq) {
this.name = name;
this.qq = qq;
}
public String getName() {
return name;
}
// 用户通过QQ中介者发送消息
public void sendMsg(String msg, User receiver) {
qq.sendMsg(this, receiver, msg);
}
}
}
用户对象之间的交互需要通过QQ这个中介者对象进行交互,因此用户对象应该持有中介者对象QQ
@Test
public void f15() {
MediatorFactory mediatorFactory = new MediatorFactory();
MediatorFactory.QQ qq = mediatorFactory.new QQ();
MediatorFactory.User user01 = mediatorFactory.new User("user01", qq);
MediatorFactory.User user02 = mediatorFactory.new User("user02", qq);
user01.sendMsg("hello", user02);
user02.sendMsg("hi", user01);
}
用户对象之间发送消息必须通过中介者对象QQ
测试结果:
用户对象发送消息会通过中介者对象QQ
2.9 备忘录模式(Memento)
备忘录模式是在破坏对象封装性的前提下恢复状态,思路是创建备忘录对象来保存对象状态
程序:
public class MementoFactory {
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
public class Activity {
private String state;
public String getState() {
return state;
}
public Memento begin() {
state = "开始";
return new Memento(state);
}
public Memento resume() {
state = "暂停";
return new Memento(state);
}
public Memento end() {
state = "结束";
return new Memento(state);
}
public void restore(Memento memento) {
state = memento.getState();
}
}
}
创建备忘录类Memento,Activity活动类在修改活动状态的时候会返回备忘录对象,通过restore方法可以恢复活动对象的状态
@Test
public void f16() {
MementoFactory mementoFactory = new MementoFactory();
MementoFactory.Activity activity = mementoFactory.new Activity();
MementoFactory.Memento beginMemento = activity.begin();
MementoFactory.Memento resumeMemento = activity.resume();
MementoFactory.Memento endMemento = activity.end();
activity.restore(endMemento);
System.out.println(activity.getState());
activity.restore(resumeMemento);
System.out.println(activity.getState());
activity.restore(beginMemento);
System.out.println(activity.getState());
}
创建活动对象,创建备忘录对象,通过restore方法可以恢复活动状态
测试结果:
备忘录模式可以恢复活动状态
2.10 解释器模式(Interpreter)
解释器模式是解释执行语言的表达式,会将每个表达式抽象成一个类,通过组合表达式可以构建复杂的表达式
程序:
public class InterpreterFactory {
public interface Interpreter {
int interpreter();
}
public class NumberInterpreter implements Interpreter {
private int num;
public NumberInterpreter(int num) {
this.num = num;
}
@Override
public int interpreter() {
return num;
}
}
public class AddInterpreter implements Interpreter {
private Interpreter number1;
private Interpreter number2;
public AddInterpreter(Interpreter number1, Interpreter number2) {
this.number1 = number1;
this.number2 = number2;
}
@Override
public int interpreter() {
return number1.interpreter() + number2.interpreter();
}
}
}
解释器模式会定义一个解释器接口,定义数字解释器和相加操作的解释器,重写interpreter方法
@Test
public void f17() {
InterpreterFactory interpreterFactory = new InterpreterFactory();
InterpreterFactory.NumberInterpreter number1 = interpreterFactory.new NumberInterpreter(3);
InterpreterFactory.NumberInterpreter number2 = interpreterFactory.new NumberInterpreter(6);
InterpreterFactory.AddInterpreter addInterpreter = interpreterFactory.new AddInterpreter(number1, number2);
System.out.println(addInterpreter.interpreter());
}
定义两个数字解释器和一个相加操作的解释器,计算相加操作的结果
测试结果:
解释器模式可以计算多个表达式得到结果
2.11 访问者模式(Visitor)
访问者模式是在不改变元素类的前提下,定义这些元素的操作,将数据结构和操作分离开,使得操作可以独立的变化
程序:
public class VisitorFactory {
public interface Visitor {
abstract public void visitElement1(Element1 element1);
abstract public void visitElement2(Element2 element2);
}
public class RealVisitor implements Visitor {
@Override
public void visitElement1(Element1 element1) {
element1.log1();
}
@Override
public void visitElement2(Element2 element2) {
element2.log2();
}
}
public interface Element {
void accept(Visitor visitor);
}
public class Element1 implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElement1(this);
}
public void log1() {
System.out.println("打印元素1的日志");
}
}
public class Element2 implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElement2(this);
}
public void log2() {
System.out.println("打印元素2的日志");
}
}
}
定义元素,定义访问者接口和实现类
@Test
public void f20() {
VisitorFactory visitorFactory = new VisitorFactory();
VisitorFactory.Element1 element1 = visitorFactory.new Element1();
VisitorFactory.Element2 element2 = visitorFactory.new Element2();
VisitorFactory.RealVisitor realVisitor = visitorFactory.new RealVisitor();
element1.accept(realVisitor);
element2.accept(realVisitor);
}
调用元素的accept方法,可以调用访问者visitor的方法
测试结果:
3、七大设计原则
设计模式的七大原则是
(1)单一职责原则:一个类只负责一个职责
(2)开闭原则:对扩展开放,对修改关闭
(3)里氏替换原则:子类可以替换父类的方法保证程序运行正常,子类可以实现父类没有的方法完成新增功能,但是子类不应该重写父类的方法
假设有一个父类 Bird 和一个子类 Penguin。假设 Bird 类有一个方法 fly(),表示鸟类能够飞行。如果我们按照里氏替换原则,Penguin 类(企鹅)继承自 Bird,但企鹅是不会飞的
如果我们在 Penguin 类中实现了 fly() 方法,且它要么做得不如 Bird 类中的 fly() 方法好(即不能飞),要么导致错误或异常(例如抛出不适当的异常),则违反了里氏替换原则
正确的做法是将 fly() 方法移除或使其在 Penguin 中不适用,或者将 Penguin 类从 Bird 类中移除,因为企鹅并不符合“可以飞”的行为约定。在这种情况下,我们可能会创建一个 FlyingBird 类来继承自 Bird,而 Penguin 类则从 Bird 类中移除,确保只有那些确实可以飞的鸟类继承 FlyingBird
(4)依赖反转原则:抽象不要依赖细节,细节应该依赖抽象,这里抽象指的是接口和抽象类
(5)接口隔离原则:尽可能将一个接口拆分成多个小接口,客户端不应该依赖它不需要的接口
(6)合成复用原则:尽量使用组合替换继承
(7)迪米特法则:最少知识原则,一个类应该保持对另一个类最小的了解