设计模式
面向对象设计原则
1、单一职责原则
定义:
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
单一职责原则是实现高内聚、低耦合的指导方针,是最简单却最难运用的原则,需要设计人员发现类的不同职责并将其分离
2、开闭原则
定义:
软件实体应当对扩展开放,对修改关闭。
指软件实体应尽量在不修改原有代码的情况下进行扩展。
3、里氏替换原则
定义:
所有引用基类的地方必须能透明地使用其子类的对象。
里氏替换原则表明,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。
在运用里氏替换原则时,应该将父类设计为抽象类或者接口,让子类继承父类或实现父类接口,并实现在父类中声明的方法。
4、依赖倒转原则
定义:
高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
依赖倒转原则要求:要针对接口编程,不要针对实现编程。
5、接口隔离原则
定义:
客户端不应该依赖那些它不需要的接口。
在使用接口隔离原则的时候,需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来不方便。
6、合成复用原则
定义:
优先使用对象组合,而不是继承来达到复用的目的。
一般而言,如果两个类之间是”Has-A”关系应使用组合或聚合,如果是”Is-A”关系可使用继承。
7、迪米特法则-又称最少知识原则
定义:
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
前提
- 设计模式的目的是在一个软件体系中找到稳定点与变化的点,用设计模式控制好变化
- 代码设计模式的实现应该是“重构到设计模式”,也就是不应该是为了用某个模式去写代码,而是在代码的优化过程中依照原则重构,便会自然而然的靠近设计模式。
常用方法:
- 静态->动态
- 早绑定->晚绑定
- 继承->组合
- 编译时依赖->运行时依赖
- 紧耦合->松耦合
1.模板模式
1.1.动机
对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因而无法和任务整体同步实现。
1.2.定义
定义一个操作中的算法的骨架(稳定),而将一些步骤延迟到子类(变化)中。模板方法使得子类可以复用一个算法的结构即可重定义(override)该算法的某些特定步骤。
1.3.实例
Java web中的Filter类就是这个思想,Filter需要实现的就是init,doFilter,destory。是稳定的,符合方法前提,只是具体实现(尤其是doFilter步骤)会变化。
package javax.servlet;import java.io.IOException;public interface Filter {void init(FilterConfig var1) throws ServletException;void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;void destroy();}
1.4.优点
- 封装不变部分,扩展可变部分。
- 提取公共代码,便于维护。
- 行为由父类控制,子类实现。
2.策略模式
2.1.动机
某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象点的异常复杂,而且有时候支持不适用的算法也是一个性能负担。策略模式的目的就是根据需要透明的改变对象的算法,将对象与算法解耦。
2.2.定义
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法,使得他们根据场景可互相替换
2.3.实例
首先假定有下面这种情况:在Calculate这个类中有三种计算方式,我们把这三种全部封装到类里面,使用时就实例化调用,好似没有问题,但是如果现在有一种新的计算方式也要加入这个类,就需要修改了,这违背了开闭原则。应当是对修改关闭,对扩展开放的。
package com.xiao.strategy;public class Calculate {public int add(int a,int b){return a+b;}public int sub(int a,int b){return a-b;}public int multi(int a,int b){return a*b;}public static void main(String[] args) {new Calculate().add(1,2);}}
于是我们用策略模式修改
- 定义一个计算策略的接口
package com.xiao.strategy;public interface AbstractCalculate {int operation(int a,int b);}
- 具体的策略来实现这个接口
package com.xiao.strategy;public class AddCalculate implements AbstractCalculate {@Overridepublic int operation(int a, int b) {return a+b;}}public class SubCalculate implements AbstractCalculate {@Overridepublic int operation(int a, int b) {return a-b;}}public class MulCalculate implements AbstractCalculate {@Overridepublic int operation(int a, int b) {return a*b;}}
- 定义一个具体执行策略的类
package com.xiao.strategy;public class CalculateImpl {private AbstractCalculate abstractCalculate;public CalculateImpl(AbstractCalculate abstractCalculate) {this.abstractCalculate = abstractCalculate;}public int operation(int a,int b){return this.abstractCalculate.operation(a,b);}public static void main(String[] args) {int add = new CalculateImpl(new AddCalculate()).operation(5,10);System.out.println("5+10="+add);int sub = new CalculateImpl(new SubCalculate()).operation(5,10);System.out.println("5-10="+sub);}}
这时,如果要扩展,只需要写新的实现AbstractCalculate这个接口的类就好了。
2.4.优点
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
3.观察者模式
3.1.动机
在软件建构中,我们需要为某些对象创建一种“通知依赖关系”,————-一个对象(目标对象)的状态发送改变,所有的其他关心对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使得软件不能很好的抵御变化。
3.2.定义
定义对象键的一种一对多【甚至多对多】(变化)的依赖关系,当一个或多个对象状态发送改变时,所有依赖于这些对象的其他对象的到通知。
3.3.实例
有这么一个简单的情景:一家进口公司和一家出口公司都想同时知道人民币与美元的汇率,以便做出调整。
这个模型中汇率就是被观察的对象,公司是观察的对象,需要知道汇率的变化,最直接的想法是:让汇率对象内部同时维护两家公司,每当汇率变化就通知公司,但是这种想法下,一旦有其他的公司也想知道汇率,就必须要该源码了,显然违背了开闭原则。
上面的设计之所以出问题,在于被观察者内部需要维护变化的具体实现。于是,在观察者模式中,我们就把具体实现变为抽象实现,因为都是公司,我们就提取出公共属性抽象为接口(或抽象类),由于公司的数量不定(可能添加或删除),于是我们维护成一个可变长的数组。
Rate接口
package com.xiao.observer;public interface Rate {//添加一个观察者void addCompany(Company company);//删除一个观察者void removeCompany(Company company);//查询float getRate();//改变void updateRate(float rate);//通知观察者void notifyCompanies();}
Rate具体实现(RMB与Dollar的实现几乎完全一致,只是name不同)
package com.xiao.observer;import java.util.ArrayList;import java.util.List;public class RMBRate implements Rate {private List<Company> companies = new ArrayList();private float rate;private final String name = "人民币";public RMBRate(float rate) {this.rate = rate;}@Overridepublic float getRate() {return rate;}@Overridepublic void addCompany(Company company) {companies.add(company);}@Overridepublic void removeCompany(Company company) {companies.remove(company);}//一旦发生更新,就通知所有观察者@Overridepublic void updateRate(float rate) {this.rate = rate;notifyCompanies();}@Overridepublic void notifyCompanies() {for (Company company : companies) {company.reflact(this.name,this.getRate());}}}
Company具体实现
package com.xiao.observer;public class InputCompany implements Company {@Overridepublic void reflact(String name,float updateRate) {System.out.println("进口公司:"+(updateRate>=0?name+"汇率增加"+updateRate:name+"汇率减少"+(-updateRate)));}}public class OutputCompany implements Company {@Overridepublic void reflact(String name,float updateRate) {System.out.println("出口公司:"+(updateRate>=0?name+"汇率增加"+updateRate:name+"汇率减少"+(-updateRate)));}}
测试
package com.xiao.observer;public class ObserverDemo {public static void main(String[] args) {//初始化汇率Rate rmb = new RMBRate(100.0f);Rate dollar = new DollarRate(80.0f);//初始化公司Company inputCompany = new InputCompany();Company outputCompany = new OutputCompany();//添加观察者rmb.addCompany(inputCompany);rmb.addCompany(outputCompany);dollar.addCompany(inputCompany);dollar.addCompany(outputCompany);//被观察者做出改变同时通知观察者rmb.updateRate(20.0f);dollar.updateRate(-10.0f);}}/**结果:进口公司:人民币汇率增加20.0出口公司:人民币汇率增加20.0进口公司:美元汇率减少10.0出口公司:美元汇率减少10.0**/
3.4.优点
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
3.5.发布订阅模式与观察者模式
首先:发布订阅模式与观察者模式不同
- 出版者+订阅者 = 观察者模式
- 出版者+中间商+订阅者 = 发布订阅模式
在观察者模式中,出版者与订阅者相互熟知(比如上面的实例:汇率是要让公司知道,公司就是要知道汇率的变化),观察者模式的目的是通过双方的抽象结合实现松耦合。
而发布订阅模式中,出版者与订阅者完全零耦合,出版者只需要将更新的内容交给中间商,订阅者把想要知道的内容交给中间商,中间商从发布者那里取到后交给订阅者,整个过程发布者和玉订阅者没有接触,完全解耦
- 观察者模式
- 发布订阅模式
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 中间商
往更深层次讲:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
从使用层面上讲:
- 观察者模式,多用于单个应用内部
- 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件
4.装饰模式
4.1.动机
在软件组件中,如果责任划分的不清晰,使用基础得到的结果往往是随着需求的变化,子类急剧膨胀,这关键就是划清责任。
4.2.定义
动态(组合)地给一个对象增加一些额外的职责。该模式比审查子类(继承)去扩展父类功能更加灵活(消除重复代码&减少子类个数)。
4.3.实例
被装饰者基类与装饰者共同接口。
package com.xiao.decorater;public interface Shape {void draw();}
被装饰者基类
package com.xiao.decorater;public class BackgroundShape implements Shape {@Overridepublic void draw() {System.out.println("有背景了......");}}
装饰者类基类
package com.xiao.decorater;public abstract class Decorater implements Shape {protected Shape shape;public Decorater(Shape shape) {this.shape = shape;}@Overridepublic void draw(){}public abstract void decorate();}
装饰者类
package com.xiao.decorater;public class LineDecorater extends Decorater {public LineDecorater(Shape shape) {super(shape);}@Overridepublic void draw() {shape.draw();decorate();}public void decorate(){System.out.println("画了一条线");}}
package com.xiao.decorater;public class RentDecorate extends Decorater {public RentDecorate(Shape shape) {super(shape);}@Overridepublic void draw() {shape.draw();decorate();}public void decorate(){System.out.println("画了一个矩形");}}
package com.xiao.decorater;public class RedDecorate extends Decorater{public RedDecorate(Shape shape) {super(shape);}@Overridepublic void draw() {shape.draw();decorate();}@Overridepublic void decorate() {System.out.println("染红了......");}}
测试
package com.xiao.decorater;public class DecoraterDemo {public static void main(String[] args) {new RedDecorate(new LineDecorater(new RentDecorate(new BackgroundShape()))).draw();}}/**结果有背景了......画了一个矩形画了一条线染红了......**/
4.4.优点
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
5.桥接模式
5.1.动机
有时在一个类中会出现不同维度的变化内容,假设这些变化全部定义为抽象实现,那继承这个抽象类时就会出现有时不需要其中一个维度的设置却必须实现,那如果让这个维度有具体实现,那要不就是这个维度固定,要不就是再多些几个同样的类。显然不合适。
5.2.定义
桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。对于两个甚至多个对变化的维度,使用桥接模式十分合适
5.3.实例
有如下要求:要绘制一个图形,可能是矩形或圆,这个图形的颜色可以是红色或绿色。
将颜色抽象出来
package com.xiao.bridge;public interface Color {String paint();}
颜色具体实现
package com.xiao.bridge;public class GreenColor implements Color {@Overridepublic String paint() {return "绿色";}}
package com.xiao.bridge;public class RedColdr implements Color {@Overridepublic String paint() {return "红色";}}
抽象对象(有颜色与形状两个维度)
package com.xiao.bridge;public abstract class Shape {private Color color;public Shape(Color color) {this.color = color;}public Color getColor() {return color;}public abstract void draw();}
抽象对象拓展
package com.xiao.bridge;public class CircleShape extends Shape {public CircleShape(Color color) {super(color);}@Overridepublic void draw() {System.out.println("一个"+this.getColor().paint()+"的圆");}}
package com.xiao.bridge;public class RentShape extends Shape {public RentShape(Color color) {super(color);}@Overridepublic void draw() {System.out.println("一个"+this.getColor().paint()+"的矩形");}}
测试
package com.xiao.bridge;public class BridageDemo {public static void main(String[] args) {Shape rent = new RentShape(new RedColdr());Shape circle = new CircleShape(new GreenColor());rent.draw();circle.draw();}}/**结果一个红色的矩形一个绿色的圆**/
5.4.优点
- 抽象和实现的分离。
- 优秀的扩展能力。
- 实现细节对客户透明。
5.5.理解
为什么这个模式要叫桥接模式?
我的理解:
这个类中有多个维度的独立的内容,我们讲这些维度都定义为一个个的接口,这些接口就是“桥”,桥一端连接具体实现,一端连接抽象对象,通过这个“桥”把类中内容的具体实现扩展到类的外面,用户操作时使用不同维度的接口就可以了,不用关心具体的实现,这就不仅实现了解耦,也实现了高扩展性,实现了复用。
6.工厂模式
6.1.动机
- 客户只知道创建产品的工厂名,而不知道具体的产品名。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌
6.2.定义
工厂模式实现将对象实例封装到一个工厂中,用户在使用时,只需要知道对象的最终接口类和名称即可。
6.3.实例
对象最终接口
public interface Shape {void draw();}
具体对象
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}}public class Square implements Shape {@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}}public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}}
工厂
public class ShapeFactory {//使用 getShape 方法获取形状类型的对象public Shape getShape(String shapeType){if(shapeType == null){return null;}if(shapeType.equalsIgnoreCase("CIRCLE")){return new Circle();} else if(shapeType.equalsIgnoreCase("RECTANGLE")){return new Rectangle();} else if(shapeType.equalsIgnoreCase("SQUARE")){return new Square();}return null;}}
测试
public class FactoryPatternDemo {public static void main(String[] args) {ShapeFactory shapeFactory = new ShapeFactory();//获取 Circle 的对象,并调用它的 draw 方法Shape shape1 = shapeFactory.getShape("CIRCLE");//调用 Circle 的 draw 方法shape1.draw();//获取 Rectangle 的对象,并调用它的 draw 方法Shape shape2 = shapeFactory.getShape("RECTANGLE");//调用 Rectangle 的 draw 方法shape2.draw();//获取 Square 的对象,并调用它的 draw 方法Shape shape3 = shapeFactory.getShape("SQUARE");//调用 Square 的 draw 方法shape3.draw();}}/**结果Inside Circle::draw() method.Inside Rectangle::draw() method.Inside Square::draw() method.**/
6.4.优点
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
7.抽象工厂
7.2.动机
工厂模式是为了解决某一类对象实例化的屏蔽,比如用户需要的是上衣,那我们搞一个工厂,将所有的上衣实例放在工厂中,让用户去取。但有时我们会面临“一系列相互依赖的对象”的创建,比如现在用户不仅要上衣,还要裤子,鞋子等,显然把他们全部放在一个工厂里面是不合适的。这是我们就可以搞一个超级工厂->生产不同种类的工厂的工厂,这个工厂不生产具体某一种产品,所以叫做抽象工厂。
7.2.定义
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
7.3.实例
第一产品接口
public interface Shape {void draw();}
第一类产品实体
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}}public class Square implements Shape {@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}}public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}}
第二类产品接口
public interface Color {void fill();}
第二类产品实体
public class Red implements Color {@Overridepublic void fill() {System.out.println("Inside Red::fill() method.");}}public class Green implements Color {@Overridepublic void fill() {System.out.println("Inside Green::fill() method.");}}public class Blue implements Color {@Overridepublic void fill() {System.out.println("Inside Blue::fill() method.");}}
抽象工厂
public abstract class AbstractFactory {public abstract Color getColor(String color);public abstract Shape getShape(String shape) ;}
第一类产品工厂
public class ShapeFactory extends AbstractFactory {@Overridepublic Shape getShape(String shapeType){if(shapeType == null){return null;}if(shapeType.equalsIgnoreCase("CIRCLE")){return new Circle();} else if(shapeType.equalsIgnoreCase("RECTANGLE")){return new Rectangle();} else if(shapeType.equalsIgnoreCase("SQUARE")){return new Square();}return null;}@Overridepublic Color getColor(String color) {return null;}}
第二类产品工厂
public class ColorFactory extends AbstractFactory {@Overridepublic Shape getShape(String shapeType){return null;}@Overridepublic Color getColor(String color) {if(color == null){return null;}if(color.equalsIgnoreCase("RED")){return new Red();} else if(color.equalsIgnoreCase("GREEN")){return new Green();} else if(color.equalsIgnoreCase("BLUE")){return new Blue();}return null;}}
工厂生成器【用于决定选择哪种工厂】
public class FactoryProducer {public static AbstractFactory getFactory(String choice){if(choice.equalsIgnoreCase("SHAPE")){return new ShapeFactory();} else if(choice.equalsIgnoreCase("COLOR")){return new ColorFactory();}return null;}}
测试
public class AbstractFactoryPatternDemo {public static void main(String[] args) {//获取形状工厂AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");//获取形状为 Circle 的对象Shape shape1 = shapeFactory.getShape("CIRCLE");//调用 Circle 的 draw 方法shape1.draw();//获取形状为 Rectangle 的对象Shape shape2 = shapeFactory.getShape("RECTANGLE");//调用 Rectangle 的 draw 方法shape2.draw();//获取形状为 Square 的对象Shape shape3 = shapeFactory.getShape("SQUARE");//调用 Square 的 draw 方法shape3.draw();//获取颜色工厂AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");//获取颜色为 Red 的对象Color color1 = colorFactory.getColor("RED");//调用 Red 的 fill 方法color1.fill();//获取颜色为 Green 的对象Color color2 = colorFactory.getColor("Green");//调用 Green 的 fill 方法color2.fill();//获取颜色为 Blue 的对象Color color3 = colorFactory.getColor("BLUE");//调用 Blue 的 fill 方法color3.fill();}}/**结果Inside Circle::draw() method.Inside Rectangle::draw() method.Inside Square::draw() method.Inside Red::fill() method.Inside Green::fill() method.Inside Blue::fill() method.**/
7.4.优点
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
7.5.问题
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
8.原型模式
8.1.动机
在软件系统中,经常面临“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临较大的变化,但是它们却拥有比较稳定一致的接口。
8.2.定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。java直接就有原型模式的实现,就是clone方法。
8.3.实例
java克隆分为两种:
- 浅克隆
- 深克隆
- 首先要克隆的对象必须实现Clonable接口,并且重写clone方法
- 如果对象属性中没有其他对象,直接克隆就是深克隆,但是如果有,直接clone就是浅克隆,对象内部的其他对象没有完成克隆,要想实现深克隆,不仅要依赖类实现Clonable接口,重写clone方法,在当前克隆对象类中clone重写是要对依赖对象克隆重置。
- String类型由于字符串常量池的存在,无法实现深克隆.
Student类
package com.xiao.prototype;public class Student implements Cloneable{private int id;private String name;private int age;public Student(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() { return id;}public void setId(int id) { this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age; }public void setAge(int age) {this.age = age;}@Overrideprotected Student clone() throws CloneNotSupportedException {return (Student)super.clone();}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}}
测试
package com.xiao.prototype;public class ProtoDemo {public static void main(String[] args) throws CloneNotSupportedException {//创建对象Student xiao = new Student(1, "xiao", 18);System.out.println(xiao.toString());//克隆Student clone = xiao.clone();//判断是否相同System.out.println(clone==xiao);//修改克隆对象的属性clone.setId(2);clone.setName("yun");clone.setAge(19);//前后比较System.out.println(clone);System.out.println(xiao);}}/**结果Student{id=1, name='xiao', age=18}falseStudent{id=2, name='yun', age=19}Student{id=1, name='xiao', age=18}**/
含有其他对象的对象深克隆实现
Teacher类
package com.xiao.prototype;public class Teacher implements Cloneable{private int id;private String name;private Student student;public Teacher(int id, String name, Student student) {this.id = id;this.name = name;this.student = student;}public int getId() { return id;}public void setId(int id) {this.id = id;}public String getName() { return name;}public void setName(String name) {this.name = name;}public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}@Overrideprotected Teacher clone() throws CloneNotSupportedException {Teacher clone = (Teacher)super.clone();clone.setStudent(this.getStudent().clone());return clone;}@Overridepublic String toString() {return "Teacher{" +"id=" + id +", name='" + name + '\'' +", student=" + student +'}';}}
@Overrideprotected Teacher clone() throws CloneNotSupportedException {Teacher clone = (Teacher)super.clone();clone.setStudent(this.getStudent().clone());return clone;}如果要深克隆clone的重写需要设置依赖对象的clone。
测试
package com.xiao.prototype;public class ProtoDemo2 {public static void main(String[] args) throws CloneNotSupportedException {Teacher teacher = new Teacher(1, "张三", new Student(1, "xiao", 18));Teacher clone = teacher.clone();System.out.println(clone.getStudent() == teacher.getStudent());}}/**结果false**/
8.4.优点
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
9.建造者模式
9.1.动机
在软件系统中,有时会面临“一个复杂对象的创建”,其通常各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分进程要面对较大的变化,但是将他们组合起来的算法比较固定
9.2.定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。如java的StringBuilder。
9.3.模式1
经典的建造者模式下有四个角色:
- 抽象建造者(builder):描述具体建造者的公共接口,一般用来定义建造细节的方法,并不涉及具体的对象部件的创建。
- 具体建造者(ConcreteBuilder):描述具体建造者,并实现抽象建造者公共接口。
- 指挥者(Director):调用具体建造者来创建复杂对象(产品)的各个部分,并按照一定顺序(流程)来建造复杂对象。
- 产品(Product):描述一个由一系列部件组成较为复杂的对象。
这种模式适用于这个复杂对象的结构比较固定,比如要建一栋楼,确定了有五层。那用户只需要告诉指挥者每一层的细节要求,指挥者就会按照要求完成建造。
实例
就模拟造一栋四层的楼。
产品
package com.xiao.builder1;public class Product {private String floor0;private String floor1;private String floor2;private String floor3;public Product() {}public Product(String floor0, String floor1, String floor2, String floor3) {this.floor0 = floor0;this.floor1 = floor1;this.floor2 = floor2;this.floor3 = floor3;}public String getFloor0() {return floor0;}public void setFloor0(String floor0) {this.floor0 = floor0;}public String getFloor1() {return floor1;}public void setFloor1(String floor1) {this.floor1 = floor1;}public String getFloor2() {return floor2;}public void setFloor2(String floor2) {this.floor2 = floor2;}public String getFloor3() {return floor3;}public void setFloor3(String floor3) {this.floor3 = floor3;}@Overridepublic String toString() {return "Product{" +"floor0='" + floor0 + '\'' +", floor1='" + floor1 + '\'' +", floor2='" + floor2 + '\'' +", floor3='" + floor3 + '\'' +'}';}}
抽象工厂
package com.xiao.builder1;public abstract class AbstractBuilder {protected Product product;public abstract AbstractBuilder buildFloor0();public abstract AbstractBuilder buildFloor1();public abstract AbstractBuilder buildFloor2();public abstract AbstractBuilder buildFloor3();public Product getProduct(){return this.product;}}
具体工厂
package com.xiao.builder1;public class ProductBuilder extends AbstractBuilder {public ProductBuilder() {this.product = new Product();}@Overridepublic AbstractBuilder buildFloor0() {product.setFloor0("地下室建好了");return this;}@Overridepublic AbstractBuilder buildFloor1() {product.setFloor1("第一层建好了");return this;}@Overridepublic AbstractBuilder buildFloor2() {product.setFloor2("第二层建好了");return this;}@Overridepublic AbstractBuilder buildFloor3() {product.setFloor3("第三层建好了");return this;}}
指挥者
package com.xiao.builder1;public class Director {private AbstractBuilder abstractBuilder;public Director(AbstractBuilder builder) {this.abstractBuilder = builder;}public Product direct(){abstractBuilder.buildFloor0();abstractBuilder.buildFloor1();abstractBuilder.buildFloor2();abstractBuilder.buildFloor3();return abstractBuilder.getProduct();}}
测试
package com.xiao.builder1;public class Builder1Demo {public static void main(String[] args) {Product product = new Director(new ProductBuilder()).direct();System.out.println(product);}}/**结果Product{floor0='地下室建好了', floor1='第一层建好了', floor2='第二层建好了', floor3='第三层建好了'}**/
9.4.模式2
有一些情况下,一个复杂的对象的组成是不固定的,比如String类型,一个字符串里面可能包含任意东西,这就代表建造者无法确定产品的结构,当然只要给要求,建造者还是可以建造,只是由于结构不确定,指挥者就没法按照预定的计划指挥了。这时指挥者就不需要了,用户直接与建造者对接,用户给要求,建造者按照要求在产品基础上建造。这就是第二种模式,只需要三个角色
- 产品
- 抽象建造者
- 具体建造者
实例
现在要建造一间房屋,需要的有家具,喷漆,装饰
Rome
package com.xiao.builder2;import java.util.ArrayList;import java.util.List;public class Rome {//家具private List<String> furniture;//喷漆颜色private String color;//装饰(可以没有)private List<String> decorate;public Rome() {this.furniture = new ArrayList<>();this.decorate = new ArrayList<>();}public Rome(List<String> furnitures, String color, List<String> decorate) {this.furniture = furnitures;this.color = color;this.decorate = decorate;}public List<String> getFurnitures() {return furniture;}public void setFurnitures(List<String> furnitures) { this.furniture = furnitures;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public List<String> getDecorate() {return decorate;}public void setDecorate(List<String> decorate) {this.decorate = decorate;}@Overridepublic String toString() {return "Rome{" +"furniture=" + furniture +", color='" + color + '\'' +", decorate=" + decorate +'}';}}
抽象建造者
package com.xiao.builder2;public abstract class AbstractBuilder {public abstract AbstractBuilder addFurniture(String oneFurniture);public abstract AbstractBuilder removeFurniture(String oneFurniture);public abstract AbstractBuilder setColor(String newColor);public abstract AbstractBuilder addDecoration(String oneDecoration);public abstract AbstractBuilder removeDecoration(String oneDecoration);public abstract Rome getRome();}
具体建造者
package com.xiao.builder2;public class RomeBuilder extends AbstractBuilder{private Rome rome;public RomeBuilder(Rome rome) {this.rome = rome;}@Overridepublic AbstractBuilder addFurniture(String oneFurniture) {rome.getFurnitures().add(oneFurniture);return this;}@Overridepublic AbstractBuilder removeFurniture(String oneFurniture) {for(String furinture:rome.getFurnitures()){if (!oneFurniture.equals(furinture)){continue;}else {rome.getFurnitures().remove(oneFurniture);}}return this;}@Overridepublic AbstractBuilder setColor(String newColor) {this.rome.setColor(newColor);return this;}@Overridepublic AbstractBuilder addDecoration(String oneDecoration) {this.getRome().getDecorate().add(oneDecoration);return this;}@Overridepublic AbstractBuilder removeDecoration(String oneDecoration) {for(String decotate:rome.getDecorate()){if (!oneDecoration.equals(decotate)){continue;}else {rome.getDecorate().remove(oneDecoration);}}return this;}@Overridepublic Rome getRome() {return this.rome;}}
测试
package com.xiao.builder2;public class Builder2Demo {public static void main(String[] args) {Rome rome = new RomeBuilder(new Rome()).setColor("蓝色").addFurniture("沙发").addFurniture("椅子").addDecoration("壁画").addDecoration("花").getRome();System.out.println(rome);}}/**结果Rome{furniture=[沙发, 椅子], color='蓝色', decorate=[壁画, 花]}**/
9.5.优点
1、产品的建造和表示分离,实现了解耦。
2、将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
3、增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“。
10.单例模式
10.1.动机
在软件系统中,经常有一些特殊的类,必须保证他们在系统中只能存在一个实例,才能确保它们的逻辑正确性,以及良好的效率。
10.2.定义
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
10.3.6种实现
1.懒汉式,线程不安全
- 是否 Lazy 初始化:是
- 是否多线程安全:否
- 实现难度:易
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
public class Singleton {private static Singleton instance;private Singleton (){}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
问题所在:单线程下没有问题,如果是多线程,会出现线程A进入第6行后还未执行第七行线程B也进入了第六行判断通过,就会实例化两个了。
2.懒汉式,线程安全
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:易
- 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
- 优点:第一次调用才初始化,避免内存浪费。
- 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class Singleton {private static Singleton instance;private Singleton (){}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
问题所在:synchronized是悲观锁,多线程是一定是一条一条的进入,但是很多时候对这个实例的访问只是“读操作”,这样就会对性能有影响。
3.饿汉式
- 是否 Lazy 初始化:否
- 是否多线程安全:是
- 实现难度:易
- 这种方式比较常用,但容易产生垃圾对象。
- 优点:没有加锁,执行效率会提高。
- 缺点:类加载时就初始化,浪费内存。
public class Singleton {private static Singleton instance = new Singleton();private Singleton (){}public static Singleton getInstance() {return instance;}}
问题所在:它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
4.原始双检锁DCL
- 是否 Lazy 初始化:是
- 是否多线程安全:有概率不安全[指令重构问题]
- 实现难度:较复杂
public class Singleton {private static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
- 为什么要有两次判断(singleton == null):
- 第一次:懒汉式线程安全的版本的问题就是不论是有实例,都会加锁导致性能低,所以这个版本就每次先判断是否有事例,有就直接返回。否则加锁创建
- 第二次:如果不加这个判断,假设两个线程A,B先后第一次判断对象实例不存在,A将类模板加锁创建了实例,但B拿到锁后不会判断,直接创建实例,就又有了两个实例,所有要有两次判断。
- 指令重构问题[reorder]:
- 线程实际上是在CPU指令层面争抢时间片的,而对象的创建在指令层面不是一步完成的,大致有三步:1.分配内存,2.调用构造函数,3.将构造的结果放到内存中
- 按照上面代码的运行,假设有A,B两个线程,A先判断,加锁,开始创建对象,但是刚刚完成第一步分配了内存,这时对象实例就不是null了(指针不是空了),线程B抢到了时间片,判断发现不是null,就取到了实例,但是显然这个实例是不能用的。
5.Volatile双检锁DCL
- JDK 版本:JDK1.5 起
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:较复杂
相比上一种,这种方式只有一处改变,就是在实例字段前加上volatile,它的作用就是让编译器知道这个实例的创建必须是原子化的,这就避免了上面指令重构的问题。
public class Singleton {private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
6.静态内部类
- 是否 Lazy 初始化:是
- 是否多线程安全:是
- 实现难度:一般
这个方式利用了java静态内部类在初次使用时才创建的机制,能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton (){}public static final Singleton getInstance() {return SingletonHolder.INSTANCE;}}
10.4.优点
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
11.享元模式
11.1.动机
在软件系统中采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥于系统中,从而带来很高的运行时代价。
11.2.定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。在我理解中,享元模式更像是动态工厂,将一些经常使用的对象放在工厂中,当用户需要某个对象时,如果工厂中有,就直接给,如果没有,就在工厂中添加一个,以便之后用(这一点就好像Redis一样)。
模式中的角色:
- Flyweight: 享元接口,通过这个接口传入外部状态并作用于外部状态;
- ConcreteFlyweight: 具体的享元实现对象,必须是可共享的,需要封装享元对象的内部状态;
- UnsharedConcreteFlyweight: 非共享的享元实现对象,并不是所有的享元对象都可以共享,非共享的享元对象通常是享元对象的组合对象;
- FlyweightFactory: 享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口;
11.3.单纯享元模式
单纯享元模式,所有享元对象都是可以共享的,即所有的享元对象都是最小粒度的,不存在非共享具体享元类(有享元对象拼接而成)。
实例
现在要有一个消息类(Massage),这个类有字体属性类(Font),颜色属性类(Color),和消息具体的内容,这三个内容中消息的具体内容是不定的,但颜色与字体实际上可变化的范围不多。这时,如果每次实例化一个消息类就新实例化一个字体类,一个颜色类很显然会浪费很多内存,于是我们可以把常用的颜色与字体放在一个享元工厂中,用时取就可以了。
享元抽象类
package com.xiao.flyWeight;public interface FlyWeight {public String getName();}
具体享元
package com.xiao.flyWeight;public class Color implements FlyWeight{private String ColorName;public Color(String colorName) {ColorName = colorName;}public String getColorName() {return ColorName; }public void setColorName(String colorName) {ColorName = colorName;}@Overridepublic String toString() {return "Color{" +"ColorName='" + ColorName + '\'' +'}';}@Overridepublic String getName() {return this.getColorName();}}
package com.xiao.flyWeight;public class Font implements FlyWeight{private String fontName;public Font(String fontName) {this.fontName = fontName;}public String getFontName() {return fontName;}public void setFontName(String fontName) {this.fontName = fontName;}@Overridepublic String toString() {return "Font{" +"fontName='" + fontName + '\'' +'}';}@Overridepublic String getName() {return this.getFontName();}}
享元使用对象
package com.xiao.flyWeight;public class Massage {private Font font;private Color color;private String content;public Massage() {}public Massage(Font font, Color color, String content) {this.font = font;this.color = color;this.content = content;}public Font getFont() {return font;}public void setFont(Font font) {this.font = font;}public Color getColor() {return color;}public void setColor(Color color) {this.color = color;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}@Overridepublic String toString() {return "Massage{" +"font=" + font +", color=" + color +", content='" + content + '\'' +'}';}}
享元工厂
package com.xiao.flyWeight;import java.util.HashMap;public class ColorFlyFactory{protected HashMap<String,FlyWeight> flies;public ColorFlyFactory() {this.flies = new HashMap<>();}Color getColor(String colorName) {Color color;color = (Color) flies.get(colorName);if(color == null){color = new Color(colorName);flies.put(colorName,color);}return color;}}
package com.xiao.flyWeight;import java.util.HashMap;public class FontFlyFactory{protected HashMap<String,FlyWeight> flies;public FontFlyFactory() {this.flies = new HashMap<>();}Font getFont(String fontName) {Font font;font = (Font)flies.get(fontName);if(font == null){font = new Font(fontName);flies.put(fontName,font);}return font;}}
测试
package com.xiao.flyWeight;public class FlyWeightDemo {public static void main(String[] args) {ColorFlyFactory colorFlyFactory = new ColorFlyFactory();FontFlyFactory fontFlyFactory = new FontFlyFactory();Massage massage1 = new Massage();massage1.setContent("第一条消息");massage1.setFont(fontFlyFactory.getFont("宋体"));massage1.setColor(colorFlyFactory.getColor("红色"));System.out.println(massage1);Massage massage2 = new Massage();massage2.setContent("第二条消息");massage2.setFont(fontFlyFactory.getFont("宋体"));massage2.setColor(colorFlyFactory.getColor("红色"));System.out.println(massage2);System.out.println(massage1.getFont() == massage2.getFont());System.out.println(massage1.getColor() == massage2.getColor());}}/**结果Massage{font=Font{fontName='宋体'}, color=Color{ColorName='红色'}, content='第一条消息'}Massage{font=Font{fontName='宋体'}, color=Color{ColorName='红色'}, content='第二条消息'}truetrue**/
11.4.复合享元模式
单纯享元模式中享元工厂中的享元都是最小粒度的,而复合享元模式中允许从享元工厂中提取一些特殊享元组成复合享元对象。当然这个复合享元对象没有专用的用途,它实际上是轻量版的享元工厂(但是在这里面找不会每次找不到都添加,保证轻量化),比如虽然我们在享元工厂中放了许多享元,但是有一些使用频繁,我们就可以吧这些享元提出来放在这么一个对象中使用时就不用在大的工厂中遍历寻找了,提高了效率。[当然这个复合享元由于实现了享元接口,也可以放在享元工厂中]。
复合享元对象
package com.xiao.flyWeight;import java.util.*;public class ComposeFlyWeight implements FlyWeight {private HashMap<String,FlyWeight> flies = new HashMap<>();private String name;/*** 在这个复合享元中添加一系列享元* @param addFlies*/public void addFlies(Collection<FlyWeight> addFlies){for(FlyWeight addFly : addFlies){this.addFly(addFly);}}/*** 从这个享元对象中弹出指定的享元* @param popFlies* @return*/public void popFlies(Collection<FlyWeight> popFlies){Set<FlyWeight> pops = new LinkedHashSet<>();for(FlyWeight popFly : popFlies){this.popFly(popFly);}}/*** 添加一个指定的享元* @param addFly*/public void addFly(FlyWeight addFly){flies.put(addFly.getName(),addFly);}/*** 弹出一个指定的享元* @param addFly*/public void popFly(FlyWeight addFly){FlyWeight comFly = flies.get(addFly);if(comFly == null){System.out.println("不存在这个享元");}else {flies.remove(addFly);}}/*** 获取其中的享元(不同于工厂,没找到不会添加)* @param name* @return*/public FlyWeight getFly(String name){FlyWeight fly = flies.get(name);return fly;}@Overridepublic String getName() {return this.name;}}
测试
package com.xiao.flyWeight;import java.util.ArrayList;import java.util.Collection;public class ComposeFlyDemo {public static void main(String[] args) {ComposeFlyWeight compose = new ComposeFlyWeight();ColorFlyFactory colorFlyFactory = new ColorFlyFactory();FontFlyFactory fontFlyFactory = new FontFlyFactory();compose.addFly(colorFlyFactory.getColor("红色"));compose.addFly(colorFlyFactory.getColor("蓝色"));Collection<FlyWeight> fonts= new ArrayList<>();fonts.add(fontFlyFactory.getFont("宋体"));fonts.add(fontFlyFactory.getFont("楷体"));compose.addFlies(fonts);System.out.println(compose.getFly("红色"));System.out.println(compose.getFly("楷体"));System.out.println(compose.getFly("紫色"));}}/**结果Color{ColorName='红色'}Font{fontName='楷体'}null**/
11.5.优点
大大减少对象的创建,降低系统的内存,使效率提高。
12.外观模式(门面模式)
12.1.动机
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
12.2.定义
降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。注意,接口维护的子系统一般是高聚合的,这是它区别于策略模式的点,策略模式中接口对接的是平行的不同实现(比如JDBC对不同数据库的支持),但外观模式的目的是解开程序与一个高耦合复杂子系统的耦合,实现“对外松耦,对内高聚”
类似下图所示
- 不使用外观模式
- 使用外观模式
12.3.实例
模拟一个复杂的子系统太麻烦了!!!
子系统
package com.xiao.facade;public class ModuleA {public void operation(){System.out.println("A操作");}}public class ModuleB {public void operation(){System.out.println("B操作");}}public class ModuleC {public void operation(){System.out.println("C操作");}}
接口(这个接口不一定是interface,是字面意思)
package com.xiao.facade;public class Facade {public void operation(){new ModuleA().operation();new ModuleB().operation();new ModuleC().operation();}}
测试
package com.xiao.facade;public class FacadeDemo {public static void main(String[] args) {new Facade().operation();}}/**结果A操作B操作C操作**/
12.4.扩展
使用外观模式还有一个附带的好处,就是能够有选择性地暴露方法。一个模块中定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部模块之间相互调用时使用的。有了Facade类,那么用于子系统内部模块之间相互调用的方法就不用暴露给子系统外部了。
比如:有如下A,B,C模块
public class Module {/*** 提供给子系统外部使用的方法*/public void a1(){};/*** 子系统内部模块之间相互调用时使用的方法*/protected void a2(){};protected void a3(){};}
public class ModuleB {/*** 提供给子系统外部使用的方法*/public void b1(){};/*** 子系统内部模块之间相互调用时使用的方法*/protected void b2(){};protected void b3(){};}
public class ModuleC {/*** 提供给子系统外部使用的方法*/public void c1(){};/*** 子系统内部模块之间相互调用时使用的方法*/protected void c2(){};protected void c3(){};}
门面
public class ModuleFacade {ModuleA a = new ModuleA();ModuleB b = new ModuleB();ModuleC c = new ModuleC();/*** 下面这些是A、B、C模块对子系统外部提供的方法*/public void a1(){a.a1();}public void b1(){b.b1();}public void c1(){c.c1();}}
这样定义一个ModuleFacade类可以有效地屏蔽内部的细节,免得客户端去调用Module类时,发现一些不需要它知道的方法。比如a2()和a3()方法就不需要让客户端知道,否则既暴露了内部的细节,又让客户端迷惑。
12.5.优点
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
13.代理模式
13.1.动机
为其他对象提供一种代理以控制对这个对象的访问。有这样的情况:
- 有时我们不想要用户程序知道一个类的实现细节,即屏蔽这个类,但又需要这个类的功能。
- 又比如Spring的AOP,我们需要为一个类的功能做扩展。
这时就可以有一个代理对象去完成这些工作
13.2.定义
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
java有动态代理与静态代理两种,动态代理解决的是静态代理类数量急剧增多的问题,具体的实现AOP就是很好的例子了。
13.3.代理模式与装饰者模式
从代码的实现上来看,这两个模式十分相像,都是原始类与装饰者(代理者)实现同一个接口,装饰者(代理者)内部维护一个原始类实例,在原始类基础上额外做一些事。但两者的意义截然不同。
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
简单来说:
- 让别人帮助自己完成自己并不关心的事情是代理模式
- 为了让自己的能力增强,使得增强后的自己可以使用更多方法,拓展业务叫做装饰者模式。
此外,装饰者模式往往会嵌套(比如BufferedInputStream套DataInputStream套FileInputStream),但是代理模式的嵌套比较少见(尤其是动态代理)。
- 再怎么装饰,原始类的样子还在(比如不论套多少次,FileInputStream还是FileInputStream)
- 但是代理往往会让人不知道被代理的是谁,比如房屋中介。
13.4.优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
14.适配器模式
14.1.动机
在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
14.2.定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。当然前提是==当旧接口与新接口有内在共同点时才这么考虑。==比如大水管与小水管之间需要连起来(都是用来输水的,只是规格不一样),这个中间件就是适配器。
14.3.两种结构(类与对象适配)
1.类适配模式
——适配器继承需要适配的类(一般多重继承)。
类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示:
2.对象适配模式
—— 适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。
对象匹配器依赖于对象组合,如下图所示:
为什么有类适配模式与对象适配模式呢?
java是不支持多继承的,对于旧接口(被适配)与新接口(目标)有这么几种情况:
- 旧接口:接口,新接口:接口 -> 适配器可以直接impliments这两个[类适配],也可以impliments 新接口内部维护一个就旧口类型实例[对象适配]
- 旧接口:接口,新接口:类 ->同样两种都可以(但是适配器不能再继承其他的类【不论哪种方式都必须extends 新接口】)
- 旧接口:类,新接口:接口 -> 同样两种都可以(但是如果只impliments新接口,维护一个旧接口实例,适配器就可以继承其他的类了)
- 旧接口:类,新接口:类 ->(只能使用对象对象适配模式)
问题所在:由于用户是要使用适配器作为新接口的实现去兼容旧接口,那不论新接口是接口还是类都必须impliments或extends,对于就接口而言就会受限,且我们知道==组合优于继承==,使用组合不仅可以不用考虑上面的问题,还具有高扩展性(这类似于策略模式)
组成角色:
- 目标接口(Target):— 定义Client使用的与特定领域相关的接口。
- 客户角色(Client):与符合Target接口的对象协同。
- 被适配接口(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
- 适配器角色(Adapter) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
14.4.实例
目标接口
package com.xiao.adapter;public interface Target {public void showDetail();}
被适配接口
package com.xiao.adapter;public interface Adaptee {public void show(String massage);public String getMassage();}
被适配接口的一个实现
package com.xiao.adapter;public class AdapreeImpl implements Adaptee {@Overridepublic void show(String massage) {System.out.println(this.getMassage());}@Overridepublic String getMassage() {return "通过某种手段得来的消息......";}}
适配者
package com.xiao.adapter;public class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void showDetail() {String massage = adaptee.getMassage();System.out.println("消息细节:"+massage+"("+massage.length()+")");}}
客户
package com.xiao.adapter;public class AdapterDemo {public static void main(String[] args) {Adapter adapter = new Adapter(new AdapreeImpl());adapter.showDetail();}}/**结果消息细节:通过某种手段得来的消息......(17)**/
17.5.优点
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
- 在很多业务场景中符合开闭原则。
15.中介者模式
15.1.动机
在软件构建过程中,经常会出现多个对象相互关联交互的情况,它们之间常常维持一种复杂的引用关系(Web中前端页面数据库中的数据,但页面很多,数据库的数据也很多)。
15.2.定义
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则(最少知识法则)的典型应用。
15.3.实例
在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者,这就是最好的实例了。
15.4.优点
- 类之间各司其职,符合迪米特法则。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
16.状态模式
16.1.动机
在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
16.2.定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
模式结构:
- 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
16.3.实例
现在要有一个”成绩“类,有一个成员方法是针对具体的分数做出判断,这个情景就符合状态模式的特点。具体的分数就是这个成绩类的状态。我们把当前分数是要执行的逻辑封装到这个分数中
成绩类[包含状态的类],可以发现里面只有一个Score和与状态判断有关的分数,没有任何判断。
package com.xiao.state;public class Grade {private String name;private Score score;private int scoreNum;public Grade() {//默认初始化为0分this.scoreNum = 0;//默认为不及格this.score = LowScore.getInstance();}public int getScoreNum() {return scoreNum;}public void setScoreNum(int scoreNum) {this.scoreNum = scoreNum;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Score getScore() {return score;}public void setScore(Score score) {this.score = score;}@Overridepublic String toString() {return "Grade{" +"name='" + name + '\'' +", score=" + score +'}';}public void handle(){this.score.setScore(this);}}
Score分数类[状态类],里面包含了当前状态的具体执行handle()以及发现分数与状态不对应后的状态修正setScore()。
package com.xiao.state;public interface Score {/*** 当前分数要执行的逻辑*/public void handle(Grade grade);/*** 设置环境(状态的持有类)的状态*/public void setScore(Grade grade);}
具体状态[这里使用了单例模式]
package com.xiao.state;public class LowScore implements Score {private final String name = "不及格";private static volatile LowScore lowScore;/*** 双校检锁获取单例* @return 对象实例*/public static LowScore getInstance(){if(lowScore == null){synchronized (LowScore.class){if(lowScore == null){lowScore = new LowScore();}}}return lowScore;}/*** 私有构造器*/private LowScore() {}public String getName() {return name;}@Overridepublic void handle(Grade grade) {System.out.println(grade.getName()+"当前分数为:"+grade.getScoreNum()+"["+this.getName()+"]");System.out.println("需要补习吗?");}@Overridepublic void setScore(Grade grade) {int num = grade.getScoreNum();if(num<60 && num>=0)handle(grade);else if(num<90){grade.setScore(MidScore.getInstance());grade.handle();}else{grade.setScore(HighScore.getInstance());grade.handle();}}}
package com.xiao.state;public class MidScore implements Score{private final String name = "中等";private static volatile MidScore midScore;/*** 双校检锁获取单例* @return 对象实例*/public static MidScore getInstance(){if(midScore == null){synchronized (MidScore.class){if (midScore == null){midScore = new MidScore();}}}return midScore;}/*** 私有构造器*/private MidScore() {}public String getName() {return name;}@Overridepublic void handle(Grade grade) {System.out.println(grade.getName()+"当前分数为:"+grade.getScoreNum()+"["+this.getName()+"]");System.out.println("继续努力");}@Overridepublic void setScore(Grade grade) {int num = grade.getScoreNum();if(num>=60 && num<90)handle(grade);else if(num<60 && num>=0){grade.setScore(LowScore.getInstance());grade.handle();}else {grade.setScore(HighScore.getInstance());grade.handle();}}}
package com.xiao.state;//由于这个对象只是读操作,所以使用单例模式(使用双校检锁)public class HighScore implements Score{private final String name = "优秀";private static volatile HighScore highScore;/*** 双校检锁获取单例* @return 对象实例*/public static HighScore getInstance(){if(highScore == null){synchronized (HighScore.class){if(highScore == null){highScore = new HighScore();}}}return highScore;}public String getName() {return name;}/*** 私有构造器*/private HighScore() {}@Overridepublic void handle(Grade grade) {System.out.println(grade.getName()+"当前分数为:"+grade.getScoreNum()+"["+this.getName()+"]");System.out.println("再接再厉");}@Overridepublic void setScore(Grade grade) {int num = grade.getScoreNum();if(num>=90)handle(grade);else if(num>=60){grade.setScore(MidScore.getInstance());grade.handle();}else{grade.setScore(LowScore.getInstance());grade.handle();}}}
测试
package com.xiao.state;public class StateDemo {public static void main(String[] args) {Grade grade = new Grade();grade.setName("张三");grade.setScoreNum(50);grade.handle();grade.setScoreNum(65);grade.handle();grade.setScoreNum(95);grade.handle();}}/**结果张三当前分数为:50[不及格]需要补习吗?张三当前分数为:65[中等]继续努力张三当前分数为:95[优秀]再接再厉**/
16.4.状态模式与策略模式
咋一看,这两个模式是否相像,都是在对象中出现if-else if-else复杂判断结构时出现,通过一个抽象的接口,将不同的实现与类分离出去,但是不论是目的,用户使用还是实现细节都明显不同
- 目的:策略模式实际上是同一种“状态”的不同算法实现,而状态模式是不同状态下的实现。
- 实现细节:策略模式的框架比较简单,就是一个接口连接几个平行的具体实现。而状态模式不仅要实现接口与具体实现的对接,还要保证不同的具体实现之间可以完成自动的判断与切换。
- 用户使用:,当使用策略模式下的类时需要传入具体的算法实现来操作,而状态模式下,只需要传入一个状态的判断,就可以对用户隐形的实现状态的改变[就比如上面的例子,我们只需要传入分数值,就可以自动完成状态的切换]。典型的策略模式如JDBC后代数据库的切换【无论如何也实现不了数据库的自动切换,必须是用户指定好要操作的数据库才可以】简而言之:策略模式只是将具体的实现用一个接口封装了起来,而状态模式更智能化,可以后台完成状态及相爱难改观算法的切换
16.5.优点
- 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
17.备忘录模式
17.1.动机
在软件构架过程中,某些对象在状态转化过程中,可能由于某些需要,要求程序能够回溯到对象之前的处于某个点的状态。如果使用一些公共接口来让其他对象得到对象的状态,便会保留对象的实现细节。就是备份的思想
17.2.定义
在不破坏封装新的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态了。
很显然有两种情况:
- 这个对象的所有属性都需要快照保存。(完全属性备忘录)
- 这个对象只有某些属性需要保存。【比如一个访问者对象,有访问人姓名,国家,年龄,身高等属性,但你只想知道有哪些人来过,也就是只想以后恢复这个人的姓名】(部分属性备忘录)
17.3.部分属性备忘录
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
发起人对象
package com.xiao.memory;public class Person {private int id;/*** 需要保存的字段*/private String name;private int age;public Person(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() {return id;}public String getName() {return name; }public int getAge() {return age; }public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}public PersonMemo creatMemo(){PersonMemo personMemo = new PersonMemo(this.name);return personMemo;}}
备忘录角色
package com.xiao.memory;public class PersonMemo {private String name;public PersonMemo(String name) {this.name = name;}public PersonMemo() {}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "PersonMemo{" +"name='" + name + '\'' +'}';}}
管理者角色
package com.xiao.memory;import java.util.ArrayList;import java.util.List;public class PersonMemoTaker {/*** 存放一系列Person对象的快照*/List<PersonMemo> personMemos;public PersonMemoTaker() { personMemos = new ArrayList<>();}public PersonMemoTaker(List<PersonMemo> personMemos) {this.personMemos = personMemos;}public PersonMemo getPersonMemo(int index) {return personMemos.get(index);}public void addPersonMemo(PersonMemo personMemo){personMemos.add(personMemo);}}
测试
package com.xiao.memory;public class MemoryDemo {public static void main(String[] args) {//创建备忘录管理者PersonMemoTaker personMemoTaker = new PersonMemoTaker();Person xiao = new Person(1, "xiao", 18);PersonMemo xiaoMemo = xiao.creatMemo();personMemoTaker.addPersonMemo(xiaoMemo);xiao.setName("yun");System.out.println(xiao);System.out.println(personMemoTaker.getPersonMemo(0).getName());}}/**结果Person{id=1, name='yun', age=18}xiao**/
17.4.完全属性备忘录
完全属性备忘就是要求对象的所有属性都要快照保存,这显然就是原型模式的使用,我们不再需要备忘录角色,只需要发起者对象实现clonable接口(当然所有对象字段都要实现clonable),实现深拷贝,每次的备份就是直接clone()就可以了。交由管理者管理。
17.5.优点
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起者类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
18.组合模式
18.1.动机
在软件某些情况下,客户过多的依赖于对象容器复杂的内部结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来代码的维护性,扩展性等弊端。
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
18.2.定义
有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
注意:这里的组合实际上就是多个对象放在了一起,组合模式的核心组件始终只有一个对象
18.3.透明方式
在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
公共接口
package com.xiao.composite;public interface Component {/*** 添加一个组件(枝节点实现)* @param component*/public void add(Component component);/*** 移除一个组件(枝节点实现)* @param component*/public void remove(Component component);/*** 获得一个子组件(枝节点实现)* @param index*/public Component getChild(int index);/*** 所有叶节点都有的操作*/public void operation();}
叶子节点
package com.xiao.composite;public class Leaf implements Component {private String name;public Leaf(String name) {this.name = name;}@Overridepublic void add(Component component) {}@Overridepublic void remove(Component component) {}@Overridepublic Component getChild(int index) {return null;}@Overridepublic void operation() {System.out.println("树叶"+this.name+"被访问了");}}
枝节点
package com.xiao.composite;import java.util.ArrayList;import java.util.List;public class Composite implements Component {List<Component> leaves;public Composite() {leaves = new ArrayList<>();}public Composite(List<Component> leaves) {this.leaves = leaves;}@Overridepublic void add(Component component) {leaves.add(component);}@Overridepublic void remove(Component component) {leaves.remove(component);}@Overridepublic Component getChild(int index) {return leaves.get(index);}@Overridepublic void operation() {leaves.forEach((L)-> L.operation());}}
测试
package com.xiao.composite;public class ComponentDemo {public static void main(String[] args) {Component composite1 = new Composite();Component composite2 = new Composite();Leaf leaf1 = new Leaf("叶子1");Leaf leaf2 = new Leaf("叶子2");Leaf leaf3 = new Leaf("叶子3");/**结构* composite1:* composite2:* leaf1* leaf2* leaf3*/composite1.add(composite2);composite2.add(leaf1);composite2.add(leaf2);composite1.add(leaf3);composite2.operation();System.out.println("==========================");composite1.operation();}}/**结果树叶叶子1被访问了树叶叶子2被访问了==========================树叶叶子1被访问了树叶叶子2被访问了树叶叶子3被访问了**/
18.4.安全方式
安全式的组合模式与透明式组合模式的实现代码类似,只要对其做简单修改就可以了,既然问题出在公共接口中有叶子类无法使用的方法,那就不放,只在接口中方公共的方法operation(),当然因为枝节点需要使用,那么用户使用时就需要将枝节点声明为Composite,而不能是接口,就不是完全透明的了。
interface Component {public void operation();}
package com.xiao.composite;public class ComponentDemo {public static void main(String[] args) {Composite composite1 = new Composite();Composite composite2 = new Composite();Leaf leaf1 = new Leaf("叶子1");Leaf leaf2 = new Leaf("叶子2");Leaf leaf3 = new Leaf("叶子3");/**结构* composite1:* composite2:* leaf1* leaf2* leaf3*/composite1.add(composite2);composite2.add(leaf1);composite2.add(leaf2);composite1.add(leaf3);composite2.operation();System.out.println("==========================");composite1.operation();}}
18.5.优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
19.迭代器模式
19.1.动机
软件中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
19.2.定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式。Java 中的 Collection、List、Set、Map 等都包含了迭代器
19.3.实例
Java的集合中的迭代器就是最典型的例子了,不论是List还是Set还是Map,都可以直接转化为一个迭代器类型交给用户,用户不用关系这个迭代器的原型,只需要迭代就可以了。
19.4.优点
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口
20.责任链模式
20.1.动机
在软件构建过程中,一个请求可能被多个对象处理,但是每个请求运行时只能有一个接受者,如果显示指定,将必不可少的带来请求发送者与接受者的紧耦合。
20.2.定义
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
20.3.实例
JAVA Web中的Filter就是典型的责任链模式,在这条请求处理链路上,从第一个开始每个节点都会判断这个请求是否是自己处理的以及交给谁处理。Filter就是这样,对发送来的请求,要判断这个请求是否符合条件,是就放行,否则就拦截。
注意:责任链不一定只有一条,比如Filter可以为多个Servlet拦截【如配置拦截请求为”/“所有的请求都会被拦截,判断后根据请求路径交给对应的Servlet】。
20.4.优点
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
21.命令模式
21.1.动机
在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。
21.2.定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
一般命令模式中会出现四个角色:
- 命令(包括抽象接口与具体实现,,也可以是组合模式诞生的组合命令),
- 命令接受者(实际上就是命令执行的对象),
- 执行者(里面可以封装一个【当然也可以是多个】命令以便调用),
- 客户(使用执行者最终执行命令)。
当然,有时命令的内容可能是比较简单的,不是操作某个对象的,也就是说没有命令接受者。我们模拟这个
21.2.实例
命令抽象类
package com.xiao.command;public interface Command {public void execute();}
命令具体实现类
package com.xiao.command;public class CommandA implements Command {@Overridepublic void execute() {System.out.println("命令A执行");}}
package com.xiao.command;public class CommandB implements Command {@Overridepublic void execute() {System.out.println("命令B执行");}}
package com.xiao.command;public class CommandC implements Command {@Overridepublic void execute() {System.out.println("命令C执行");}}
package com.xiao.command;import java.util.ArrayList;import java.util.List;/***组合命令**/public class ComposeCommand implements Command {List<Command> commands;public ComposeCommand() {this.commands = new ArrayList<>();}@Overridepublic void execute() {this.commands.forEach(C->C.execute());}public void addCommand(Command command){this.commands.add(command);}public void removeCommand(Command command){this.commands.remove(command);}public Command getCommond(int index){return this.commands.get(index);}}
执行者
package com.xiao.command;public class Invoker {private Command cmd;private String name;public Invoker() {}public Invoker(Command cmd, String name) {this.cmd = cmd;this.name = name;}public void setCmd(Command cmd) {this.cmd = cmd;}public void setName(String name) {this.name = name;}public void call(){System.out.println("命令执行者:"+this.name);this.cmd.execute();}}
测试
package com.xiao.command;public class CommandDemo {public static void main(String[] args) {Invoker invoker = new Invoker();invoker.setName("张三");/***传入单纯命令执行*/Command command1 = new CommandA();Command command2 = new CommandB();invoker.setCmd(command1);invoker.call();invoker.setCmd(command2);invoker.call();/*** 传入组合命令执行*/System.out.println("==================================");ComposeCommand composeCommand = new ComposeCommand();composeCommand.addCommand(command1);composeCommand.addCommand(command2);composeCommand.addCommand(new CommandC());invoker.setCmd(composeCommand);invoker.call();}}/**结果命令执行者:张三命令A执行命令执行者:张三命令B执行==================================命令执行者:张三命令A执行命令B执行命令C执行**/
21.4.优点
22.访问者模式
22.1.动机
在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便
访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
22.2.定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
当系统中存在类型数量稳定(固定)的一类数据结构,或是可比遍历,可迭代的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。
注意,访问者模式的关键是不同的访问者会议不同的方式访问几乎固定的内容。比如一个类中有固定的ElementA与ElementB,这两者的属性是固定的,但是由于访问对象的不同,可能会对这两个Element有不同的操作。
还有,这个模式中访问者与访问元素是双向依赖的。
22.3.实例
就模拟上面的假设。有两个Visitor要访问一个类中的ElementA与ElementB,但是访问方式不同。
Element抽象类
package com.xiao.visitor;public interface Element {/*** 被访问元素被访问后的实现* @param visitor*/public void accept(Visitor visitor);}
Element实现类
package com.xiao.visitor;public class ElementA implements Element {@Overridepublic void accept(Visitor visitor) {System.out.println(visitor.visit(this)+"访问到ElementA");}}
package com.xiao.visitor;public class ElementB implements Element {@Overridepublic void accept(Visitor visitor) {System.out.println(visitor.visit(this)+"访问到ElementB");}}
访问者抽象类
package com.xiao.visitor;public interface Visitor {/*** 访问者访问那个元素* @param element*/public String visit(Element element);}
访问者实现类
package com.xiao.visitor;public class VisitorA implements Visitor {private String name;public VisitorA(String name) {this.name = name;}@Overridepublic String visit(Element element) {return this.name+"-----------";}}
package com.xiao.visitor;public class VisitorB implements Visitor {private String name;public VisitorB(String name) {this.name = name;}@Overridepublic String visit(Element element) {return this.name+"-----------";}}
被访问元素持有者[这里采用了可遍历的Element数组]
package com.xiao.visitor;import java.util.ArrayList;import java.util.List;/*** 持有元素的真实对象*/public class ElementObject {private List<Element> elements;public ElementObject() {this.elements = new ArrayList<>();}public void addElement(Element element){elements.add(element);}public void removeElement(Element element){elements.remove(element);}public void handle(Visitor visitor){elements.forEach(E->E.accept(visitor));}}
测试
package com.xiao.visitor;public class VisitorDemo {public static void main(String[] args) {ElementObject object = new ElementObject();object.addElement(new ElementA());object.addElement(new ElementB());object.handle(new VisitorA("张三"));System.out.println("==========切换访问者============");object.handle(new VisitorB("李四"));}}/**结果张三-----------访问到ElementA张三-----------访问到ElementB==========切换访问者============李四-----------访问到ElementA李四-----------访问到ElementB**/
22.4.优点
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
23.解释器模式
23.1.动机
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
22.2.定义
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这个模式的使用可以说是极少的,比如说将固定格式的字符串转化为一种行为,比如”a+b-c”将这个字符串中的算是计算出来,这就是解释器模式的应用。
22.3.实例
编译器、运算表达式计算。
一般不会用到,就不写了。
22.4.优点
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
总结
1.创建型设计模式(简单来说就是用来创建对象的)
| 设计模式 | 简述 | 一句话归纳 | 目的 | 生活案例 |
|---|---|---|---|---|
| 工厂模式(Factory Pattern) | 不同条件下创建不同实例 | 产品标准化,生产更高效 | 封装创建细节 | 实体工厂 |
| 单例模式(Singleton Pattern) | 保证一个类仅有一个实例,并且提供一个全局访问点 | 世上只有一个我 | 保证独一无二 | CEO |
| 原型模式(Prototype Pattern) | 通过拷贝原型创建新的对象 | 拔一根猴毛,吹出千万个 | 高效创建对象 | 克隆 |
| 建造者模式(Builder Pattern) | 用来创建复杂的复合对象 | 高配中配和低配,想选哪配就哪配 | 开放个性配置步骤 | 选配 |
2.结构型设计模式(关注类和对象的组合)
| 设计模式 | 简述 | 一句话归纳 | 目的 | 生活案例 |
|---|---|---|---|---|
| 代理模式(Proxy Pattern) | 为其他对象提供一种代理以控制对这个对象的访问 | 没有资源没时间,得找别人来帮忙 | 增强职责 | 媒婆 |
| 外观模式(Facade Pattern) | 对外提供一个统一的接口用来访问子系统 | 打开一扇门,通向全世界 | 统一访问入口 | 前台 |
| 装饰器模式(Decorator Pattern) | 为对象添加新功能 | 他大舅他二舅都是他舅 | 灵活扩展、同宗同源 | 煎饼 |
| 享元模式(Flyweight Pattern) | 使用对象池来减少重复对象的创建 | 优化资源配置,减少重复浪费 | 共享资源池 | 全国社保联网 |
| 组合模式(Composite Pattern) | 将整体与局部(树形结构)进行递归组合,让客户端能够以一种的方式对其进行处理 | 人在一起叫团伙,心在一起叫团队 | 统一整体和个体 | 组织架构树 |
| 适配器模式(Adapter Pattern) | 将原来不兼容的两个类融合在一起 | 万能充电器 | 兼容转换 | 电源适配 |
| 桥接模式(Bridge Pattern) | 将两个能够独立变化的部分分离开来 | 约定优于配置 | 不允许用继承 | 桥 |
3.行为型设计模式(关注对象之间的通信)
| 设计模式 | 简述 | 一句话归纳 | 目的 | 生活案例 |
|---|---|---|---|---|
| 模板模式(Template Pattern) | 定义一套流程模板,根据需要实现模板中的操作 | 流程全部标准化,需要微调请覆盖 | 逻辑复用 | 把大象装进冰箱 |
| 策略模式(Strategy Pattern) | 封装不同的算法,算法之间能互相替换 | 条条大道通罗马,具体哪条你来定 | 把选择权交给用户 | 选择支付方式 |
| 责任链模式(Chain of Responsibility Pattern) | 拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 | 各人自扫门前雪,莫管他们瓦上霜 | 解耦处理逻辑 | 踢皮球 |
| 迭代器模式(Iterator Pattern) | 提供一种方法顺序访问一个聚合对象中的各个元素 | 流水线上坐一天,每个包裹扫一遍 | 统一对集合的访问方式 | 逐个检票进站 |
| 命令模式(Command Pattern) | 将请求封装成命令,并记录下来,能够撤销与重做 | 运筹帷幄之中,决胜千里之外 | 解耦请求和处理 | 遥控器 |
| 状态模式(State Pattern) | 根据不同的状态做出不同的行为 | 状态驱动行为,行为决定状态 | 绑定状态和行为 | 订单状态跟踪 |
| 备忘录模式(Memento Pattern) | 保存对象的状态,在需要时进行恢复 | 失足不成千古恨,想重来时就重来 | 备份、后悔机制 | 草稿箱 |
| 中介者模式(Mediator Pattern) | 将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散 | 联系方式我给你,怎么搞定我不管 | 统一管理网状资源 | 朋友圈 |
| 解释器模式(Interpreter Pattern) | 给定一个语言,定义它的语法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子 | 我想说”方言“,一切解释权都归我 | 实现特定语法解析 | 摩斯密码 |
| 观察者模式(Observer Pattern) | 状态发生改变时通知观察者,一对多的关系 | 到点就通知我 | 解耦观察者与被观察者 | 闹钟 |
| 访问者模式(Visitor Pattern) | 稳定数据结构,定义新的操作行为 | 横看成岭侧成峰,远近高低各不同 | 解耦数据结构和数据操作 | KPI考核 |
