借用公开课 Justice 中的话,了解设计模式不必定能让咱们解决软件设计与开发中的问题,但能让咱们在遇到问题时,思考的方式不至鲁莽与茫然。php
面向对象软件设计具备五大基本原则(首字母缩写为:SOLID):html
单一职责原则(SRP:Single Responsibility Principle):不要让一个类承担过多的职责,就一个类而言,应该仅有一个引发它变化的缘由。git
开闭原则(OCP:Open-Close Priciple):保证类对扩展开放,而对修改封闭。github
里氏代换(LSP:The Liskov Substitution Principle):子类必须可以替换掉它们的父类。web
接口隔离原则(ISP:The Interface Segregation priciple):客户端不该该依赖它不须要的接口;一个类对另外一个类的依赖应该创建在最小的接口上;不要让类实现含有无用方法的接口。算法
依赖倒转(DIP:Dependency Inversion Principle):高层次的模块不该该依赖于低层次的模块,他们都应该依赖于抽象;抽象不该该依赖于具体,具体应该依赖于抽象。数据库
如下对 GRASP & GoF 所表明的设计模式进行介绍,这些设计模式并不是彻底按照以上基本原则,有的甚至为了解决某一类问题而破坏这些原则。
下述全部示例代码能够在 Github 上查看。编程
通用责任链分配模式的核心思想是职责分配(Responsibility Assignment)。设计模式
专家模式要求将请求的处理交由信息的全部者。api
可能会致使信息专家对象承担过多的职责。
考虑登陆需求,在控制器中,将登陆验证逻辑交由拥有登陆所需信息的类来进行,而不是控制器自己。
public class Controller { public String login(){ User user = new User("username", "password"); boolean canLogin = user.login(); if(canLogin){ return "www.website.com/index"; }else{ return "www.website.com/error.401.php"; } } }
控制器模式要求模块可以接受请求,并将请求转发到业务处理模块,且按照处理模块的处理结果反馈响应(MVC 模式)。
控制器承担了过多职责,控制器每每与大量类存在耦合。
MVC 模式中的控制器层。
注意,View
类也能够直接调用 Model
类。
public class Controller { public Object dispatch(Object request){ Model model = new Model(); Object res = model.handle(request); return res; } }
建立者模式定义了一些标准,要求在如下状况下,A 类对象须要是 B 类对象的建立者:
A 类对象是 B 类对象的聚合体;
A 类对象包含 B 类对象;
A 类对象使用 B 类对象;
A 类对象记录 B 类对象状态;
A 类对象拥有建立 B 类对象的数据/信息。
因为以上关系可能存在于大量类之间,致使 B 类的建立过程难以统一。
GoF:Gang of Four,指被称为设计模式先驱的四人:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。他们提出了如下 23 种设计模式,且这些设计模式又可被分为建立型模式、结构型模式,以及行为型模式。
确保类中只有一个实例,试图获取这一类的逻辑代码所获得的对象均为同一个。
在多线程编程中须要保证访问的同步。
如获取配置文件的内容,因为配置文件在项目运行后通常不会再修改,因此配置对象能够采用单例模式实现。
public class Config { private static Config config = null; // 私有化构造方法 private Config() {} /** * 当对象为空时,先建立再返回 * 反之直接返回 * @return */ public static Config getConfig(){ if(Config.config == null){ Config.config = Config.createConfig(); return Config.getConfig(); }else{ return Config.config; } } /** * 若是对象的建立过程比较复杂,能够单独抽离一个 createConfig 方法 * 固然也能够把建立过程直接写在 getConfig 方法之中 * @return */ private static Config createConfig(){ return new Config(); } }
注意:为了让外界不能再调用构造方法,应当将其私有化。此外,在其余语言中,可能还须要将序列化和克隆方法均声明为私有。
经过拷贝原型实例而非从新调用构造方法的方式获得新的对象。
考虑原型对象的过程可能比较繁琐(涉及深拷贝问题);且须要特别注意循环引用的问题(类的属性中又包含该类)。
对一类通知对象的构建。通知对象中含有大量的重复属性,如通知头部信息和通知尾部信息。
public class Message implements Cloneable{ // 通用头部 private Object commonHeader = null; // 通用尾部 private Object commonFooter = null; // 定制主体 private Object body; @Override protected Message clone() throws CloneNotSupportedException { Message message = (Message) super.clone(); // 若是头部和尾部的对象建立过程须要深拷贝,则须要特殊处理这段代码 message.commonHeader = new Object(); message.commonFooter = new Object(); return message; } public void setBody(Object body) { this.body = body; } }
注意:在 Java 代码中须要实现 Cloneable
接口,而在其余语言中可能须要用其余机制。如 PHP 中,须要重写魔术方法 __clone()
,并在须要调用时使用 $obj1 = clone $obj2
。
使得复杂对象的构造过程和表示分离,以即可以复用相同的构造过程。
每一类产品都须要一个构造器,增长了代码量。
订单可能须要转换为 XML
或 Json
格式,虽然转换方法自己是不一样的,但转换的步骤(转换订单地址、转换配送时间等)是相同的,于是可使用构造器模式实现。
注意,虽然 Client
依赖箭头指向的是 OrderJSONBuilder
,但其实际依赖的 OrderBuilder
接口。
public class Client { private void doSome(){ OrderBuilder orderBuilder = new OrderJSONBuilder(); Director director = new Director(orderBuilder); // 采用指导器用统一的过程指导构建 director.build(); // 从具体的构造类的得到构造结果 Object res = orderBuilder.getRes(); } }
注意,用 Director
进行构造过程,而最终的结果是从具体的 OrderBuilder
中得到的,Director
中的代码为:
public class Director { private OrderBuilder orderBuilder; public Director(OrderBuilder orderBuilder) { this.orderBuilder = orderBuilder; } public void build(){ this.orderBuilder.convertAddr(); this.orderBuilder.convertFooter(); this.orderBuilder.convertHeader(); this.orderBuilder.convertTime(); } }
为了介绍后续的工厂类模式,这里先对简单工厂的实现进行介绍(该模式并不属于 GoF 的 23 种设计模式之一)。简单工厂模式将对象的构建过程统一到一块儿,在业务代码中须要使用到这一对象时,统一经过工厂类进行构建。这种模式使得对象的构造过程得以统一,让对象构造过程的变化不至于致使大范围的代码修改。此外,当构造过程很是复杂时,采用简单工厂也能够减小代码的冗余。
构造工厂的引入增长了代码量。
订单对象的构建过程一般须要对样式、实际信息、附加信息等进行构造,相对复杂和麻烦,这时可使用订单构造工厂来进行实现。
public class OrderFactory { public static Order createOrder(){ Object style = new Object(); Object body = new Object(); Object appendix = new Object(); Order order = new Order(style, body, appendix); return order; } }
对比简单工厂模式,若是当前须要工厂来进行具体对象的构建,但工厂所要进行的建立过程如今还未可知时,咱们便须要考虑使用工厂方法模式进行实现。工厂方法模式将具体的工厂建立行为交由子类实现。
工厂方法模式进一步聚合了一组构建对象和一组构建它们的工厂。当须要建立新的对象时,新的工厂也须要被定义。
订单的导出过程结果能够是 pdf 形式,也能够是 html 形式,而订单的构造过程为转换格式并导出,对于不一样格式的订单构造,只有转换格式这一步是不一样的,此时能够考虑使用工厂方法模式。
abstract public class OrderFactory { protected Order order; public Order export(){ this.convert(); return this.order; } abstract protected void convert(); }
而后,能够在 Client
中这样使用:
public class Client { public void doSome(){ OrderFactory factory = new PdfOrderFactory(); Order order = factory.export(); } }
类比工厂方法模式,若是建立的对象分为多类,每一类又含有相同的分类时,能够考虑使用抽象工厂模式。该模式为产品族群对象或相互关联对象提供了统一的接口。
应对产品类型增长这一变化时,抽象工厂模式相对困难。
图表能够分为饼状图、折线图和柱形图,而每一种图表格又能够分为扁平风格和水晶风格。咱们能够把这种类型的对象建立过程采用抽象工厂模式进行实现。
Client
端使用的方式为:
public class Client { public void doSome(){ FlatChartFactory flatChartFactory = new FlatChartFactory(); CrystalChartFactory crystalChartFactory = new CrystalChartFactory(); BarChart barChart = flatChartFactory.createBarChart(); LineChart lineChart = crystalChartFactory.createLineChart(); } }
ChartFactory
的代码为:
abstract public class ChartFactory { abstract public PieChart createPieChart(); abstract public LineChart createLineChart(); abstract public BarChart createBarChart(); }
抽象工厂实际上是将不一样的分类方式巧妙的利用了起来。对于这一示例,若是须要增长 3D 类型的构造工厂,则只需从新实现一个工厂类便可。但若要新增一款产品(如雷达图),则须要改动的代码会至关多。
经过将原接口转换为目标接口以实现不一样对象之间的适配。
一个适配器只能适应一个目标需求。大量使用适配器可能会致使项目代码层级愈来愈多。
视图层渲染视图的时候,须要使用某一类型的对象,而业务代码所能提供的是另外一种类型。此时可使用适配器模式来解决问题。
public class Controller { public void dispatch(){ Viewer viewer = new Viewer(); Data originData = new Data(); DList data = new DataAdapter(originData); // 要求传入的 DList 类型的对象,咱们借助适配器进行了接口适配 viewer.render(data); } }
这种实现一方接口的适配器被称为对象适配器;若是该适配器同时实现或继承调用者和被调用者双方,则称其为类适配器(可能须要多继承的语法支持)。
桥模式旨在进行抽象层和实现层的分离。
增长了类设计的数量。
数据库日志类中须要实现对数据库的日志操做,该日志操做又分为 XML 格式日志和 JSON 格式日志。此外,数据库管理类须要依赖数据库操做类实现数据操做,且数据库管理类又存在 MySQL、SQLite 等多种实现。应对这一需求,可使用桥模式实现。
public class Client { public void doSome(){ DBOperator operator = new DBMySQLOperator(); DBLogger logger = new DBXMLLogger(); logger.setOperator(operator); logger.log(); } }
组合模式将聚合体及其内部的组成元素统一看成一种类型看待和操做。
组合模式要求把聚合体和元素都看做等同的类型,这在一些状况下存在使用的局限性。
被渲染页面中可能存在多个布局模块,模块中的一些部分又能够看成子布局模块,这其中也存在一些非聚合体组件。对于这种需求和状况,能够考虑使用组合模式。
视图层基于某一模块进行渲染:
public class Viewer { public void render(){ Renderable page = new Module(); page.draw(); } }
若模块的某子部分为普通的元素,则直接渲染,若仍为模块,则递归渲染:
public class Module implements Renderable { private List<Renderable> components = new ArrayList<>(); @Override public void draw() { Iterator<Renderable> iterator = this.components.iterator(); while (iterator.hasNext()){ iterator.next().draw(); } } }
实现动态地向对象中添加功能。
动态地添加功能,则意味着实际调用对象的功能与原对象不一样,致使调试上出现困难。
每一种咖啡都有价格选项,而只有一部分咖啡能够加糖或加牛奶。面对这种对象组织和需求,能够考虑使用装饰器模式。
public class Client { public void offer(){ Coffee coffee = new SimpleCoffee(); // 加糖 Coffee coffeWithSuger = new CoffeeSugerDecorator(coffee); // 加牛奶 Coffee coffeeWithMilkAndSuger = new CoffeeMilkDecorator(coffeWithSuger); } }
为了避免把复杂的操做过程通通暴露出去,能够在类中仅定义一个公有方法给外界,而把复杂的过程隐藏在这个方法中。
暴露的方法的会受到内部实现方法变更的影响。
一次 API 接口的调用可能须要通过不少步骤,若是这些方法通通暴露在外界的话,会增长外部使用这一对象的难度。于是咱们能够只留下一个方法,而把复杂的步骤隐藏在这一方法中。
在客户端,咱们能够只调用暴露的方法:
public class Client { public void callAPI(){ APICaller apiCaller = new APICaller(); Object res = apiCaller.call(); } }
而把具体的过程隐藏起来:
public class APICaller { public Object call(){ Object res = getOriginData(); res = encryptData(res); res = compressData(res); return res; } private Object getOriginData(){ return new Object(); } private Object encryptData(Object originData){ // 加密处理 return originData; } private Object compressData(Object originData){ // 压缩 return originData; } }
享元模式旨在采用共享方式有效使用数量巨大的细粒度对象。
在向享元管理类申请资源时须要伴随必定的逻辑处理过程,会有资源消耗。
把资源类对象的申请过程交由资源管理类进行,管理类拥有资源池,负责合理地分配和回收资源,以达到资源的最大利用率。
public class Client { public void doSome(){ ResourcePool pool = new ResourcePool(); try { Object o = pool.apply(4); } catch (Exception e) { System.out.println("资源申请失败"); e.printStackTrace(); } } }
在资源管理类中,代码为:
public class ResourcePool { public ArrayList<Object> resources = new ArrayList<>(); public Object apply(int index) throws Exception { if(resources.size() >= index + 1){ if(resources.get(index) == null){ Object flyweight = new Object(); resources.set(index, flyweight); } return resources.get(index); }else{ throw new Exception("Invalid request"); } } }
注意,这里实现的是简化的资源申请和管理策略。
为目标对象提供代理对象,从而能够在真正的对象执行逻辑先后插入一些逻辑代码,如日志记录等。
代理类的引入使得代码的逻辑变得复杂。
在向数据库存取数据操做的先后进行操做类型和花费时间的记录,此时能够考虑使用代理模式。
在客户端使用相同的方式调用操做类:
public class Client { public void doSome(){ DBOperator operator = new DBOperatorProxy(); Object o = operator.getData(); } }
而实际的代码执行过程先后被加入了另外的逻辑:
public class DBOperatorProxy implements DBOperator{ private DBOperator dbOperator = new DBOperatorImpl(); @Override public Object getData() { // 前置操做 Object res = this.dbOperator.getData(); // 后置操做 return res; } }
注意,这里使用的是静态代理模式,在不少语言中,还可使用反射机制实现动态代理甚至面向切面开发。
当处理某一请求须要多个步骤,且这多个步骤之间能够以某一顺序依次进行时,可使用责任链模式。
责任链模式虽然串联了解决问题的步骤,但并未保证问题必定能够在链上被处理。
订单验证功能可能须要多层验证机制才能确保无误,此时可使用责任链模式进行实现。
在客户端中,只需调用首位验证器便可:
public class Client { public void auth(){ Object object = new Object(); Validator validator = new LoginValidator(); validator.validate(object); } }
这以后的链式操做会由验证器依次进行:
abstract public class Validator { protected Validator successor = null; public boolean validate(Object object){ // 验证过程 boolean isValid = false; if(isValid){ return true; }else{ if(this.successor != null){ return this.successor.validate(object); }else{ return false; } } } public void setSuccessor(Validator successor) { this.successor = successor; } }
固然,咱们也能够经过 setter
方法动态的设置责任链上的验证器后继节点。
命令模式将操做封装为类,使得对执行过程的撤销变得易于实现。
因为操做都被封装为了类,所以项目中的代码量将会剧增。
对数据库的操做能够分为增删改查,当进行具体操做时,为了保证操做易于撤回,咱们能够考虑使用命令模式进行实现。
在客户端,可使用 Invoker
执行命令:
public class Client { public void doSome(){ Command commandA = new QueryCommand(); Command commandB = new CreateCommand(); Command commandC = new UpdateCommand(); Command commandD = new DeleteCommand(); Invoker invoker = new Invoker(); invoker.invoke(commandA); invoker.invoke(commandB); invoker.undo(); invoker.invoke(commandC); invoker.undo(); invoker.invoke(commandD); } }
Invoker
中的代码为:
public class Invoker { private History history = new History(); public void invoke(Command command){ this.history.add(command); } public void undo(){ this.history.undo(); } }
在 History
中,对命令历史进行保存:
public class History { private Stack<Command> history = new Stack<>(); public void undo(){ Command command = history.pop(); command.undo(); } public void add(Command command){ command.redo(); this.history.push(command); } }
对于自定义表达式的运算,须要涉及到对表达式的解析和执行过程。如此,能够考虑使用解释器模式。
难以处理复杂的解释过程。
如自定义的中文加法运算的执行过程(一 + 一 = 二),可使用该模式。
将表达式分为终结表达式和非终结表达式,在解释过程当中,用相似递归的方式进行解释:
public class AddExpression extends NonFinalExpression { @Override public int interpret(Context context) { int left = this.left.interpret(context); int right = this.right.interpret(context); return left + right; } }
当但愿可以在不暴露内部实现细节的状况下提供某一类的遍历手段时,能够考虑使用迭代器模式。
迭代过程返回内部的对象,会形成封装性的破坏。
好比要遍历工资管理类中的所有工资清单,可使用这一模式。
将一个类变为可迭代的类(不一样语言中实现的方式不一样):
public class SalaryCollection implements Iterator<Salary>{ private Salary salaries[] = { new Salary(10000), new Salary(20000), new Salary(15000), new Salary(30000), new Salary(5000) }; private Integer index = 0; @Override public boolean hasNext() { return index >= salaries.length; } @Override public Salary next() { return salaries[index++]; } }
而后在客户端对其进行遍历:
public class Client { public void doSome(){ SalaryCollection collection = new SalaryCollection(); int sum = 0; for (; collection.hasNext(); ) { Salary salary = collection.next(); sum += salary.getValue(); } } }
当多个对象之间均有交互请求,且交互逻辑较为复杂时,可使用仲裁者模式造成对象间交互的中间层(仲裁者),用以解耦对象之间的显式依赖。
显然,解耦的依赖转移到了仲裁者对象自己,使得当被仲裁对象发生变化时,仲裁者自己也必须响应该变化并进行修改。
系统中存在顾客、餐厅员工,以及配餐员三个角色,其中,餐厅员工会通知配餐员送餐;配餐员会告知顾客餐正在配送中;而顾客也能够向配送员或餐厅员工询问餐的进展。对于这种多个对象之间存在多种耦合的行为,能够考虑采用仲裁者模式实现。
在仲裁者模式中,请求均经过仲裁者对象进行,从而对各个对象进行了解耦:
public class Mediator { private Patron patron = new Patron(); private Deliverer deliverer = new Deliverer(); private Staff staff = new Staff(); public void askForDeliverer(){ this.deliverer.receiveAsk(); } public void askForStaff(){ this.staff.receiveAsk(); } public void sendConfirm(){ this.patron.getConfirm(); } public void requestToDeliver(){ this.deliverer.receiveRequest(); } }
在不破坏封装性的状况下,对对象的内部信息进行保存。
用于记录内部信息快照的备忘录对象自己是耗费资源的。
如下演示对于订单状态的备忘保存实现。
OrderHistory
中负责记录订单的历史信息,存储的是 Memento 对象:
public class OrderHistory { private ArrayList<Memento> mementos = new ArrayList<>(); public void store(Order order){ Memento memento = order.createMemento(); this.mementos.add(memento); } public void restore(Order order, int index){ Memento memento = this.mementos.get(index); order.restore(memento); } }
而订单类负责产生备忘录对象:
public class Order { private State state; public Memento createMemento(){ Memento memento = new Memento(); memento.setState(this.state); return memento; } public void restore(Memento memento){ this.state = memento.getState(); } }
注意,不能把历史信息直接存储在 Order
类中,由于一旦 Order
类生命周期结束,历史信息也会消失,这样是不合理的。
观察者模式是很是经常使用的一种设计模式。若是当某一事物发生改变时,这种行为会触发一系列的对象产生变化,此时就可使用观察者模式来解耦这种一对多的对象关联关系。
可能会致使观察者的级联更新。
不一样的用户使用不一样的方式(邮件、短信)注册了某一通知服务,当有新通知到达时,系统会基于不一样的注册方式向注册用户发送通知。应对这一需求,可使用观察者模式。
public class Publisher { private List<Observer> observers = new ArrayList<>(); public void addObserver(Observer observer){ this.observers.add(observer); } public void publish(Message message){ Iterator<Observer> iterator = this.observers.iterator(); while (iterator.hasNext()){ Observer observer = iterator.next(); observer.update(message); } } public void removeObserver(Observer observer){ this.observers.remove(observer); } }
容许一个对象的具体行为随着内部状态的改变而改变。
各个子状态间存在先后继的耦合关系。
对一个订单而言,其存在三种状态:准备、完成、配送。当处于准备状态时,订单须要进行加工操做;当处于完成状态时,须要执行通知以实现配送;当处于配送状态时,须要实时发布物流消息。对于这一需求,能够经过状态模式实现。
客户端调用的方式为:
public class Client { public void doSome(){ // 初始化,进入准备状态 Order order = new Order(); // 进入完成状态 order.action(); // 进入配送状态 order.action(); // 结束 } }
准备状态代码:
public class PrepareState implements State { @Override public void handle(Order order) { // 执行加工操做业务代码 // ... // 转换状态 order.setState(new FinishedState()); } }
结束状态代码:
public class FinishedState implements State { @Override public void handle(Order order) { // 执行通知操做业务代码 // ... // 转换状态 order.setState(new DeliveringState()); } }
配送状态代码:
public class DeliveringState implements State { @Override public void handle(Order order) { // 执行发布操做业务代码 // ... // 转换状态 order.setState(null); } }
业务的具体算法能够在运行时确认和变更。即业务过程的策略能够进行设置和调整。
调用方须要手动指定策略。引入策略会破坏封装性。
在要展现的列表数据处理逻辑中,对列表的排序操做存在多种策略,如按照姓名排序、按照年龄排序、按照薪资排序等。对于这种形式的需求,能够考虑采用策略模式进行实现。
在使用方,手动指定策略:
public class Client { public void doSome(){ DataViewer dataViewer = new DataViewer(); SortStrategy strategyA = new SortByNameStrategy(); SortStrategy strategyB = new SortByAgeStrategy(); SortStrategy strategyC = new SortBySalaryStrategy(); dataViewer.setStrategy(strategyA); // or dataViewer.setStrategy(strategyB); // or dataViewer.setStrategy(strategyC); dataViewer.render(); } }
将算法或逻辑的不变行为抽离出来,而将可变部分放在子类中实现。
会产生较多的类。
在结帐这一业务中,确认帐单、得到帐单这一过程是相同的,而选择支付方式是不一样的。咱们能够把这一需求采用模板方法模式进行实现。
在客户端的调用方法以下:
public class Client { public void doSome(){ PayOrder payOrderA = new PayOrderByCard(); // or PayOrder payOrderB = new PayOrderByCash(); payOrderA.check(); } }
PayOrder
中的实现为:
abstract public class PayOrder { final public void check(){ this.confirm(); this.pay(); this.getBill(); } // 确认 private void confirm(){ } // 得到帐单 private void getBill(){ } // 子类中须要实现的方法 abstract protected void pay(); }
在不改变聚合对象元素行为的状况下,定义施加在聚合对象元素上的行为。
聚合对象须要相对稳定。
订单分为即时订单和预购订单两类,当须要对订单的进行检查时,检查行为须要分别对这两类进行操做。此时,能够考虑使用访问者模式。
在 Manager
中,须要先设定访问者类型,而后对拥有订单的顾客 Patron
进行订单检查:
public class Manager { public void doSome(){ Patron patron = new Patron(); Visitor visitorA = new VisitorImplA(); // or Visitor visitorB = new VisitorImplB(); patron.setVisitor(visitorA); patron.check(); } }
Patron
的代码为:
public class Patron { private List<Order> orders = new ArrayList<>(); private Visitor visitor; public void setVisitor(Visitor visitor) { this.visitor = visitor; } public void check(){ Iterator<Order> iterator = this.orders.iterator(); while (iterator.hasNext()){ Order order = iterator.next(); order.accept(this.visitor); } } }
Visitor
实现类须要针对不一样的 Element
进行对应的操做,代码为:
public class VisitorImplA implements Visitor { @Override public void visitOrder(Order order) { order.order(); } @Override public void visitSubOrder(SubOrder subOrder) { subOrder.subOrder(); } }