前言
现代社会,技术日新月异,要想跟上技术的更新就必须不断学习,而学习技术最有效方式就是阅读优秀的源码,而优秀的源码都不是简单的逻辑堆积,而是有很灵活的设计模式应用其中,如果我们不懂设计模式,那么就难以读懂优秀的源码,不能读懂源码,就只能停留在低级的技术应用层面,可以说不可能晋级为一名工程师。
而设计模式说到底是一种思维学习,而思维的学习是最困难的,如果你不能接受一种思维,那么基于这种思维实现的功能和逻辑,你怎么看,怎么都觉得不对劲,怎么都觉得无法理解。那么想要拥有一种思维,最简单的方式就是反复“背诵”,反复理解,反复使用,本文就是提供一篇便于你理解,便于你反复“背诵”的文章,文本将常见设计模式最核心的精华以及思想通过清晰的UML类图和代码示例展现出来,只要你能反复理解,反复“背诵”,尝试使用,相信你一定能够彻底将这种思维占为己有,在编写代码时,能够无意识的灵活运用各种设计模式,编写优秀的代码。
这篇文章是我对《设计模式之禅》学习后,基于个人理解所总结的常用设计模式的核心知识点,如果时间充足,想学习更详细的内容可以去学习作者原著。
学习本篇文章之前有两点建议:
- 如果对UML类图不清晰的,强烈建议先学习我的这篇关于UML的文章 快速读懂UML类图,搞懂类之间的6大关系,轻松绘制UML类图,否则,将很难理解本文。
- 建议结合每种设计模式的示例代码进行学习,各种设计模式的案例代码已上传至Gitee仓库,链接如下:https://gitee.com/mekeater/design-pattern
设计模式是一种思维,绝不是一蹴而就的,本文只能尽可能的帮助博友理解各种设计模式的思想,如果想达到灵活应用,还需要反复回顾,反复理解,反复应用,我自己也是常常回来阅读的。
一、工厂方法模式
1. 定义
只需要定义一个具体工厂类,类中包含创建对象的方法即可,适用于只创建同一种等级的对象,通用类图如下:
2. 案例
女娲造不同肤色的人类,采用工厂方法模式的类图如下:
以上类图核心实现代码:
public class HumanFactory extends AbstractHumanFactory{@Overridepublic <T extends IHuman> T createHuman(Class<T> c) {IHuman human=null;try {human = (T)Class.forName(c.getName()).newInstance();}catch (Exception e){System.out.printf(e.toString());}return (T) human;}
}
3. 扩展
3.1 静态工厂模式
其实对于女娲造不同肤色的人,不需要抽象工厂类,直接在具体的工厂类中放一个创建不同肤色人类的静态方法即可,而这种在工厂类中通过静态方法创建对象的模式,就称之为静态工厂模式。
女娲造不同肤色的人类,采用静态工厂模式的类图如下:
3.2 通过工厂方法模式代替单例模式
类图如下:
类图中核心实现代码如下:
public class SingletonFactory {private static Singleton singleton;static {try {Class<?> c1 = Class.forName(Singleton.class.getName());Constructor<?> constructor = c1.getDeclaredConstructor();constructor.setAccessible(true);singleton = (Singleton) constructor.newInstance();} catch (Exception e){}}public Singleton getSingleton(){return singleton;}
}
3.3 工厂方法模式升级为抽象工厂模式
先预热一下抽象工厂模式,将女娲造不同肤色的人的一个工厂,抽象为多个工厂,类图如下:
二、抽象工厂模式
1. 定义
抽象工厂模式是工厂方法模式的升级,适用于创建包含多个不同等级对象,且每个对象又有多个产品族组成,这种对象的创建。
如汽车有奥迪,宝马等不同的品牌,而每个汽车又包含左车门和右车门等产品族。那么这种汽车对象就适合用抽象工厂模式。
抽象工厂模式有多个具体的工厂实现类。
抽象工厂模式通用类图如下:
2. 案例
女娲要造不同肤色,不同性别的人类,采用抽象工厂模式的类图如下:
工厂代码如下:
public class FemaleFactory implements IHumanFactory{@Overridepublic IHuman createBlackHuman() {return new FemaleBlackHuman();}@Overridepublic IHuman createYellowHuman() {return new FemaleYellowHuman();}@Overridepublic IHuman createWhiteHuman() {return new FemaleWhiteHuman();}
}public class MaleFactory implements IHumanFactory{@Overridepublic IHuman createBlackHuman() {return new MaleBlackHuman();}@Overridepublic IHuman createYellowHuman() {return new MaleYellowHuman();}@Overridepublic IHuman createWhiteHuman() {return new MaleWhiteHuman();}
}
三、模板方法模式
1. 定义
所谓模板方法模式,就是在抽象父类中就定义一个模板方法,该方法控制其它方法的流程,而其它方法由子类实现。适用于对象要按照一定的规则和顺序调用基本方法。
抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。
模板方法模式通用类图如下:
2. 案例
构建不同类型的奥迪车,基于模板方法模式进行设计的类图如下:
核心实现代码如下:
public abstract class AbstractAoDiCar {protected abstract void start();protected abstract void stop();protected abstract void alarm();protected abstract void enginBoom();//模板方法public void run(){this.start();this.enginBoom();this.alarm();this.stop();}
}
3. 扩展(钩子方法)
就是子类对一个方法的操作,会导致父类中模板方法的执行受到影响,这样的方法就称为钩子方法。
如我们要让AoDiCarModel1能够按喇叭,而AoDiCarModel2不能按喇叭,那该如何做呢?这个时候我们只需要添加一个钩子方法即可,类图如下:
public abstract class AbstractAoDiCar {protected abstract void start();protected abstract void stop();protected abstract void alarm();protected abstract void enginBoom();protected boolean isAlarm(){return true;}//模板方法public void run(){this.start();this.enginBoom();if (isAlarm()){this.alarm();}this.stop();}
}public class AoDiCarModel1 extends AbstractAoDiCar{private boolean alarmFlag = true; //要响喇叭@Overrideprotected void start() {System.out.println("奥迪model1启动");}@Overrideprotected void stop() {System.out.println("奥迪model1停止");}@Overrideprotected void alarm() {System.out.println("奥迪model1喇叭响");}@Overrideprotected void enginBoom() {System.out.println("奥迪model1引擎发动");}@Overrideprotected boolean isAlarm() {return this.alarmFlag;}public void setAlarm(boolean isAlarm)//钩子方法{this.alarmFlag=isAlarm;}
}
四、建造者模式
1. 定义
建造者模式是将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。适用于相同的方法,不同的执行顺序,产生不同的事件结果,这种对象的构建。
建造者模式通用类图如下:
Product采用模板方法模式构建,然后重点将模板方法通过构建者进行控制,同时通过Director类避免高层模块深入到建造者内部的实现类。
2. 案例
构建不同启动操作和顺序的宝马以及奔驰汽车对象。
核心代码如下:
public abstract class AbsCarModel {protected ArrayList<String> sequence;protected abstract void start();protected abstract void stop();protected abstract void alarm();protected abstract void enginBoom();final public void run() {for (String s : sequence) {if (s.equals("start")) {start();} else if (s.equals("stop")) {stop();} else if (s.equals("alarm")) {alarm();} else if (s.equals("enginBoom")) {enginBoom();}}}final public void setSequence(ArrayList<String> sequence){this.sequence=sequence;}
}public class BMBuilder extends AbsCarBuilder{BMModel bmModel=new BMModel();@Overridepublic void setSequence(ArrayList sequence) {this.bmModel.sequence=sequence;}@Overridepublic AbsCarModel getCarModel() {return this.bmModel;}
}
public class Director {private ArrayList<String> sequence=new ArrayList<>();private BMBuilder bmBuilder=new BMBuilder();private BenzBuilder benzBuilder=new BenzBuilder();public AbsCarModel getABenzModel(){this.sequence.clear();//奔驰A型操作步骤this.sequence.add("start");this.sequence.add("stop");benzBuilder.setSequence(this.sequence);return benzBuilder.getCarModel();}public AbsCarModel getBBenzModel(){this.sequence.clear();//奔驰B型操作步骤this.sequence.add("enginBoom");this.sequence.add("start");this.sequence.add("stop");benzBuilder.setSequence(this.sequence);return benzBuilder.getCarModel();}public AbsCarModel getABMModel(){this.sequence.clear();//宝马A型操作步骤this.sequence.add("alarm");this.sequence.add("start");this.sequence.add("stop");bmBuilder.setSequence(this.sequence);return bmBuilder.getCarModel();}public AbsCarModel getBBMModel(){this.sequence.clear();//宝马B型操作步骤this.sequence.add("start");bmBuilder.setSequence(this.sequence);return bmBuilder.getCarModel();}
}public class Client {public static void main(String[] args) {Director director = new Director();//宝马A型车director.getABMModel().run();//奔驰B型车director.getBBenzModel().run();}
}
Note: 建造者模式和工厂方法模式的区别
建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同;而工厂方法模式则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的。
五、代理模式
1. 定义
为其它对象提供一种代理,以控制对这个对象的访问。适用于对象本身使用之前或者之后有很多细碎的需要要处理,而这些细碎的需要交给代理去做,就可以保证对象本身只处理实际的核心业务,不用关心其它非本职责的任务。代理模式通用UML类图如下所示:
2. 案例
如你一个游戏用户,不要自己打怪升级(那太累了),想直接拿到高级别的ID进行高端游戏,该用户就可以采用代理模式,找一个代理用户帮自己升级,UML类图如下:
核心代码如下:
//游戏代理用户
public class GamePlayerProxy implements IGamePlayer{//被代理用户private IGamePlayer gamePlayer;public GamePlayerProxy(IGamePlayer gamePlayer){this.gamePlayer=gamePlayer;}@Overridepublic void login(String user, String psw) {this.gamePlayer.login(user,psw);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.upgrade();}
}
3. 扩展
3.1 普通代理
普通代理就是我们要知道代理的存在,只有通过代理才能执行被代理对象的方法,它要求客户端只能访问代理角色,而不能访问真实角色。
采用普通代理对案例进行设计,UML类图如下,只是修改了真实角色和代理角色的构造函数,这样就使得调用者只知道代理的存在,但不用知道代理的真正角色是谁。
核心实现代码如下:
//游戏用户
public class GamePlayer implements IGamePlayer {private String name;public GamePlayer(IGamePlayer proxy, String name) throws Exception {if (proxy == null) {throw new Exception("未传入代理对象,不能创建真实角色");} else {this.name = name;}}@Overridepublic void login(String user, String psw) {System.out.println("登录名:" + user + "密码:" + psw + " 登录成功");}@Overridepublic void killBoss() {System.out.println(this.name + "在打怪");}@Overridepublic void upgrade() {System.out.println(this.name + "升级了");}
}//游戏代理用户
public class GamePlayerProxy implements 代理模式.IGamePlayer {//被代理用户private IGamePlayer gamePlayer;public GamePlayerProxy(String name) throws Exception {this.gamePlayer = new GamePlayer(this, name);}@Overridepublic void login(String user, String psw) {this.gamePlayer.login(user, psw);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.upgrade();}
}public class Client {public static void main(String[] args) throws Exception {//调用者只知道代理的存在,不用知道代理的真实对象是谁GamePlayerProxy gamePlayerProxy = new GamePlayerProxy("张三");gamePlayerProxy.login("zhangsan","123");gamePlayerProxy.killBoss();gamePlayerProxy.upgrade();}
}
3.2 强制代理
强制代理是调用者直接调用真实角色,而不必知道代理的存在,因为真实角色管理代理角色。这就类似你看到一个明星,想问她的一些事情,但她却让你找她的助理,这里面明星就是真实角色,而助理就是代理角色,你虽然直接看到明星,但得到的结果却是助理。
采用强制代理对案例进行设计的UML类图如下:
核心实现代码如下:
//游戏用户
public class GamePlayer implements IGamePlayer {private IGamePlayer proxy = null;private String name;public GamePlayer(String name) {this.name = name;}@Overridepublic void login(String user, String psw) {if (isProxy()) {System.out.println("登录名:" + user + " 密码:" + psw + " 登录成功");} else {System.out.println("请使用指定的代理访问");}}@Overridepublic void killBoss() {if (isProxy()) {System.out.println(this.name + "在打怪");} else {System.out.println("请使用指定的代理访问");}}@Overridepublic void upgrade() {if (isProxy()) {System.out.println(this.name + "升级了");} else {System.out.println("请使用指定的代理访问");}}@Overridepublic IGamePlayer getProxy() {this.proxy = new GamePlayerProxy(this);return this.proxy;}private boolean isProxy() {if (this.proxy == null) {return false;} else {return true;}}
}//游戏代理用户
public class GamePlayerProxy implements IGamePlayer {//被代理用户private IGamePlayer gamePlayer;public GamePlayerProxy(IGamePlayer gamePlayer){this.gamePlayer=gamePlayer;}@Overridepublic void login(String user, String psw) {this.gamePlayer.login(user,psw);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.upgrade();}//代理的代理暂时还没有,就是自己@Overridepublic IGamePlayer getProxy() {return this;}
}
客户端使用强制代理
public class Client {public static void main(String[] args) throws Exception {//真实角色自己操作,不行!GamePlayer gamePlayer=new GamePlayer("张三");gamePlayer.login("zhangsan","123");gamePlayer.killBoss();gamePlayer.upgrade();System.out.println("===================================");//创建一个代理,操作真实角色,也不行!GamePlayerProxy proxy = new GamePlayerProxy(gamePlayer);proxy.login("zhangsan","123");proxy.killBoss();proxy.upgrade();System.out.println("===================================");//必须由真实角色获得代理对象,才能对真实角色进行操作IGamePlayer proxy1 = gamePlayer.getProxy();proxy1.login("zhangsan","123");proxy1.killBoss();proxy1.upgrade();}
}
3.3 代理的真正意义
如果代理仅仅是调用真实角色的方法,那直接用真实角色即可,再加个代理有啥用?因为代理不仅仅会调用真实角色的方法,往往需要自己先/后处理一些琐碎的事务之后才能调用真实角色的方法。
也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。
如游戏代打案例,代理帮你升级,肯定不会免费吧,一定会收费的,因此,正常一点的游戏代打UML类图如下所示:
核心代码如下:
//游戏代理用户
public class GamePlayerProxy implements IGamePlayer, IProxy {//被代理用户private IGamePlayer gamePlayer;public GamePlayerProxy(IGamePlayer gamePlayer){this.gamePlayer=gamePlayer;}@Overridepublic void login(String user, String psw) {this.gamePlayer.login(user,psw);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.upgrade();this.pay();}@Overridepublic void pay() {System.out.println("代理费100元");}
}
3.4 动态代理
动态代理核心技术是反射,因此在理解这种设计模式之前,强烈建议先看下我的这篇关于“反射”的文章 彻底玩转Java注解和反射。
动态代理是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。现在流行的面向横切编程(AOP)其核心就是采用了动态代理机制。
基于java.lang.reflect包下的InvocationHandler接口和Proxy类,采用动态代理模式对案例进行实现的UML类图如下:
核心实现代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class GamePlayerIH implements InvocationHandler {//被代理的实例Object obj = null;GamePlayerIH(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//通过反射调用被代理对象方法Object result = method.invoke(this.obj, args);return result;}
}public class Client {public static void main(String[] args) {IGamePlayer gamePlayer=new GamePlayer("张三");GamePlayerIH gamePlayerIH=new GamePlayerIH(gamePlayer);ClassLoader classLoader = gamePlayer.getClass().getClassLoader();//动态产生一个代理者(宣称所有接口方法都已通过InvocationHandle实现)IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, gamePlayer.getClass().getInterfaces(), gamePlayerIH);proxy.login("zhangsan","123");proxy.killBoss();proxy.upgrade();}
}
如果我们想在调用代理对象的某个方法之前或者之后,通知一条消息,或者执行什么操作,该如何做呢?很简单!例如我们想要在执行login方法的时候发出一条消息,只需要对GamePlayerIH 做如下修改即可,这种通知方式就是AOP思想核心。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class GamePlayerIH implements InvocationHandler {//被代理的实例Object obj = null;GamePlayerIH(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//通过反射调用被代理对象方法Object result = method.invoke(this.obj, args);//AOP横切(在程序运行中间)添加一个通知消息if (method.getName().equals("login")) {System.out.println("通知:有人用你的账号登录了游戏");}return result;}
}
动态代理通用UML类图如下,DynamicProxy有两条线,一条线去操作代理对象的任务,另一条线是处理自己的通知。并且DynamicProxy类只是对Proxy.newProxyInstance方法的再封装而已,让它形式上有一个代理类,同时可以将自己的通知封装进去。
核心实现代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class DynamicProxy<T> {public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){//寻找JoinPoint连接点,发出自己的通知new BeforeAdvice().exec();//生成动态代理return (T)Proxy.newProxyInstance(loader,interfaces,h);}
}
静态代理和动态代理有什么区别?
都是需要实现一个代理类,有区别,注意看父类,动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。
六、原型模式
1. 定义
原型模式就是先产生一个包含大量共有信息的类,然后通过实现Object的clone方法拷贝出副本,然后对副本修改部分细节信息,就构建了一个完整的个性对象。
简单来说就是一个类实现Cloneable标记接口,然后实现Object的clone方法,然后就可以通过clone复制出另一个对象。Object类的clone方法的原理是从内存中(具体地说就是堆内存) 以二进制流的方式进行拷贝, 重新分配一个内存块。
原型模式通用UML类图如下:
核心代码如下:
public class ConcretePrototype implements Cloneable{@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
2. 案例
如一份广告邮件内容基本相同,我们可以就创建一个对象,然后每次拷贝一份对象,对其细节修改后就是一个全新的对象,然后发出去。UML类图如下:
核心实现代码如下:
public class Mail implements Cloneable{private String receiver;private String subject;private String appellation;private String context;private String tail;public Mail(AdvTemplate advTemplate){this.subject=advTemplate.getAdvSubject();this.context=advTemplate.getAdvContext();}@Overrideprotected Object clone() {try {return super.clone();} catch (CloneNotSupportedException e) {throw new RuntimeException(e);}}
}
注意:
- 拷贝有深拷贝和浅拷贝两种方式,具体区别以及实现方法可以参考我的这篇文章
- 若对象被final修饰,则不能clone
七、中介者模式
1. 定义
用一个中介对象封装一系列相互关联对象的交互,中介者使得各个对象直接不需要显示的相互作用,降低各个对象直接的耦合性。适用于有一大堆相互关联的对象,此时构造一个中介者来处理各个对象直接复杂的交互,每个对象只需要通过中介者进行相应的操作即可。
类似在公司中,有大量的员工,每个员工与大量的同事之间在业务上都有一定的相互联系,这时候就需要一个中介者来统一处理各个同事之间的业务联系。
中介者模式通用UML类图如下。ConcreteMediator是具体的中介者,Colleague1,Colleague2,…就是具有相互联系各个同事。
2. 案例
如我们需要设计一个软件管理公司对电脑的采购,销售,库存,最直接的设计UML类图如下:
核心实现代码如下:
public class Purchase {public void buyComputer(int num){Stock stock = new Stock();Sale sale = new Sale();int saleScore = sale.getSaleScore();if (saleScore>80){System.out.println("销售良好,采购电脑:"+num+"台");stock.increase(num);}else {System.out.println("销售不好,采购电脑:"+num/2+"台");stock.increase(num/2);}}public void refuseBuy(){System.out.println("拒绝采购!");}
}public class Sale {public void sellComputer(int num){Stock stock = new Stock();Purchase purchase = new Purchase();if (stock.getStockNum()<num){System.out.println("库存不足,采购"+num+"台");purchase.buyComputer(num);stock.increase(num);}System.out.println("销售电脑:"+num+"台");stock.decrease(num);}public int getSaleScore(){Random random = new Random(System.currentTimeMillis());int saleScore = random.nextInt(100);System.out.println("销售情况得分:"+saleScore);return saleScore;}public void discountSale(){Stock stock = new Stock();System.out.println("打折销售,清空库存:"+stock.getStockNum());stock.decrease(stock.getStockNum());}
}public class Stock {private static int COMPUTER_NUMBER=100;//初始100台public void increase(int num){COMPUTER_NUMBER+=num;System.out.println("当前库存:"+COMPUTER_NUMBER);}public void decrease(int num){COMPUTER_NUMBER-=num;System.out.println("当前库存:"+COMPUTER_NUMBER);}public int getStockNum(){return COMPUTER_NUMBER;}public void clearStock(){Purchase purchase = new Purchase();Sale sale = new Sale();System.out.println("库存压力大,需要清库数量:"+COMPUTER_NUMBER);sale.discountSale();purchase.refuseBuy();}
}
三个类之间相互交互,内部会有复杂的交互逻辑,各个类的耦合性很高,简单的三个对象还好,但如果更多呢?比如下图,你还敢按照上面的写吗?或者你真的写了,估计写不到一半自己就想si了。
对于这么一堆有相互关联的对象就适用中介者模式来设计,对于本案例采用中介者模式设计的UML类图如下:
实现代码如下:
public abstract class AbsMediator {protected Sale sale;protected Purchase purchase;protected Stock stock;public AbsMediator(){sale=new Sale(this);purchase=new Purchase(this);stock=new Stock(this);}public abstract void execute(String tag, Object... objects);}public class Mediator extends AbsMediator{@Overridepublic void execute(String tag, Object... objects) {if (tag.equals("0"))//采购{this.buyComputer((Integer) objects[0]);} else if (tag.equals("1"))//销售{this.sellComputer((Integer) objects[0]);} else if (tag.equals("2"))//打折销售{this.discountSale();} else if (tag.equals("3"))//清仓{this.clearStock();}}private void buyComputer(int num){int saleScore = super.sale.getSaleScore();if (saleScore>80){System.out.println("销售良好,采购电脑:"+num+"台");super.stock.increase(num);}else {System.out.println("销售不好,采购电脑:"+num/2+"台");super.stock.increase(num/2);}}private void sellComputer(int num){if (super.stock.getStockNum()<num){System.out.println("库存不足,采购"+num+"台");super.purchase.buyComputer(num);super.stock.increase(num);}System.out.println("销售电脑:"+num+"台");super.stock.decrease(num);}private void discountSale(){System.out.println("打折销售,清空库存:"+super.stock.getStockNum());super.stock.decrease(super.stock.getStockNum());}private void clearStock(){System.out.println("库存压力大,需要清库数量:"+super.stock.getStockNum());super.sale.discountSale();super.purchase.refuseBuy();}
}public class Purchase extends AbsColleague{public Purchase(AbsMediator mediator) {super(mediator);}public void buyComputer(int num){super.mediator.execute("0",num);}public void refuseBuy(){System.out.println("拒绝采购!");}
}public class Sale extends AbsColleague{public Sale(AbsMediator mediator) {super(mediator);}public void sellComputer(int num){super.mediator.execute("1",num);}public int getSaleScore(){Random random = new Random(System.currentTimeMillis());int saleScore = random.nextInt(100);System.out.println("销售情况得分:"+saleScore);return saleScore;}public void discountSale(){super.mediator.execute("2");}
}public class Stock extends AbsColleague{private static int COMPUTER_NUMBER=100;//初始100台public Stock(AbsMediator mediator) {super(mediator);}public void increase(int num){COMPUTER_NUMBER+=num;System.out.println("当前库存:"+COMPUTER_NUMBER);}public void decrease(int num){COMPUTER_NUMBER-=num;System.out.println("当前库存:"+COMPUTER_NUMBER);}public int getStockNum(){return COMPUTER_NUMBER;}public void clearStock(){super.mediator.execute("3");}
}
note:
- 中介者模式的缺点很明显,那就是中介者会膨胀的很大,而且逻辑很复杂,同事类越多,逻辑越复杂。
- 面向对象的编程中,对象之间必然有联系,但并不是只要有联系就加入中介者,因为本来简单的对象之间的联系,不必用中介者模式,但你非要用,就会导致逻辑很复杂,所以只有大量对象之间具有高度耦合的情况下,再使用中介者模式。
八、命令模式
1. 定义
命令模式是将一个任务请求对应封装为一个命令对象,并增加一个调用者来执行来接收和执行命令,适用于任何带有命令情景的任务。具体来说适用于一个任务需要命令很多的对象去处理。命令模式通用UML类图如下:封装一系列具体的命令对象ConcreteCommand,然后由Invoker调用,客户端只和Invoker交互。
2. 案例
如客户要求我们为其研发一个内部员工管理系统,针对该任务,我们安排了需求组,美工组,代码组,三个小组合作完成该任务。
如果不采用命令模式,UML类图如下,客户端需要与各个组进行交互。
采用命令模式设置的UML类图如下:
核心实现代码如下:
public abstract class AbsCommand {protected RequirementGroup rg=new RequirementGroup();protected PageGroup pg=new PageGroup();protected CodeGroup cg=new CodeGroup();public abstract void execute();
}public class AddRequirementCommand extends AbsCommand{@Overridepublic void execute() {super.rg.find();super.rg.add();super.rg.plan();}
}public class Invoker {private AbsCommand command;public void setCommand(AbsCommand command){this.command=command;}public void action(){this.command.execute();}
}public class Client {public static void main(String[] args) {//定义接头人Invoker invoker = new Invoker();//封装命令AddRequirementCommand addRequirementCommand = new AddRequirementCommand();//接收命令invoker.setCommand(addRequirementCommand);//执行命令invoker.action();}
}
命令模式的优点是可以很好使得类之间进行解耦,缺点也很明显,有多少命令就有多少具体的命令对象,子类数量将膨胀的非常大。
note:中介者模式中的中介者是处理大量有依赖关系对象之间的操作,而命令模式中的Invoker是接收封装的命令。
九、责任链模式
1. 定义
责任链模式适用于对于一个请求,有一串对象可能去处理它,即如果当前对象不能处理(承担这个处理这个请求的责任),则转给下一个对象去处理,循环往复,直至有对象处理为止。责任链的实现本质就是自定义单向链表类,核心就是定义一个next。
责任链模式通用UML类图如下:
2. 案例
古代对女性要求三从四德,三从指的是一个女子若未出嫁,则要听从父亲的话,若出嫁,则要听从丈夫的话,若夫亡,则要听从儿子的话。这个场景中有三个对象可能处理女子的请求,完全适合用责任链模式来设计,UML类图如下:
核心实现代码如下:
public interface IWomen {int getType();String getRequest();
}public abstract class AbsHandle {public final static int FATHER_TYPE_REQUEST=1;public final static int HUSBAND_TYPE_REQUEST=2;public final static int SON_TYPE_REQUEST=3;private int type;private AbsHandle nextHandle;public AbsHandle(int type){this.type=type;}public final void HandleMessage(IWomen women){if (women.getType()==this.type){this.response(women);}else{if (this.nextHandle !=null){this.nextHandle.HandleMessage(women);}else {System.out.println("无人负责,故无所约束,你决定\n");}}}public void setNext(AbsHandle handle){this.nextHandle =handle;}protected abstract void response(IWomen women);
}public class Father extends AbsHandle{public Father() {super(FATHER_TYPE_REQUEST);}@Overrideprotected void response(IWomen women) {System.out.println("女儿向父亲请示");System.out.println(women.getRequest());System.out.println("父亲答复:同意\n");}
}public class Client {public static void main(String[] args) {Random random = new Random();List<IWomen> womenList=new ArrayList<>();for (int i = 0; i <5; i++) {womenList.add(new Women(random.nextInt(4),"我要出去逛逛"));}AbsHandle father=new Father();AbsHandle husband=new Husband();AbsHandle son=new Son();father.setNext(husband);husband.setNext(son);for (IWomen iWomen : womenList) {father.HandleMessage(iWomen);}}
}
十、观察者模式
1. 定义
观察者模式是定义对象之间一种一对多的依赖关系,使得每当一个对象(被观察者Observable)改变状态,则所有依赖于它的对象(观察者Observer)都会得到通知并被自动更新。观察者模式的核心实现是将观察者安插到被观察者内部,被观察者一有动静就会通知给观察者。观察者模式的通用UML类图如下:
2. 案例
所谓知己知彼百战百胜,古代如何知道敌人的信息呢?你想的没错,通过安排间谍,那么通过观察者模式实现该方案,敌方就是被观察者,而间谍就是观察者,如李斯,王斯…想要观察韩非子的一举一动,基于观察者模式UML类图如下:
核心实现代码如下:
public class HanFeiZi implements IHanFeiZi,IObservable{private List<IObserver> observers=new ArrayList<>();@Overridepublic void haveBreakfast() {System.out.println("韩非子:老子要吃饭了");this.notifyObserver("韩非子在吃饭");}@Overridepublic void haveFun() {System.out.println("韩非子:老子要玩耍了");this.notifyObserver("韩非子在玩耍");}@Overridepublic void addObserver(IObserver observer) {observers.add(observer);}@Overridepublic void deleteObserver(IObserver observer) {observers.remove(observer);}@Overridepublic void notifyObserver(String context) {for (IObserver observer : observers) {observer.update(context);}}
}public class LiSi implements IObserver{@Overridepublic void update(String context) {System.out.println("李斯:观察到韩非子有活动");System.out.println("李斯:秦老板,"+context);}
}public class Client {public static void main(String[] args) {HanFeiZi hanFeiZi = new HanFeiZi();LiSi liSi = new LiSi();WangSi wangSi = new WangSi();hanFeiZi.addObserver(liSi);hanFeiZi.addObserver(wangSi);hanFeiZi.haveFun();}
}
Note:
Java本身提供了观察者模式的被观察者模板对象java.util.Observable,以及观察者的模板对象java.util.Observer,我们可以直接用子类继承,而不用自己再定义观察者和被观察者的接口类。但这种简单的观察者模式已经被取代,可以在java.util.Observable中查看具体替代的原因及新的替代者。
十一、策略模式
1. 定义
策略模式是定义一组算法,并将这些算法用高层类封装起来,使得各个算法可以互相切换。适用于多个类只有在算法或者行为上稍有不同的场景。最直观的优点,策略模式可以避免使用多重条件判断,非常容易扩展。
策略模式非常简单,可以说就是纯对面向对象的继承和多态机制的运用,通用UML类图如下:定义一个算法接口,实现一组算法,然后将算法接口聚合到高层类Context中。
2. 案例1
比如周瑜赔了夫人又折兵的故事中,诸葛亮给了周瑜三个锦囊,让他在不同的情况下依次打开,应对当时遇到的情况,这就是典型的策略问题,何种情况应该采用何种策略,采用策略模式的UML类图如下:
public interface IStrategy {void operate();
}public class Context {IStrategy strategy;public Context(IStrategy strategy){this.strategy=strategy;}void operate(){this.strategy.operate();}
}public class ZhaoYun_Client {public static void main(String[] args) {Strategy1 strategy1 = new Strategy1();Context context = new Context(strategy1);context.operate();context=new Context(new Strategy2());context.operate();context=new Context(new Strategy3());context.operate();}
}
3. 案例2
采用常规策略模式实现加减算法,UML类图如下:
核心实现代码如下:
public interface ICalculator {public int exec(int a,int b);
}public class Context {private ICalculator calculator;public Context(ICalculator calculator){this.calculator=calculator;}public int exec(int a,int b){return this.calculator.exec(a,b);}
}public class Client {public static void main(String[] args) {int exec = new Context(new Add()).exec(1, 5);System.out.println(exec);}
}
上面常规的策略模式也可以改为采用枚举实现,策略枚举方式实现的代码可读性更好,核心代码如下:
public enum Calculator {ADD("+"){@Overridepublic int exec(int a, int b) {return a+b;}},SUB("-"){@Overridepublic int exec(int a, int b) {return a-b;}};private String value;//关键点,枚举构造函数private Calculator(String _value){this.value=_value;}public String getValue() {return value;}public abstract int exec(int a,int b);
}public class Client {public static void main(String[] args) {//可以看到枚举策略的可读性很好System.out.println(Calculator.ADD.exec(1,5));System.out.println(Calculator.ADD.getValue());}
}
从上面的实现可以看出,枚举固有的特性,隐含的实现了策略模式的架构。
十二、装饰模式
1. 定义
装饰模式是动态的给一个对象添加一些额外的职责。适用于动态给一个对象附加或者撤销一些功能。装饰模式本质是继承关系的一个替代方案,因为一层层继承是静态的扩展,不具有动态性,不易维护。
note:装饰模式不要装饰太多层,因为虽然可以方便增加装饰,但装饰的层太多,就像剥洋葱,出现bug,要剥很多次层才能找到问题,增加系统的复杂度。
装饰模式通用UML类图如下:核心点在于装饰抽象类继承了抽象组件,且抽象组件又聚合到了抽象装饰类。
2. 案例
比如你的成绩单如果只是看成绩,每科分数确实不太理想,但如果加上每科的最高成绩,就会发现你的成绩与最高分其实没差多少,说明是大家考的都不好;或者再加上你的班级排名,发现排名其实还进步了呢,这样家长也许就能接受了,你就少了一顿鸡毛掸子(哈哈哈),就这个场景采用装饰模式设计的UML类图如下:
核心实现代码如下:
public abstract class ASchoolReport {public abstract void report();public abstract void sign(String name);
}public abstract class ADecorator extends ASchoolReport {private ASchoolReport schoolReport;public ADecorator(ASchoolReport schoolReport){this.schoolReport=schoolReport;}@Overridepublic void report() {this.schoolReport.report();}@Overridepublic void sign(String name) {this.schoolReport.sign(name);}
}public class HighScoreDecorator extends ADecorator{public HighScoreDecorator(ASchoolReport schoolReport) {super(schoolReport);}public void reportHighScore(){System.out.println("这次考试全班最高分:语文75,数学78,自然80");}@Overridepublic void report() {//装饰一下this.reportHighScore();super.report();}
}public class Father {public static void main(String[] args) {//原装成绩单ASchoolReport schoolReport=new YourSchoolReport();//装饰(包装)下最高分schoolReport=new HighScoreDecorator(schoolReport);//继续装饰(包装)下最排名schoolReport=new SortDecorator(schoolReport);//再把装饰好的成绩单拿给老爸看schoolReport.report();schoolReport.sign("老子");}
}
十三、适配器模式
1. 定义
适配器模式适用于后期扩展功能时,所提供的接口与系统现有接口不一致的情况,通过适配器类将一个对象转换为客户端所需要的对象。使得原本因接口不匹配而无法一起工作的两个类可以一起工作。
适配器模式根据适配器类与被适配对象的关系又分为类适配器模式和对象适配器模式。如果适配器类继承于被适配对象就能完成适配,这种模式称之为类适配器模式。但如若被适配对象不止一个的时候,由于Java只能继承一个类,因此需要通过将被适配对象聚合到适配器类中的方式实现适配,这种模式称为对象适配器模式。
类适配器模式的通用UML类图如下:
对象适配器模式的通用UML类图如下:
2. 案例
比如一个系统的人员信息接口是IUsetInfo,但是后期来了一个新业务,用的人员信息数据接口是IOuterUser,这两个接口是不一样的,因此可以采用适配器模式实现两个接口的转换,因为被适配的对象只有一个,因此采用类适配器模式即可,具体UML类图如下:
核心实现代码如下:
public interface IUserInfo {String getName();String getAge();String getAddress();String getJobPosition();
}public interface IOuterUser {Map getUserBaseInfo();Map getUerOfficeInfo();Map getUserHomeInfo();
}public class OuterAdapter extends OuterUser implements IUserInfo{@Overridepublic String getName() {String name = (String) super.getUserBaseInfo().get("name");System.out.println(name);return null;}@Overridepublic String getAge() {String age = (String) super.getUerOfficeInfo().get("age");System.out.println(age);return null;}@Overridepublic String getAddress() {String address = (String) super.getUserHomeInfo().get("address");System.out.println(address);return null;}@Overridepublic String getJobPosition() {String jobPosition = (String) super.getUerOfficeInfo().get("JobPosition");System.out.println(jobPosition);return null;}
}public class Client {public static void main(String[] args) {UserInfo userInfo = new UserInfo();userInfo.getAddress();OuterAdapter outerAdapter = new OuterAdapter();outerAdapter.getAddress();}
}
十四、迭代器模式
1. 定义
迭代器模式提供一种方法访问一个容器对象中的各个元素,而又不需要暴露对象的内部细节。其实就是实现Java.util.Iterator接口。因为Java对于常用容器都实现了Iterable接口,所以,像Java这种高级语言,很少再自己使用迭代器模式,因为它太常用了,已经渗透到所有语言中,不要开发者再去扩展。
2. 案例
比如一个Product集合,采用迭代器模式实现对该集合的所有元素进行访问的UML类图如下:
public interface IProject {String getProductInfo();void add(String name, int num, int cost);IProjectIterator iterator();
}public interface IProjectIterator extends Iterator {}public class ProjectIterator implements IProjectIterator{private ArrayList<IProject> projects;private int currItemIndex =0;public ProjectIterator(ArrayList<IProject> projects) {this.projects = projects;}@Overridepublic boolean hasNext() {if (this.currItemIndex >= projects.size() || this.projects.get(this.currItemIndex) == null) {return false;} else {return true;}}@Overridepublic IProject next() {return this.projects.get(this.currItemIndex++);}
}public class Project implements IProject {private ArrayList<IProject> projects = new ArrayList<>();private String name;private int num;private int cost;public Project() {}public Project(String name, int num, int cost) {this.name = name;this.num = num;this.cost = cost;}@Overridepublic String getProductInfo() {return "项目名称:" + this.name +" 项目人数:" + this.num +" 项目费用" + this.cost;}@Overridepublic void add(String name, int num, int cost) {this.projects.add(new Project(name, num, cost));}@Overridepublic IProjectIterator iterator() {return new ProjectIterator(this.projects);}
}public class Client {public static void main(String[] args) {Project project = new Project();project.add("王者荣耀", 1000, 1000000);project.add("英雄联盟", 2000, 2000000);project.add("魔兽世界", 3000, 3000000);IProjectIterator iterator = project.iterator();while (iterator.hasNext()){IProject next = (IProject) iterator.next();System.out.println(next.getProductInfo());}}
}
十五、组合模式
1. 定义
组合模式又叫部分-整体模式,该模式将对象组合成树形结构以表示“部分-整体”的层次结构,适用于维护和展示部分-整体关系的场景。
组合模式通用UML类图如下:核心点就是一个(整体)对象中添加一个(部分)对象。
实现代码如下:
public abstract class AComponent {public void operation(){//do something}
}public class Composite extends AComponent{private ArrayList<AComponent> subList=new ArrayList<>();public void add(AComponent component){this.subList.add(component);}public void remove(AComponent component){this.subList.remove(component);}public ArrayList<AComponent> getChild(){return this.subList;}
}public class Leaf extends AComponent{@Overridepublic void operation() {super.operation();}
}public class Client {public static void main(String[] args) {Composite root=new Composite();root.operation();Composite branch=new Composite();Leaf leaf=new Leaf();root.add(branch);branch.add(leaf);}//递归遍历(从上往下)private static void display(Composite root){for (AComponent component : root.getChild()) {if (component instanceof Leaf){component.operation();}else {display((Composite) component);}}}
}
2. 案例
设计一个程序去整理公司如下图的组织架构。
2.1 普通的设计方案
很简单嘛。公司组织架构就是一颗树,而一棵树包括树根,树枝,树叶三种对象,直接拍脑袋想到的UML类图如下:
核心实现代码如下:
public class Root implements IRoot{private ArrayList subList=new ArrayList();private String name;private String position;private int salary;public Root(String name, String position, int salary) {this.name = name;this.position = position;this.salary = salary;}@Overridepublic String getInfo() {return "名称:"+this.name+"\t职位:"+this.position+"\t薪水:"+this.salary;}@Overridepublic void add(IBranch branch) {this.subList.add(branch);}@Overridepublic void add(ILeaf leaf) {this.subList.add(leaf);}@Overridepublic ArrayList getSubInfo() {return this.subList;}
}public class Leaf implements ILeaf{private String name;private String position;private int salary;public Leaf(String name, String position, int salary) {this.name = name;this.position = position;this.salary = salary;}@Overridepublic String getInfo() {return "名称:"+this.name+"\t职位:"+this.position+"\t薪水:"+this.salary;}
}public class Client {public static void main(String[] args) {//创造节点IRoot ceo=new Root("王麻子","总经理",100000);IBranch devDep=new Branch("刘瘸子","研发经理",10000);IBranch saleDep=new Branch("张瘸子","销售经理",8000);IBranch moneyDep=new Branch("赵驼背","财务经理",8000);IBranch firstDevGroup=new Branch("刘能","开发一组组长",9000);IBranch secondDevGroup=new Branch("张能","开发二组组长",9000);ILeaf a=new Leaf("a","开发人员",2000);ILeaf b=new Leaf("b","开发人员",2000);ILeaf c=new Leaf("c","开发人员",2000);ILeaf d=new Leaf("d","开发人员",2000);ILeaf e=new Leaf("e","开发人员",2000);ILeaf f=new Leaf("f","开发人员",2000);ILeaf g=new Leaf("g","开发人员",2000);ILeaf h=new Leaf("h","销售人员",5000);ILeaf i=new Leaf("i","销售人员",5000);ILeaf j=new Leaf("j","财务人员",6000);ILeaf k=new Leaf("k","CEO秘书",8000);ILeaf devDog=new Leaf("研发部狗腿子","研发部副总",9000);//构造树ceo.add(devDep);ceo.add(saleDep);ceo.add(moneyDep);ceo.add(k);devDep.add(firstDevGroup);devDep.add(secondDevGroup);devDep.add(devDog);firstDevGroup.add(a);firstDevGroup.add(b);firstDevGroup.add(c);secondDevGroup.add(d);secondDevGroup.add(e);secondDevGroup.add(f);moneyDep.add(j);saleDep.add(h);saleDep.add(i);System.out.println(ceo.getInfo());getAllSubInfo(ceo.getSubInfo());}private static void getAllSubInfo(ArrayList subList){for (Object o : subList) {if (o instanceof Leaf){System.out.println(((Leaf) o).getInfo());}else {Branch branch= (Branch) o;System.out.println(branch.getInfo());getAllSubInfo(branch.getSubInfo());}}}
}
2.2 改进的设计方案
上面的普通方案,用脑子再稍微微想一想,就知道树枝和树根的结构其实一模一样,干嘛还有两个,所以可以合为一个;然后,每个对象中都有getInfo方法,那么我们应该把它提出来封装,改进后的UML类图如下:
核心实现代码如下:
public class Branch implements IBranch{private String name;private String position;private int salary;ArrayList<ICrop> subList=new ArrayList<>();public Branch(String name, String position, int salary) {this.name = name;this.position = position;this.salary = salary;}@Overridepublic void add(ICrop crop) {subList.add(crop);}@Overridepublic ArrayList getSubInfo() {return this.subList;}@Overridepublic String getInfo() {return "名称:"+this.name+"\t职位:"+this.position+"\t薪水:"+this.salary;}
}
2.3 采用组合模式的设计方案
采用组合模式对2.2的方案再改进,UML类图如下:
核心实现代码如下:
public abstract class ACrop {private String name;private String position;private int salary;public ACrop(String name, String position, int salary) {this.name = name;this.position = position;this.salary = salary;}public String getInfo(){return "名称:"+this.name+"\t职位:"+this.position+"\t薪水:"+this.salary;}
}public class Branch extends ACrop{private ArrayList<ACrop> subList=new ArrayList<>();public Branch(String name, String position, int salary) {super(name, position, salary);}public void add(ACrop crop){this.subList.add(crop);}public ArrayList getSubInfo(){return this.subList;}
}public class Leaf extends ACrop{public Leaf(String name, String position, int salary) {super(name, position, salary);}
}
3. 扩展
常规的组合模式可以很方便实现从上到下遍历数据,但是想要实现从下往上遍历数据,我们就需要在组合模式的基础稍微扩展下,只需要给每个对象添加它的父对象即可,UML类图如下:
核心实现代码如下:
public abstract class AComponent {AComponent parent;public void operation(){}protected void setParent(AComponent component){this.parent=component;}public AComponent getParent(){return this.parent;}
}public class Composite extends AComponent {private ArrayList<AComponent> subList=new ArrayList<>();public void add(AComponent component){component.setParent(this);this.subList.add(component);}public void remove(AComponent component){this.subList.remove(component);}public ArrayList<AComponent> getChild(){return this.subList;}
}
十六、状态模式
1. 定义
当一个对象内在状态改变时,其行为随之改变。适用于存在大量的case情况,每种case就是一种状态,且更能方便的处理case之间的切换条件。
状态模式通用UML类图如下:
其中AState是各个状态的抽象类角色,Context是暴露给客户端的环境角色,用户在Conetext中切换状态的同时,内部会进行状态切换,随之导致Context行为的改变。核心实现代码如下:
public abstract class AState {//当前环境角色,提供子类访问protected Context context;public void setContext(Context context){this.context=context;}public abstract void handle1();public abstract void handle2();
}public class ConcreteState1 extends AState{@Overridepublic void handle1() {//本状态下必须处理的逻辑}@Overridepublic void handle2() {//状态切换super.context.setCurrentState(Context.STATE2);super.context.handle2();}
}public class ConcreteState2 extends AState{@Overridepublic void handle1() {//状态切换super.context.setCurrentState(Context.STATE1);super.context.handle1();}@Overridepublic void handle2() {//本状态必要的操作}
}public class Context {public final static AState STATE1=new ConcreteState1();public final static AState STATE2=new ConcreteState2();private AState CurrentState;//该方法可要可不要,使用这个方法只是使得逻辑更好理解public AState getCurrentState() {return CurrentState;}public void setCurrentState(AState currentState) {this.CurrentState = currentState;this.CurrentState.setContext(this);}//委托行为public void handle1(){this.CurrentState.handle1();;}public void handle2(){this.CurrentState.handle2();}}
2. 案例
以电梯为例,电梯通常有开门,关门,运行,停止这样四个状态,但是各个状态直接的切换其实都有一定的条件,如开门状态下,正常情况下肯定不能切换到运行状态,涉及多个状态直接转换采用状态模式再合适不过,采用状态模式设计该案例的UML类图如下:
核心实现代码如下:
public abstract class ALiftState {protected Context context;public void setContext(Context context){this.context=context;}public abstract void open();public abstract void close();public abstract void run();public abstract void stop();
}public class Context {public final static OpeningState openingState=new OpeningState();public final static ClosingState closingState=new ClosingState();public final static RunningState runningState=new RunningState();public final static StoppingState stoppingState=new StoppingState();private ALiftState liftState;ALiftState getLiftState(){return liftState;}void setLiftState(ALiftState liftState){this.liftState=liftState;this.liftState.setContext(this);}void open(){this.liftState.open();}void close(){this.liftState.close();}void run(){this.liftState.run();}void stop(){this.liftState.stop();}
}public class StoppingState extends ALiftState{@Overridepublic void open() {super.context.setLiftState(Context.openingState);super.context.open();}@Overridepublic void close() {System.out.println("电梯此时不能执行关闭操作...");}@Overridepublic void run() {super.context.setLiftState(Context.runningState);super.context.run();}@Overridepublic void stop() {System.out.println("电梯停止...");}
}public class Client {public static void main(String[] args) {Context context = new Context();context.setLiftState(Context.closingState);context.open();context.run();context.close();context.run();context.stop();}
}
3. 优缺点
- 优点:
- 结构清晰,避免过多的case语句,提高系统的可维护性。
- 满足开闭原则和单一职责原则,需要增加一种状态只需要实现一个子类即可
- 封装性好,外部调用不需要知道内部如何进行状态切换。
- 缺点:
缺点也很明显,对于过多的状态会造成类膨胀问题。
十七、解释器模式
这个模式理解起来可能有点费劲,且它的使用很少,所以能搞懂就搞懂吧,不能搞懂也就算了,但最好能搞懂这种模式的思想。
1. 定义
解释器模式是一种按照规定语法进行解析的方案,其实就是简单语法分析工具。比如四则运算就是一种规定语法,我们要实现对其计算,就可以采用解释器模式,以四则运算为例,它的表达式包括两种要素,一种是数值(终结表达式),一种是符号(非终结表达式),那么我们就需要对这两种表达式进行解析。
解释器模式通用的UML类图如下:主要是对各种表达式进行解析(AbstractExpression),然后用Context对其封装,Client调用Context完成对一种文法的解析。
2. 案例
输入任意一个模型公式(本案例只需要加减即可),输入对应的参数值,运算出结果。
给定一个公式,然后实现该公式的计算非常简单,但是实现对任意公式的计算就不是那么简单了,这就需要你写的代码具有良好的扩展性,此时采用解释器模式最合适不过,也可以理解为解释器模式就是为了这种需求而产生的。
采用解释器模式实现加减运算的UML类图如下:
实现代码如下:
public abstract class Expression {public abstract int interpreter(HashMap<String,Integer> var);
}//解析数值表达式
public class VarExpression extends Expression{private String key;public VarExpression(String key) {this.key = key;}@Overridepublic int interpreter(HashMap<String, Integer> var) {return var.get(key);}
}//解析符号表达式
public abstract class SymbolExpression extends Expression {protected Expression left;protected Expression right;public SymbolExpression(Expression left, Expression right) {this.left = left;this.right = right;}
}//加号解释器
public class AddSymbolExpression extends SymbolExpression{public AddSymbolExpression(Expression left, Expression right) {super(left, right);}@Overridepublic int interpreter(HashMap<String, Integer> var) {return left.interpreter(var)+ right.interpreter(var);}
}//减号解释器
public class SubSymbolExpression extends SymbolExpression{public SubSymbolExpression(Expression left, Expression right) {super(left, right);}@Overridepublic int interpreter(HashMap<String, Integer> var) {return super.left.interpreter(var)- super.right.interpreter(var);}
}/*** 封装表达式计算逻辑*/
public class Calculator {private Expression expression;public Calculator(String expStr) {//自构建公式Stack<Expression> stack = new Stack<>();char[] chars = expStr.toCharArray();Expression left = null;Expression right = null;for (int i = 0; i < chars.length; i++) {switch (chars[i]){case '+':left=stack.pop();right=new VarExpression(String.valueOf(chars[++i]));stack.push(new AddSymbolExpression(left,right));break;case '-':left=stack.pop();right=new VarExpression(String.valueOf(chars[++i]));stack.push(new SubSymbolExpression(left,right));break;default:stack.push(new VarExpression(String.valueOf(chars[i])));}}this.expression=stack.pop();}/*** 传入具体符号对应的数据(key代表符号,value代表值)* @param var* @return*/public int run(HashMap<String,Integer> var){//递归计算return this.expression.interpreter(var);}
}public class Client {public static void main(String[] args) {//输入表达式String expStr = getExpStr();//输入数据HashMap<String, Integer> data = getValue(expStr);//计算公式Calculator calculator = new Calculator(expStr);int result = calculator.run(data);System.out.println("运行结果为:" + result);}/*** 键盘输入表达式** @return*/public static String getExpStr() {System.out.println("请输入表达式:");try {return new BufferedReader(new InputStreamReader(System.in)).readLine();} catch (IOException e) {throw new RuntimeException(e);}}/*** 输入数据并以Map的形式组织数据** @param expStr* @return*/public static HashMap<String, Integer> getValue(String expStr) {HashMap<String, Integer> data = new HashMap<>();char[] chars = expStr.toCharArray();for (char aChar : chars) {if (aChar != '+' && aChar != '-') {if (!data.containsKey(aChar)) {try {System.out.print("请输入" + aChar + "的值: ");String in = new BufferedReader(new InputStreamReader(System.in)).readLine();data.put(String.valueOf(aChar), Integer.parseInt(in));} catch (IOException e) {throw new RuntimeException(e);}}}}return data;}
}
运行代码示例:
请输入表达式:
a+b+c-d
请输入a的值: 1
请输入b的值: 1
请输入c的值: 1
请输入d的值: 1
运行结果为:2
3. 优缺点
优点:
解释器是一个简单语法分析工具, 它最显著的优点就是扩展性, 修改语法规则只要修改
相应的非终结符表达式就可以了, 若扩展语法, 则只要增加非终结符类就可以了
缺点:
- 非终结表达式多的话,会造成类膨胀
- 解释器模式采用的是递归调用,这导致它的调试很复杂,且效率不高
总结:解释器模式很少自己实现了,因为自己简单的实现存在很多缺点,但是它的思想还是值得学习的,如果遇到需要使用解释器模式的场景,可以使用现有的开源解析工具包或者脚本语言代替。
十八、门面模式
1. 定义
门面模式非常简单,就是将复杂的子系统交互全部封装到一个门面类中,外界只能通过这个门面类访问子系统。
门面模式通用类图如下所示,门面类其实就是子系统的一个委托对象。
2. 案例
比如我们实现写一份信的过程,最直接的实现类图如下:
客户端直接访问写信的各个过程,那么客户端需要知道步骤顺序,否则这封信就发不出去,其次扩展性极差。
采用门面模式实现写信过程的类图如下所示:
客户端只能访问ModernPostOffice,且通过ModernPostOffice直接实现写信的全过程,不需要知道细节,且扩展容易,如扩展检查信封的功能,只需要将Police类组合到ModernPostOffice中即可。
核心实现代码如下:
public interface ILetterProcess {void write(String context);void envelope(String address);void postBox();void send();
}public class ModernPostOffice {private ILetterProcess letterProcess=new LetterProcessImpl();private Police police=new Police();public void sendLetter(String context,String address){letterProcess.write(context);letterProcess.envelope(address);police.check(letterProcess);letterProcess.postBox();letterProcess.send();}
}public class Client {public static void main(String[] args) {ModernPostOffice modernPostOffice = new ModernPostOffice();modernPostOffice.sendLetter("你好,好久不见","南京市建邺区XXX");}
}