设计模式学习笔记整理手册
一、GoF设计模式的分类
- 创建型
主要用于创建对象 - 结构型
主要用于处理类与对象的组合 - 行为型
主要用于描述类与对象怎么交互和分配职责的
1.1 创建型
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 工厂方法模式(Factory Method)
- 原型模式(Prototype)
- 单例模式(Singleton)
1.2 结构型
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
1.3 行为型
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
二、设计原则概述
2.1 面向对象设计原则概述:
- 单一职责原则(Single Responsibility Principle, SRP)
- 开闭原则(Open-Closed Principle, OCP)
- 里氏代换原则(Liskov Substitution Principle, LSP)
- 依赖倒转原则(Dependency Inversion Principle, DIP)
- 接口隔离原则(Interface Segregation Principle, ISP)
- 合成复用原则(Composite Reuse Principle, CRP)
- 迪米特法则(Law of Demeter, LoD)
设计原则名称 | 设计原则简介 |
---|---|
单一职责原则 | 类的职责要单一,不要将太多的职责放在一个类中 |
开闭原则 | 软件实体对拓展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上拓展其功能 |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以一个子类对象 |
依赖倒转原则 | 要针对抽象层编程,而不针对具体类编程 |
接口隔离原则 | 使用多个接口来替代一个统一的接口 |
合成复用原则 | 在系统中尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系 |
迪米特法则 | 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 |
2.2 单一职责原则
单一职责原则定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
单一职责原则是实现高内聚、低耦合的指导方针
2.3 开闭原则
开闭原则定义:一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。
2.4 里氏代换原则
里氏代换原则严格的定义:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
更容易理解的定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。也可以说在软件系统中,一个可以接受基类对象的地方必然可以一个子类对象
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
2.5 依赖倒转原则
依赖倒转原则定义:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。也就是说要针对接口编程,不要针对实现编程
依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。
2.6 接口隔离原则
接口隔离原则定义:一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。也就是说使用多个接口来替代一个统一的接口
2.7 合成复用原则
合成复用原则定义:又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP),其定义如下:尽量使用对象组合,而不是继承来达到复用的目的。简言之:要尽量使用组合/聚合关系,少用继承。
2.8 迪米特法则
迪米特法则(Law of Demeter, LoD)又称为最少知识原则(Least Knowledge Principle, LKP),它有多种定义方法,其中几种典型定义如下:
- (1) 不要和“陌生人”说话。英文定义为:Don't talk to strangers.
- (2) 只与你的直接朋友通信。英文定义为:Talk only to your immediate friends.
- (3) 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。英文定义为:Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
迪米特法则可分为狭义法则和广义法则。在狭义的迪米特法则中,如果两个类之间不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
三、创建型设计模式
3.1 抽象工厂模式
3.1.1 模式定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
3.1.2 模式角色
抽象工厂模式包含如下角色:
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
Product:具体产品
AbstractProduct:抽象产品
3.1.3 简单例子
抽象工厂类:
public abstract class AbstractFactory
{
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}
具体工厂类:
public class ConcreteFactory1 extends AbstractFactory
{
public AbstractProductA createProductA()
{
return new ConcreteProductA1();
}
public AbstractProductB createProductB()
{
return new ConcreteProductB1();
}
}
3.1.4 抽象工厂模式和工厂模式的区别
抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
3.2 建造者模式
3.2.1 模式定义
建造者模式属于23种设计模式中的创建型模式,可以理解为创建对象的一种很好的方法。
所谓建造者模式就是将组件和组件的组件过程分开,然后一步一步建造一个复杂的对象。所以建造者模式又叫生成器模式。
3.2.2 模式结构
建造者模式包含如下角色
Builder:抽象建造者
ConcreteBuilder:具体建造者
Director:指挥者
Product:产品角色
如果系统只需要一个具体的建造者类的时候,可以省略抽象建造者,有时候指挥者类也可以省略,让建造者类同时充当指挥者和建造者
3.2.3 简单实例
下面给出一个简单例子
产品角色类
public class Product
{
private String partA;
private String partB;
private String partC;
//...省略set、get方法
}
抽象建造者类定义了产品的创建方法和返回方法
public abstract class Builder
{
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getResult()
{
return product;
}
具体建造者类,实现抽象建造者类接口
public class ConcreteBuilder implements Builder {
Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建partA的代码
};
public void buildPartB() {
//这里是具体如何构建partB的代码
};
public void buildPartC() {
//这里是具体如何构建partB的代码
};
public Product getResult() {
//返回最后组装成品结果
};
}
指挥者类,一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程
public class Director
{
private Builder builder;
public Director(Builder builder)
{
this.builder=builder;
}
public void setBuilder(Builder builder)
{
this.builder=builer;
}
public Product construct()
{
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
然后客户端调用,在客户端代码中,无须关心产品对象的具体组装过程,只需确定具体建造者的类型即可
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
3.2.4 模式应用
最常见的就是StringBuilder;
JDBC的PreparedStatement类
蚂蚁金服的蚂蚁庄园小鸡的装扮实现可以通过建造者模式设计
优缺点
优点:
客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦。
增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
缺点:
如果内部建造组件的方法经常变动,这种情况就不适合建造者模式了
建造者模式虽然很好的解耦,但是和单例模式比起来,可能造成过多的创建类对象,给JVM造成负载,当然在适当的场景应用也是可以提高性能的,比如StringBuilder的应用
3.2.5 模式比较
通过学习,我们发现建造模式和抽象工厂模式似乎有点类似,所以我们对比一下两种模式
抽象工厂模式:在客户端调用时,只是实例工厂类,然后调用工厂类对应的方法
建造者模式:在客户端调用时,可以通过指挥者指挥生成对象,返回的是一个完整的对象
3.3 工厂方法模式
3.3.1 模式定义
工厂方法模式:又称工厂模式,也叫虚拟构造器模式,属于构建型设计模式,工厂方法模式是在简单工厂模式上进行拓展,生产产品的过程由具体工厂类实现,基类只实现接口,这使得工厂方法模式可以在不修改工厂角色的情况下,引进新的产品。
工作方法模式也符合”开闭原则“。工厂方法模式也称虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式
3.3.2 模式结构
工厂方法模式包含如下结构:
Product:抽象产品
ConcreteProduct:具体产品
Factory:抽象工厂
ConcreteFactory:具体工厂
3.3.3 简单实例
抽象工厂类:
public abstract class PayMethodFactory
{
public abstract AbstractPay getPayMethod();
}
具体工厂类:
public class CashPayFactory extends PayMethodFactory
{
public AbstractPay getPayMethod()
{
return new CashPay();
}
}
客户端调用:
PayMethodFactory factory;
AbstractPay payMethod;
factory=new CashPayFactory();
payMethod =factory.getPayMethod();
payMethod.pay();
三种工厂方法对比:https://blog.csdn.net/u014427391/article/details/80067882
3.4 原型模式
3.4.1 模式定义
原型模式(Prototype Pattern):原型模式是提供一个原型接口,提供原型的克隆,创建新的对象,是一种对象创建型模式。
3.4.2 模式结构
原型模式包括如下角色
- Prototype :抽象原型类
- ConcretePrototype:具体原型类
- Client:客户类
3.4.3 原型模式类别
一个类包括另外一个成员变量,在使用原型模式进行对象克隆时,如果直接是通过super Cloneable接口的的clone方法,这种情况其实并不支持类中另外一些成员变量的克隆的,这种方法称之为浅克隆,所以浅克隆和深克隆的本质区别就是看其是否支持类中的成员变量的克隆。
综上,原型模式可以浅克隆和深克隆两种情况,其区别是是否支持类中的成员变量的克隆。
原型模式的浅克隆
原型模式在Java里的常用实现是通过类继承 JDK提供的Cloneable接口,重写 clone(),这种方法其实也可以称之为原型模式的浅克隆
public class A implements Cloneable
{
public Object clone()
{
A clone=null;
try
{
clone=(A)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("Clone failure!");
}
return clone;
}
}
一般来说,clone方法符合:
- 类型相同:对于任何对象a,a.clone().getClass() = a.getClass()
- 内存地址不同:也可以说对于任何对象a,a.clone()!=a,克隆对象和原对象不是同一个对象
- a对象的equals方法:对于任何对象a,a.clone().equals(a)
浅克隆的例子,例子来自《设计模式》一书的邮件复制
由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。在本实例中使用浅克隆实现邮件复制,即复制邮件(Email)的同时不复制附件(Attachment)。
附件类:
public class Attachment
{
public void download()
{
System.out.println("下载附件");
}
}
邮件类,浅克隆:
public class Email implements Cloneable
{
private Attachment attachment=null;
public Email()
{
this.attachment=new Attachment();
}
public Object clone()
{
Email clone=null;
try
{
clone=(Email)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("Clone failure!");
}
return clone;
}
public Attachment getAttachment()
{
return this.attachment;
}
public void display()
{
System.out.println("查看邮件");
}
}
客户端类:
public class Client
{
public static void main(String a[])
{
Email email,copyEmail;
email=new Email();
copyEmail=(Email)email.clone();
System.out.println("email==copyEmail?");
System.out.println(email==copyEmail);
System.out.println("email.getAttachment==copyEmail.getAttachment?");
System.out.println(email.getAttachment()==copyEmail.getAttachment());
}
}
编译返回,第一个是false,第二个是true,由前面的理论可以知道,浅克隆对于成员变量是不支持克隆的,因为对象地址还是一样的
原型模式的深克隆
上面是浅克隆的实现,对于原型模式深克隆的实现一般是提供类的序列化来实现
附件类,注意要implements Serializable
import java.io.*;
public class Attachment implements Serializable
{
public void download()
{
System.out.println("下载附件");
}
}
邮件类,同样要实现Serializable接口
import java.io.*;
public class Email implements Serializable
{
private Attachment attachment=null;
public Email()
{
this.attachment=new Attachment();
}
public Object deepClone() throws IOException, ClassNotFoundException, OptionalDataException
{
//将对象写入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return(ois.readObject());
}
public Attachment getAttachment()
{
return this.attachment;
}
public void display()
{
System.out.println("查看邮件");
}
}
客户端类:
public class Client
{
public static void main(String a[])
{
Email email,copyEmail=null;
email=new Email();
try{
copyEmail=(Email)email.deepClone();
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("email==copyEmail?");
System.out.println(email==copyEmail);
System.out.println("email.getAttachment==copyEmail.getAttachment?");
System.out.println(email.getAttachment()==copyEmail.getAttachment());
}
}
编译返回,第一个是false,第二个是flase,由前面的理论可以知道,深克隆对于成员变量是支持克隆的,因为对象地址是一样的
原型管理器
原型管理器是原型模式的拓展
例子同样来自《设计模式》一书
import java.util.*;
interface MyColor extends Cloneable
{
public Object clone();
public void display();
}
class Red implements MyColor
{
public Object clone()
{
Red r=null;
try
{
r=(Red)super.clone();
}
catch(CloneNotSupportedException e)
{
}
return r;
}
public void display()
{
System.out.println("This is Red!");
}
}
class Blue implements MyColor
{
public Object clone()
{
Blue b=null;
try
{
b=(Blue)super.clone();
}
catch(CloneNotSupportedException e)
{
}
return b;
}
public void display()
{
System.out.println("This is Blue!");
}
}
class PrototypeManager
{
private Hashtable ht=new Hashtable();
public PrototypeManager()
{
ht.put("red",new Red());
ht.put("blue",new Blue());
}
public void addColor(String key,MyColor obj)
{
ht.put(key,obj);
}
public MyColor getColor(String key)
{
return (MyColor)((MyColor)ht.get(key)).clone();
}
}
class Client
{
public static void main(String args[])
{
PrototypeManager pm=new PrototypeManager();
MyColor obj1=(MyColor)pm.getColor("red");
obj1.display();
MyColor obj2=(MyColor)pm.getColor("red");
obj2.display();
System.out.println(obj1==obj2);
}
}
3.4.4 模式应用
原型模式适用的场景
-
保存对象的状态:对于要保存的状态不是很占内存的情况,可以适用原型模式和备忘录模式保存对象状态,如果对象占用太多内存,那就还是状态模式比较好
-
创建新对象成本很大的情况:比如创建一个对象是需要查询很慢的SQL才能给对象赋值,这种情况就和适合用原型模式克隆对象,减少对象创建和查询
原型模式应用的场景
- 对于很多软件的复制和粘贴实现其实也是原型模式的应用
- Spring框架提供BeanUtils.copyProperties方法也是原型模式的应用
3.5 单例模式
3.5.1 前言
本博客介绍一种创建型模式:单例模式
这是一种比较容易理解的设计模式,可以理解为创建对象的一种很好的做法。可以尽量避免创建过多的对象,给JVM造成很大的负载。
3.5.2 应用场景
单例模式的一些应用场景:
1、比如数据连接类,这是需要经常调用的
2、网站访问量统计的服务类,需要多次调用
3、导出导入Excel表,一些业务复杂的系统需要多次调用
...
总结起来就是需要经常调用的通用类,我们可以用单例模式进行很好的设计。
编程思想
单例模式涉及了两种重要的编程思想:懒加载思想和缓存思想
缓存思想:
private static Singleton instance = null;//先放内存缓存
public static Singleton getInstance() {
if (instance == null) {//内存加载不到,创建对象
instance = new Singleton();
}
return instance;//内存缓存有,直接调用
}
懒加载思想:
下面例子就是懒加载的简单应用,创建一个对象都是需要用的时候实例,尽量不要在加载类的时候就实例了,这种方法可以很好的避免给JVM增加负载。这是一种很好的编程习惯。
public static Singleton getInstance() {
if (instance == null) {//对象需要用时才实例
instance = new Singleton();
}
return instance;
}
3.5.3 单例模式实例
下面介绍几种常用的单例模式实例
1、懒汉模式
这是一种线程不安全,懒加载的方式
public class Singleton {
private static Singleton instance;
//定义private构造函数,使类不可以被实例
private Singleton (){}
/**
* 懒汉模式,线程不安全,懒加载
* @return
*/
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面例子线程不安全,要线程安全可以加个同步锁,不过加了同步锁性能又不好了,加载慢
public class Singleton {
private static Singleton instance;
//定义private构造函数,使类不可以被实例
private Singleton (){}
/**
* 懒汉模式,线程安全,懒加载
* @return
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、饿汉模式
下面介绍一下单例模式的另外一种实现方式,饿汉模式
其实现原理就是在类内部全局new一个对象,利用Java虚拟机的类加载机制,保证了线程安全,不过很明显,一创建了,就实例了单例类,会给JVM增加负载
public class Singleton {
//定义private构造函数,使类不可以被实例
private Singleton (){}
//加载类的时候,利用JVM的类加载机制创建对象,保证了线程安全,但是效率不好
private static Singleton instance = new Singleton();
/**
* 饿汉模式,线程安全,非懒加载
* @return
*/
public static Singleton getInstance() {
return instance;
}
}
3、双检锁/双重校验锁(DCL,即 double-checked locking)
下面介绍一种双检锁的实现方式,这种方式看起来稍稍比较复杂了点,不过可以实现线程安全,同时双检锁的方式可以保证性能比较高
public class Singleton {
//定义private构造函数,使类不可以被实例
private Singleton (){}
private volatile static Singleton instance;
/**
* 双检锁/双重校验锁(DCL,即 double-checked locking)线程安全,懒加载
* @return
*/
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4、登记式/内部类
下面用内部类的方式来实现单例模式,这种方式可以和饿汉模式来对比一下
这种方式和刚才介绍的饿汉模式类似,不过区别就是做到了懒加载,我们可以分析例子。方法就是在单例类里加个内部类,这样做就不会像饿汉模式一样,单例类一加载就实例对象。当调用getInstance方法的时候,才会调用,创建对象。这样就做到了懒加载,同时也是利用JVM保证了线程安全
public class Singleton {
//定义private构造函数,使类不可以被实例
private Singleton (){}
public static class SingletonHolder{
private final static Singleton INSTANCE = new Singleton();
}
/**
* 登记式/静态内部类,线程安全,懒加载
* @return
*/
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
5、枚举模式
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
四、结构型设计模式
4.1 适配器模式
4.1.1 模式定义
适配器模式(Adapter Pattern):将一个接口转换成客户希望的接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
4.1.2 模式角色
适配器模式包括如下角色
- Traget(目标抽象类)
- Adapter(适配器类)
- Adaptee(适配者类)
- Client(客户类)
4.1.3 模式分析
适配器模式将目标类和适配者类解耦,引入一个适配器类来重用适配者类,具体的实现是在适配者类的,通过适配器模式,客户端类就不知道适配者类的具体实现了,
典型的类适配器代码:
public class Adapter extends Adaptee implements Target
{
public void request()
{
specificRequest();
}
}
典型的对象适配器代码:
public class Adapter extends Adaptee implements Target
{
public void request()
{
specificRequest();
}
}
4.1.4 模式例子
某系统需要提供一个加密模块,将用户信息(如密码等机密信息)加密之后再存储在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。使用适配器模式设计该加密模块,实现在不修改现有类的基础上重用第三方加密方法
本例子来自《设计模式》一书
目标类:
public abstract class DataOperation
{
private String password;
public void setPassword(String password)
{
this.password=password;
}
public String getPassword()
{
return this.password;
}
public abstract String doEncrypt(int key,String ps);
}
适配者类:
public final class Caesar
{
public String doEncrypt(int key,String ps)
{
String es="";
for(int i=0;i<ps.length();i++)
{
char c=ps.charAt(i);
if(c>='a'&&c<='z')
{
c+=key%26;
if(c>'z') c-=26;
if(c<'a') c+=26;
}
if(c>='A'&&c<='Z')
{
c+=key%26;
if(c>'Z') c-=26;
if(c<'A') c+=26;
}
es+=c;
}
return es;
}
}
适配器类:
public class CipherAdapter extends DataOperation
{
private Caesar cipher;
public CipherAdapter()
{
cipher=new Caesar();
}
public String doEncrypt(int key,String ps)
{
return cipher.doEncrypt(key,ps);
}
}
public class NewCipherAdapter extends DataOperation
{
private NewCipher cipher;
public NewCipherAdapter()
{
cipher=new NewCipher();
}
public String doEncrypt(int key,String ps)
{
return cipher.doEncrypt(key,ps);
}
}
4.1.5 模式分类
适配器模式可以分为默认适配器和双向适配器
默认适配器
在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。
双向适配器
如果在对象适配器中,在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么这个适配器就是一个双向适配器
4.1.6 模式应用
- JDBC驱动软件都是一个介于jdbc接口和数据库引擎接口之间的适配器软件。JDBC驱动软件使java程序可以适配各种数据库引擎
- Spring AOP框架中,对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型借助适配器模式来实现。
4.2 桥接模式
4.2.1 模式定义
桥接模式(Bridge Pattern)是将抽象部分和实现部分分离,使它们可以独立地改变,是一种对象结构型模式。
4.2.2 模式角色
桥接模式包含如下角色:
- Abstraction(抽象类)
- RefinedAbstraction(扩充抽象类)
- Implementor(实现类接口)
- ConcreteImplementor(具体实现类)
4.2.3 模式分析
桥接模式关键在于如何将抽象化与实现化解耦,使得两者可以独立改变。
抽象化:抽象就是忽略一些信息,将不同的实体当作同样的实体对待。在面向对象中将对象的共同性质抽取出来形成类的过程称之为抽象化的过程
实现化:针对抽象话给出的具体实现,就是实现化,抽象化与实现化是互逆的过程
解耦:解耦就是将抽象化和实现化直接的耦合解脱开,或者说将两者之间的强关联变成弱关联,将两个角色由继承改成关联关系(组合或者聚合)
典型代码:
public interface Implementor
{
public void operationImpl();
}
public abstract class Abstraction
{
protected Implementor impl;
public void setImpl(Implementor impl)
{
this.impl=impl;
}
public abstract void operation();
}
public class RefinedAbstraction extends Abstraction
{
public void operation()
{
//代码
impl.operationImpl();
//代码
}
}
4.2.4 模式例子
画出不同颜色的圆,DrawAPI 接口的实体类 RedCircle、GreenCircle。Shape 是一个抽象类,例子来自:http://www.runoob.com/design-pattern/bridge-pattern.html
创建桥接接口:
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}
接口具体实现类:
public class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: red, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
public class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: "
+ radius +", x: " +x+", "+ y +"]");
}
}
抽象类关联方式实现接口:
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}
具体类实现抽象类:
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}
打印到控制台:
Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[ color: green, radius: 10, x: 100, 100]
4.2.5 模式应用
- 一些软件的跨平台设计有时候也是应用了桥接模式
- JDBC的驱动程序,实现了将不同类型的数据库与Java程序的绑定
- Java虚拟机实现了平台的无关性,Java虚拟机设计就是通过桥接模式
4.3 组合模式
4.3.1 模式意图
介绍模式定义定义之前先介绍一下组合模式的意图。其实就是将对象组合成整体-部分层次的树形结构,客户端调用时,对于调用容器对象或者说组合对象("树枝")和单个对象("树叶")是一致的。
4.3.2 模式定义
组合模式(Composite Pattern):组合多个对象形成树形结构来表示“整体-部分”的结构层次。
组合模式又称“整体-部分”(Part-Whole)模式,属于对象结构型的设计模式。将对象组合在组件类里,用于描述整体和部分的关系。组合模式对单个对象和组合对象或者说容器对象的使用具有一致性。
4.3.3 模式角色
组合模式包括如下角色:
- Component:抽象构件
- Leaf:叶子构件
- Composite:容器构件
- Client:客户类
4.3.4 模式分析
组合模式定义一个抽象的构件类,主要用于被客户端调用,客户调调用时就不需要关心是单个对象还是容器对象了。
容器对象和抽象构件类是一种聚合关系,容器对象里即可以包含叶子,也可以包含容器,递归组合,从而形成一个树形结构。
实际例子
例子来自:《设计模式》一书
抽象构件类:
public abstract class Component
{
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract Component getChild(int i);
public abstract void operation();
}
叶子类:
public class Leaf extends Component
{
public void add(Component c)
{ //异常处理或错误提示 }
public void remove(Component c)
{ //异常处理或错误提示 }
public Component getChild(int i)
{ //异常处理或错误提示 }
public void operation()
{
//实现代码
}
}
容器类:
public class Composite extends Component
{
private ArrayList list = new ArrayList();
public void add(Component c)
{
list.add(c);
}
public void remove(Component c)
{
list.remove(c);
}
public Component getChild(int i)
{
(Component)list.get(i);
}
public void operation()
{
for(Object obj:list)
{
((Component)obj).operation();
}
}
}
4.3.5 模式应用
组合模式应用
- XML文档解析
- JDK的AWT/Swing
...
4.4 装饰模式
4.4.1 模式定义
装饰模式:装饰模式就是允许向一个现有的对象添加新的功能,同时又不改变其结构,装饰模式是一种对象结构型设计模式。
4.4.2 模式角色
对于装饰模式可以分为如下角色
- Component:抽象构件
- ConcreteComponent:具体构件
- Decorator:抽象装饰类
- ConcreteDecorator:具体装饰类
4.4.3 模式分析
对于装饰模式进行解释,更易于理解。要给一个类或对象新增行为,一般有两种方法,一种是继承方法,通过类继承的方法可以使Z子类拥有自身方法的同时,拥有父类的方法这就是一种新增类行为的方法;对于另外一种新增类行为的方法就是关联方法,即将一个类嵌入另外一个类,对于这个类,我们称之为装饰器(Decorator)
上面说了继承机制和关联机制,对于关联机制与继承机制相比,关联优势在于不会破坏类的封装性,继承的耦合度还是比关联要大的,所以应用关联机制的装饰模式偶尔度还是比较小的,这个就是装饰模式的优点了,不过装饰模式需要创建比较多的对象,这种缺点或许可以用享元模式减少类的创建。
下面给出装饰模式的经典代码:
继承抽象构件接口
public class Decorator extends Component
{
private Component component;
public Decorator(Component component)
{
this.component=component;
}
public void operation()
{
component.operation();
}
}
具体装饰类型实现抽象装饰接口
public class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation();
addedBehavior();
}
public void addedBehavior()
{
//新增方法
}
}
4.4.4 模式例子
给出《设计模式》一书的多重加密例子:
某系统提供了一个数据加密功能,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。现使用装饰模式设计该多重加密系统。
抽象构件接口:
public interface Cipher
{
public String encrypt(String plainText);
}
具体构件类:
public final class SimpleCipher implements Cipher
{
public String encrypt(String plainText)
{
String str="";
for(int i=0;i<plainText.length();i++)
{
char c=plainText.charAt(i);
if(c>='a'&&c<='z')
{
c+=6;
if(c>'z') c-=26;
if(c<'a') c+=26;
}
if(c>='A'&&c<='Z')
{
c+=6;
if(c>'Z') c-=26;
if(c<'A') c+=26;
}
str+=c;
}
return str;
}
}
抽象装饰类:
public abstract class CipherDecorator implements Cipher
{
private Cipher cipher;
public CipherDecorator(Cipher cipher)
{
this.cipher=cipher;
}
public String encrypt(String plainText)
{
return cipher.encrypt(plainText);
}
}
具体装饰类:
public class AdvancedCipher extends CipherDecorator
{
public AdvancedCipher(Cipher cipher)
{
super(cipher);
}
public String encrypt(String plainText)
{
String result=super.encrypt(plainText);
result=mod(result);
return result;
}
public String mod(String text)
{
String str="";
for(int i=0;i<text.length();i++)
{
String c=String.valueOf(text.charAt(i)%6);
str+=c;
}
return str;
}
}
public class ComplexCipher extends CipherDecorator
{
public ComplexCipher(Cipher cipher)
{
super(cipher);
}
public String encrypt(String plainText)
{
String result=super.encrypt(plainText);
result= this.reverse(result);
return result;
}
public String reverse(String text)
{
String str="";
for(int i=text.length();i>0;i--)
{
str+=text.substring(i-1,i);
}
return str;
}
}
客户端类进行调用:
public class Client
{
public static void main(String args[])
{
String password="sunnyLiu"; //明文
String cpassword; //密文
Cipher sc,ac,cc;
sc=new SimpleCipher();
cpassword=sc.encrypt(password);
System.out.println(cpassword);
cc=new ComplexCipher(sc);
cpassword=cc.encrypt(password);
System.out.println(cpassword);
ac=new AdvancedCipher(cc);
cpassword=ac.encrypt(password);
System.out.println(cpassword);
}
}
模式应用
装饰模式应用最常见的就是JDK提供的Java IO操作
- 抽象构件类:InputStream
- 具体构件类:FileInputStream、ByteArrayInputStream等
- 抽象装饰类:FilterInputStream
- 具体装饰类:BufferedInputStream、DataInputStream等
...
4.4.5 模式分类
装饰模式可以分为透明装饰模式和半透明装饰模式。
透明装饰模式
透明装饰模式要求客户端面向抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应该全部声明为抽象构件类型。
Cipher sc,cc,ac;
sc=new SimpleCipher();
cc=new ComplexCipher(sc);
ac=new AdvancedCipher(cc);
半透明装饰模式
半透明装饰模式是比较常见的,大多数装饰模式都是半透明(semi-transparent)的装饰模式,而不是完全透明(transparent)的,即允许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。
Transform camaro;
camaro=new Car();
camaro.move();
Robot bumblebee=new Robot(camaro);
bumblebee.move();
bumblebee.say();
4.5 外观模式
4.5.1 模式定义
外观模式:外观模式就是提供一个统一的接口,用来访问子系统的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。,外观模式也称门面模式,是一种对象结构型设计模式。
4.5.2 模式角色
从模式定义可以知道,外观模式应该包含如下角色:
- Frcade:外观角色
- SubSystem:子系统角色
- Client:客户端角色
经典例子:
public class Facade
{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void method()
{
obj1.method();
obj2.method();
obj3.method();
}
}
4.5.3 模式简单分析
外观模式为客户端类提供了便捷,客户端类不需要关注子系统的设计,直接提供外观类访问就好
外观模式符合“迪米特法则”,引入一个单一简单的接口,给客户端调用,从而降低了客户端和子系统的耦合度
不过外观模式也有一些缺点,每一种设计模式都是有缺点和优点的,需要根据复杂的业务场景进行选用。加入没引用一个抽象的外观类的话,一旦业务改变就需要进行外观类和客户端类代码的调整了
对于一些很复杂的业务系统来说,有时候可以设计多个外观类进行系统解耦
4.5.4 简单例子实践
JDBC数据库操作的例子,本例子来自《设计模式》一书
import java.sql.*;
public class JDBCFacade {
private Connection conn=null;
private Statement statement=null;
public void open(String driver,String jdbcUrl,String userName,String userPwd) {
try {
Class.forName(driver).newInstance();
conn = DriverManager.getConnection(jdbcUrl,userName,userPwd);
statement = conn.createStatement();
}
catch (Exception e) {
e.printStackTrace();
}
}
public int executeUpdate(String sql) {
try {
return statement.executeUpdate(sql);
}
catch (SQLException e) {
e.printStackTrace();
return -1;
}
}
public ResultSet executeQuery(String sql) {
try {
return statement.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public void close() {
try {
conn.close();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.5.5 模式应用
外观模式适用于复杂的系统,可以用于系统解耦。下面简单列举一下外观模式的一些应用场景
-
JavaEE框架里的Session就是用了外观模式
-
学JSP的JDBC数据库操作也是经常用外观模式的
4.6 享元模式
4.6.1 模式定义
享元模式(Flyweight Pattern)就是通过共享技术实现大量细粒度对象的复用。享元模式是通过细粒度对象的共享,所以也可以说享元模式是一种轻量级模式。按照Gof模式分类,享元模式属于对象结构型模式。
4.6.2 模式解释
-
可以共享的内容称为内部状态(Intrinsic State),需要外部环境设置的不能共享的内容称为外部状态(Extrinsic State)。享元模式需要创建一个享元工厂负责维护享元池(Flyweight Pool),享元池用于存储具有相同内部状态的享元对象。
-
享元模式中共享的仅仅是享元对象,外部状态是需要通过环境类设置的,在实际使用中,能共享的内部状态不是很多的,所以设计享元对象是比较小的,也就是细粒度对象,所以说享元模式就是通过共享技术实现大量细粒度对象的复用
-
创建大量对象会一定程度影响系统性能,不方便程序阅读,使用享元模式可以减少对象使用。
4.6.3 模式角色
享元模式包括下面角色
-
Flyweight:抽象享元类
-
ConcreteFlyweight:具体享元类
-
UnsharedConcreteFlyweight:非分享具体享元类
-
FlyweightFactory:享元工厂类
享元模式的核心在享元工厂类,享元工厂类的作用在与维护享元池,需要什么对象,可以从享元池获取
4.6.4 典型例子
例子来自:《设计模式》一书
public class Flyweight
{
//内部状态作为成员属性
private String intrinsicState;
public Flyweight(String intrinsicState)
{
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState)
{
......
}
}
public class FlyweightFactory
{
private HashMap flyweights = new HashMap();
public Flyweight getFlyweight(String key)
{
if(flyweights.containsKey(key))
{
//享元池有对象,直接获取
return (Flyweight)flyweights.get(key);
}
else
{
//创建具体的享元对象,存储在享元池
Flyweight fw = new ConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
4.6.5 模式应用
- JDK类库中的String类使用了享元模式
...
4.6.6 模式分类
享元模式分为单存享元模式和复合享元模式
- 单纯享元模式:在单纯享元模式中不存在非共享具体单元,所有的具体享元类对象都是可以共享的。
- 复合享元模式:通过复合模式将单纯享元模式进行组合,形成复合享元对象
4.7 代理模式
4.7.1 模式定义
代理模式:代理模式就是引入一个代理对象,通过代理对象实现对原对象的引用。代理模式是一种对象结构型。
4.7.2 代理模式包含如下角色
- Subject:抽象主题角色
- Proxy:代理主题角色
- RealSubject:真实主题角色
4.7.3 模式例子
public class Proxy implements Subject
{
private RealSubject realSubject = new RealSubject();
public void preRequest()
{…...}
public void request()
{
preRequest();
realSubject.request();
postRequest();
}
public void postRequest()
{……}
}
4.7.4 模式类型
来自:《设计模式》一书归纳分类
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
- 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
- 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 防火墙(Firewall)代理:保护目标不让恶意用户接近。
- 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
- 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
下面介绍一下静态代理和动态代理
代理模式分为静态代理和动态代理 • 静态代理:静态代理就是编译阶段就生成代理类来完成对代理对象的一系列操作。
• 动态代理:动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
4.7.5 静态代理
静态代理:静态代理就是编译阶段就生成代理类来完成对代理对象的一系列操作。
主题接口:
public interface Subject {
abstract public void request();
}
目标对象:
public class RealSubject implements Subject {
public void request() {
System.out.println( " From real subject. " );
}
}
代理对象:
public class StaticProxySubject implements Subject {
private RealSubject realSubject; // 以真实角色作为代理角色的属性
public ProxySubject() { }
public void request() { // 该方法封装了真实对象的request方法
//懒加载,用的时候才加载
if ( realSubject == null ) {
realSubject = new RealSubject();
}
realSubject.request(); // 此处执行真实对象的request方法
}
}
编写客户端类:
public class Client{
StaticProxySubject sps = new StaticProxySubject();
sps.request();
}
4.7.6 动态代理
动态代理:动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
生成动态代理的方法有很多: JDK中自带动态代理,CGlib, javassist等。
JDK动态代理
Proxy类。该类即为动态代理类,该类最常用的方法为:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
。
newProxyInstance()方法用于根据传入的接口类型interfaces返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示被代理类实现的接口列表,第三个参数h表示所指派的调用处理程序类。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
private Class<?> target;//委托类
public MyInvocationHandler(Class<?> target){
this.target=target;
}
//实际执行类bind
public Object bind(Class<?> target){
//利用JDK提供的Proxy实现动态代理
return Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target},this);
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
/**代理环绕**/
//执行实际的方法
Object invoke = method.invoke(target, args);
return invoke;
}
}
CGLIB动态代理
CGLIB动态代理实现相关类需要在项目中导入 cglib-nodep-2.1_3.jar ,主要涉及两个类:
MethodInterceptor接口。它是代理实例的调用处理程序实现的接口,该接口中定义了如下方法:public Object intercept(Object proxy, Method method, Object[] arg2, MethodProxy mp);
intercept()方法中第一个参数proxy表示代理类,第二个参数method表示需要代理的方法,第三个参数args表示代理方法的参数数组,第四个参数mp用 来去调用被代理对象方法
package com.demo;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyInterceptor implements MethodInterceptor{
private Object target; ;//代理的目标对象
public MyInterceptor(Object target) {
this.target = target;
}
//proxy 在其上调用方法的代理实例 method拦截的方法 args 拦截的参数
//invocation 用来去调用被代理对象方法
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy invocation) throws Throwable {
//1.记录日志 2.时间统计开始 3.安全检查
Object retVal = invocation.invoke(target, args);
//4.时间统计结束
return retVal;
}
//创建代理对象的方法
public Object proxy(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();//该类用于生成代理类
enhancer.setSuperclass(this.target.getClass());//设置父类
enhancer.setCallback(this);//设置回调用对象为本身
return enhancer.create();
}
}
五、行为型设计模式
5.1 职责链模式
5.1.1 行为型模式
介绍职责链模式之前先介绍一下行为型设计模式,因为按照GoF模式分类,职责链就是一种行为型设计模式。行为型设计模式就是主要表示类或者对象之间的关联关系,分为类行为型和对象行为型。类行为型一般都是通过类的继承或者多态等等方式实现。对象行为型就是通过对象的聚合等等关联实现。
5.1.2 职责链模式定义
职责链模式是一种对象行为型模式。根据“合成复用”原则,尽量使用关联来取代类继承,对象行为型可以说是一种不错的行为型模式。
职责链模式是一种将请求的发送者和请求处理者分离的一种模式。职责链可以是线型、环型或者树形的,不需要关系请求处理的细节,只要将请求沿着路径发送就好,做到了请求发送者和请求处理者解耦。
引用一下
Chain of Responsibility Pattern: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
翻译过来就是:
职责链模式:通过给予多个对象处理请求的机会,避免将请求的发送方与接收方耦合。将接收对象链接起来,并沿着链传递请求,直到对象处理它。
5.1.3 职责链模式角色
职责链模式包括下面几种角色:
- Handler:抽象处理者
- ConcreteHandler:具体处理者
- Client:客户端
上面已经说了请求发送者和请求处理者,其实请求发送者就是客户端,请求处理者就是ConcreteHandler,也就是说,Client只是需要什么业务请求的就发送而已,完全可以对ConcreteHandler请求处理者改造,而不影响到Client,也就是前面所说的做到了请求发送者和请求处理者的解耦。
5.1.4 简单实例
例子参考:《设计模式》一书
抽象类:
public abstract class Handler
{
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler)
{
this.nextHandler=nextHandler;
}
public abstract void handleRequest(String request);
}
具体实现者:
public class ConcreteHandler extends Handler
{
public void handleRequest(String request)
{
if(请求request满足条件)
{
...... //处理请求;
}
else
{
this.nextHandler.handleRequest(request); //转发请求
}
}
}
客户端调用:
public class Client
{
public static void main(String args[])
{
Handler handler1 = new ConcreteHandler();
handler handler2 = new ConcreteHandler();
handler1.setNextHandler(handler2);
handler1.handleRequest("test");
}
}
网上这个例子也写的还可以,可以参考学习
http://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html
5.1.5 模式应用
在Java中的异常处理机制
try
{
……
}catch(IOException e3){
……
}finally{
……
}
Mybatis Plugin 插件(拦截器)的应用,也是用动态代理和职责链模式进行设计和实现的:
可以看一下博客:https://www.jianshu.com/p/b82d0a95b2f3
5.2 命令模式
5.2.1 模式定义
命令模式(Command Pattern):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分离,两者之间通过命令对象进行沟通,方便将命令对象进行储存、传递、调用、增加与管理。命令模式别名为动作(Action)模式或事务(Transaction)模式,属于对象行为型模式。
5.2.2 模式角色
命令模式包括如下角色:
- Client:客户类,负责调用
- Command:抽象命令类,声明执行命令的接口,拥有执行命令的抽象方法 execute()。
- ConcreteCommand:具体命令类,是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- Invoker:调用者,请求的发送者,通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
- Receiver:接收者,执行命令功能的相关操作,是具体命令对象业务的真正实现者。
5.2.3 模式分析
命令模式的本质:是对命令进行封装,将发出命令的责任和执行命令的责任分离。
命令模式的实际执行者是接收者(Receiver),调用者和接收者两者之间通过命令对象进行沟通。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
典型的命令模式代码
抽象命令类:
public abstract class Command
{
public abstract void execute();
}
具体命令类:
public class ConcreteCommand extends Command
{
private Receiver receiver;
public void execute()
{
receiver.action();
}
}
调用者Invoker类:
public class Invoker
{
private Command command;
public Invoker(Command command)
{
this.command=command;
}
public void setCommand(Command command)
{
this.command=command;
}
//业务方法,用于调用命令类的方法
public void call()
{
command.execute();
}
}
接收者(Receiver)类:
public class Receiver
{
public void action()
{
//具体操作
}
}
5.2.4 典型例子
例子来自《设计模式》一书
电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。
抽象命令类:
public interface AbstractCommand
{
public void execute();
}
具体的命令类:
换台
public class TVChangeCommand implements AbstractCommand
{
private Television tv;
public TVChangeCommand()
{
tv = new Television();
}
public void execute()
{
tv.changeChannel();
}
}
关机
public class TVCloseCommand implements AbstractCommand
{
private Television tv;
public TVCloseCommand()
{
tv = new Television();
}
public void execute()
{
tv.close();
}
}
开机
public class TVOpenCommand implements AbstractCommand
{
private Television tv;
public TVOpenCommand()
{
tv = new Television();
}
public void execute()
{
tv.open();
}
}
接收者Receiver类:
public class Television
{
public void open()
{
System.out.println("打开电视机!");
}
public void close()
{
System.out.println("关闭电视机!");
}
public void changeChannel()
{
System.out.println("切换电视频道!");
}
}
调用者(Invoker)类
public class Controller
{
private AbstractCommand openCommand,closeCommand,changeCommand;
public Controller(AbstractCommand openCommand,AbstractCommand closeCommand,AbstractCommand changeCommand)
{
this.openCommand=openCommand;
this.closeCommand=closeCommand;
this.changeCommand=changeCommand;
}
public void open()
{
openCommand.execute();
}
public void change()
{
changeCommand.execute();
}
public void close()
{
closeCommand.execute();
}
}
5.2.5 适用场景
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
5.3 解释器模式
5.3.1 模式定义
解释器模式(Interpreter Pattern):定义语言的文法,并且建立一个解释器来解释改语言中的句子,这里的“语言”意思是规定格式和语法的代码,所以解释器模式是一种类行为型模式
5.3.2 模式角色
- Context: 环境类
- Client: 客户类
- AbstractExpression: 抽象表达式
- TerminalExpression: 终结符表达式
- NonterminalExpression: 非终结符表达式
5.3.3 模式分析
模式表示,可以使用文法规则或抽象语法树来表示语言
文法规则实例:
- expression ::= value | symbol
- symbol ::= expression '+' expression | expression '-' expression
- value ::= an integer //一个整数值
在文法规则定义中可以使用一些符号来表示不同的含义,如使用“|”表示或,使用“{”和“}”表示组合,使用“*”表示出现0次或多次等,其中使用频率最高的符号是表示或关系的“|” 。
除了使用文法规则来定义一个语言,在解释器模式中还可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例。
5.3.4 典型例子
典型的解释器模式例子:
抽象表达式类:
public abstract class AbstractExpression
{
public abstract void interpret(Context ctx);
}
终结符表达式类:
public class TerminalExpression extends AbstractExpression
{
public void interpret(Context ctx)
{
//对于终结符表达式的解释操作
}
}
非终结符表达式类:
public class NonterminalExpression extends AbstractExpression
{
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right)
{
this.left=left;
this.right=right;
}
public void interpret(Context ctx)
{
//递归调用每一个组成部分的interpret()方法
//在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}
环境类代码:
public class Context
{
private HashMap map = new HashMap();
public void assign(String key, String value)
{
//往环境类中设值
}
public String lookup(String key)
{
//获取存储在环境类中的值
}
}
例子来自《设计模式》一书
5.4 迭代器模式
5.4.1 模式定义
迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor),所以迭代器模式是一种对象行为型。
5.4.2 模式角色
- Iterator:抽象迭代器
- ConcreteIterator:具体迭代器
- Aggregate:抽象聚合类
- ConcreteAggregate:具体聚合类
5.4.3 模式分析
对于迭代器模式来说,一个聚合可以有多个遍历。在迭代器模式中,提供了一个外部的迭代器对聚合对象进行访问和遍历,迭代器定义了一个访问聚合对象的接口,可以跟踪遍历元素,了解哪些元素已经遍历过而哪些没有。
迭代器模式中应用了工厂方法模式,聚合类充当工厂类,而迭代器充当产品类
迭代器模式本质
迭代器模式本质:将聚合对象存储的内部数据提取出来,封装到一个迭代器中,通过专门的迭代器来遍历聚合对象的内部数据,这就是迭代器模式的本质
聚合对象主要职责
聚合对象主要有两个职责:一是存储内部数据;二是遍历内部数据;最基本的职责还是存储内部数据
5.4.4 典型例子
例子来自:《设计模式》一书
自定义迭代器
自定义迭代器
Client:客户端调用
MyIterator:抽象迭代器
MyCollection:抽象聚合类
NewCollection:具体聚合类
NewIterator:具体迭代器
interface MyCollection
{
MyIterator createIterator();
}
interface MyIterator
{
void first();
void next();
boolean isLast();
Object currentItem();
}
class NewCollection implements MyCollection
{
private Object[] obj={"dog","pig","cat","monkey","pig"};
public MyIterator createIterator()
{
return new NewIterator();
}
private class NewIterator implements MyIterator
{
private int currentIndex=0;
public void first()
{
currentIndex=0;
}
public void next()
{
if(currentIndex<obj.length)
{
currentIndex++;
}
}
public void previous()
{
if(currentIndex>0)
{
currentIndex--;
}
}
public boolean isLast()
{
return currentIndex==obj.length;
}
public boolean isFirst()
{
return currentIndex==0;
}
public Object currentItem()
{
return obj[currentIndex];
}
}
}
class Client
{
public static void process(MyCollection collection)
{
MyIterator i=collection.createIterator();
while(!i.isLast())
{
System.out.println(i.currentItem().toString());
i.next();
}
}
public static void main(String a[])
{
MyCollection collection=new NewCollection();
process(collection);
}
}
5.4.5 适用场景
在以下的情况可以使用迭代器模式:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 为遍历不同的聚合结构提供一个统一的接口。
5.5 中介者模式
5.5.1 模式定义
中介者模式(Mediator Pattern):中介者模式就是用一个中介对象来封装一系列的对象的交互,使各对象之间不需要显式地相互作用,降低对象之间的耦合度,中介者是一种对象行为型模式。
所以中介者模式适用于对象之间存在大量的关联的情况,假如一个对象改变了,我们就需要跟踪其关联对象,做出对于调整,耦合度是很大的,所以就可以用中介者模式来降低耦合度。
5.5.2 模式角色
中介者模式包括如下角色:
- Mediator:抽象中介者
- ConcreteMediator:具体中介者
- Colleague:抽象同事类
- ConcreteColleague:具体同事类
5.5.3 模式分析
模式作用
中介者模式起到中转的作用,当同事类需要调用时,调用中介者就好,不需要调用同事类,中介者模式将同事对象之间的关系行为进行封装,起到了协调的作用
模式优缺点
中介者模式优点:
- 简化了对象之间的交互
- 减少子类生成
- 解耦各同事类
- 简化各同事类的设计和实现
中介者模式缺点:
- 由于对象之间的交互细节处理都放在中介者这里,所以具体中介者类就会随着对象的增多而变得越来越复杂,使中介者类维护起来很困难
模式经典代码
抽象中介者类:
public abstract class Mediator
{
protected ArrayList colleagues;
public void register(Colleague colleague)
{
colleagues.add(colleague);
}
public abstract void operation();
}
具体中介者类:
public class ConcreteMediator extends Mediator
{
public void operation()
{
......
((Colleague)(colleagues.get(0))).method1();
......
}
}
抽象同事类:
public abstract class Colleague
{
protected Mediator mediator;
public Colleague(Mediator mediator)
{
this.mediator=mediator;
}
public abstract void method1();
public abstract void method2();
}
具体同事类:
public class ConcreteColleague extends Colleague
{
public ConcreteColleague(Mediator mediator)
{
super(mediator);
}
public void method1()
{
......
}
public void method2()
{
mediator.operation1();
}
}
5.5.4 典型例子
例子来自:《设计模式》一书
实例:虚拟聊天室
某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息,钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息。该聊天室可以对不雅字符进行过滤,还可以对发送的图片大小进行控制。用中介者模式设计该虚拟聊天室。
抽象同事类
定义一个Member类,属于抽象同事类:
public abstract class Member
{
protected AbstractChatroom chatroom;
protected String name;
public Member(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public AbstractChatroom getChatroom()
{
return chatroom;
}
public void setChatroom(AbstractChatroom chatroom)
{
this.chatroom=chatroom;
}
public abstract void sendText(String to,String message);
public abstract void sendImage(String to,String image);
public void receiveText(String from,String message)
{
System.out.println(from + "发送文本给" + this.name + ",内容为:" + message);
}
public void receiveImage(String from,String image)
{
System.out.println(from + "发送图片给" + this.name + ",内容为:" + image);
}
}
具体同事类
具体同事类,继承抽象同事类Member:
普通会员
public class CommonMember extends Member
{
public CommonMember(String name)
{
super(name);
}
public void sendText(String to,String message)
{
System.out.println("普通会员发送信息:");
chatroom.sendText(name,to,message); //发送
}
public void sendImage(String to,String image)
{
System.out.println("普通会员不能发送图片!");
}
}
砖石会员
public class DiamondMember extends Member
{
public DiamondMember(String name)
{
super(name);
}
public void sendText(String to,String message)
{
System.out.println("钻石会员发送信息:");
chatroom.sendText(name,to,message); //发送
}
public void sendImage(String to,String image)
{
System.out.println("钻石会员发送图片:");
chatroom.sendImage(name,to,image); //发送
}
}
抽象中介者类
抽象的中介者类,定义聊天室具体有功能方法
public abstract class AbstractChatroom
{
public abstract void register(Member member);
public abstract void sendText(String from,String to,String message);
public abstract void sendImage(String from,String to,String message);
}
具体中介者类
聊天室功能实现,不需要同事类之间相互调用
import java.util.*;
public class ChatGroup extends AbstractChatroom
{
private Hashtable members=new Hashtable();
public void register(Member member)
{
if(!members.contains(member))
{
members.put(member.getName(),member);
member.setChatroom(this);
}
}
public void sendText(String from,String to,String message)
{
Member member=(Member)members.get(to);
String newMessage=message;
newMessage=message.replaceAll("不雅字符","*");
member.receiveText(from,newMessage);
}
public void sendImage(String from,String to,String image)
{
Member member=(Member)members.get(to);
//模拟图片大小判断
if(image.length()>5)
{
System.out.println("图片太大,发送失败!");
}
else
{
member.receiveImage(from,image);
}
}
}
5.5.5 模式应用
- 中介者模式是事件驱动类软件中应用比较多,中介者模式充当组件之间调用的中介,对组件调用进行协调
- MVC是JavaEE的一个基本模式,此时控制器Controller作为一个中介者,负责视图对象View和模型对象Model之间的交互,
5.6 备忘录模式
5.6.1 模式定义
备忘录模式(Memento Pattern):备忘录模式的定义是在不破坏封装的前提下,捕获一个对象的内部状态,并将该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。所以备忘录模式就是一种对象行为型模式。
5.6.2 模式角色
备忘录模式包括下面角色
- Originator(原发器)
- Memento(备忘录)
- Caretaker(负责人)
备忘录模式包括原发器类,备忘录类和负责人类。原发器可以创建一个备忘录,备忘录类存储原发器类的内部状态,根据原发器来决定保存哪些内部状态,负责人类负责保存备忘录
5.6.3 模式分析
备忘录模式主要应用于备份或者回退操作,为了使软件使用更友好,通常都有回退功能,软件一般也要提供回退机制,而要实现回退,就必须事先备份好状态信息,所以有了备忘录模式就有实现系统回退到某一个特定的历史状态。
备忘录对象用于存储另外一个对象内部状态的快照对象,所以备忘录模式又可以称之为快照模式(Snapshot Pattern)或Token模式
典型代码:
原发器类:
public class Originator {
private String state;
public Originator(){}
// 创建一个备忘录对象
public Memento createMemento(){
return new Memento(this);
}
// 根据备忘录对象恢复原发器状态
public void restoreMemento(Memento m){
state = m.state;
}
public void setState(String state)
{
this.state=state;
}
public String getState()
{
return this.state;
}
}
备忘录类:
public class Memento {
private String state;
public Memento(Originator o){
state = o.state;
}
public void setState(String state)
{
this.state=state;
}
public String getState()
{
return this.state;
}
}
负责人类:
import java.util.ArrayList;
import java.util.List;
public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento state){
mementoList.add(state);
}
public Memento get(int index){
return mementoList.get(index);
}
}
5.6.4 模式例子
实例:用户信息操作撤销
某系统提供了用户信息操作模块,用户可以修改自己的各项信息。为了使操作过程更加人性化,现使用备忘录模式对系统进行改进,使得用户在进行了错误操作之后可以恢复到操作之前的状态。
本例子来自《设计模式》一书
原发器类,创建备忘录类
package dp.memento;
public class UserInfoDTO
{
private String account;
private String password;
private String telNo;
public String getAccount()
{
return account;
}
public void setAccount(String account)
{
this.account=account;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password=password;
}
public String getTelNo()
{
return telNo;
}
public void setTelNo(String telNo)
{
this.telNo=telNo;
}
public Memento saveMemento()
{
return new Memento(account,password,telNo);
}
public void restoreMemento(Memento memento)
{
this.account=memento.getAccount();
this.password=memento.getPassword();
this.telNo=memento.getTelNo();
}
public void show()
{
System.out.println("Account:" + this.account);
System.out.println("Password:" + this.password);
System.out.println("TelNo:" + this.telNo);
}
}
备忘录类,保存原发器类状态:
package dp.memento;
class Memento
{
private String account;
private String password;
private String telNo;
public Memento(String account,String password,String telNo)
{
this.account=account;
this.password=password;
this.telNo=telNo;
}
public String getAccount()
{
return account;
}
public void setAccount(String account)
{
this.account=account;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password=password;
}
public String getTelNo()
{
return telNo;
}
public void setTelNo(String telNo)
{
this.telNo=telNo;
}
}
负责人类,创建备忘录:
package dp.memento;
public class Caretaker
{
private Memento memento;
public Memento getMemento()
{
return memento;
}
public void setMemento(Memento memento)
{
this.memento=memento;
}
}
5.6.5 模式应用
- 软件里的存档操作
- Windows 里的 ctri + z。
- IE 中的后退操作
- 数据库的事务管理
....
5.7 观察者模式
5.7.1 模式定义
观察者模式(Observer Pattern):观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖的对象皆得到通知并且被自动更新。不过观察者只能知道目标发送了改变,而不能知道具体怎么改变的。
5.7.2 观察者角色
观察者模式包含如下角色:
Subject:目标
ConcreteSubject:具体目标
Observer:观察者
ConcreteObserver:具体观察者
5.7.3 Observer模式”push”和”pull”数据
具体Subject可以通过两种方式通知具体观察者更新数据:
①push数据方式:具体Subject将变化后的数据全部交给具体观察者;
②pull数据方式:具体Subject提供获得数据的方法,具体观察者调用具体主题提供的方法获得数据。
5.7.4 典型列子
PS:代码例子来自《图说设计模式》
抽象目标类
import java.util.*;
public abstract class Subject
{
protected ArrayList observers = new ArrayList();
public abstract void attach(Observer observer);
public abstract void detach(Observer observer);
public abstract void notify();
}
具体目标类
public class ConcreteSubject extends Subject
{
public void attach(Observer observer)
{
observers.add(observer);
}
public void detach(Observer observer)
{
observers.remove(observer);
}
public void notify()
{
for(Object obs:observers)
{
((Observer)obs).update();
}
}
}
抽象观察者类
public interface Observer
{
public void update();
}
具体观察者类
public class ConcreteObserver implements Observer
{
public void update()
{
//具体更新代码
}
}
还有一个不错的例子可以参考《Head First 设计模式》里的气象局例子
5.7.5 模式优缺点
观察者优点:下面简要描述一下,观察者可以实现表现层和数据逻辑层的分离;观察者模式在观察目标和观察者之间建立一个抽象的耦合
观察者缺点:如果观察者类和目标类之间有循环关联,很容易导致系统奔溃;如果观察者太多的话,通知所有的观察者将花费很多时间
5.7.6 模式应用
Swing、RMI、JDK内置的java.util.Observer接口和java.util.Observable类都是观察者模式的应用
5.7.7 经典气象局例子
PS:本列子来自《Head First 设计模式》
自己看《Head First设计模式》,本博客仅仅是自己做做笔记
下面的例子来自《Head First设计模式》一书,推荐读者去学习
主题接口,定义一个主题接口
public interface Subject {
//注册观察者
public void registerObserver(Observer o);
//remove观察者
public void removeObserver(Observer o);
//通知观察者
public void notifyObservers();
}
weatherDate类,其实就是主题接口的实现类,weatherData类实现Subject接口,主要用来注册观察者,通知观察者等等,当数据变化时,即时通知注册的观察者,代码实现是通过循环遍历,观察者再调用更新数据接口来实现,《Head First设计模式》一书提供了基于JDK的内置类来实现的列子,读者可以去看看
import java.util.ArrayList;
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();//实例对象,通过数组列表来存在观察者对象
}
public void registerObserver(Observer o) {
observers.add(o);//注册观察者
}
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);//remove观察者对象
}
}
public void notifyObservers() {//循环遍历,通知所有注册的观察者
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
// other WeatherData methods here
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
观察者接口类
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
观察者接口实现类
public interface DisplayElement {
public void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Subject weatherData) {
weatherData.registerObserver(this);//注册观察者
}
/更新数据
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
控制台打印一下
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay =
new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
5.8 状态模式
5.8.1 模式定义
一个对象在其内部状态改变时改变其行为,这个对象我们可以称为状态对象,所以状态模式是一种对象行为型模式。
5.8.2 模式结构
-
Context:环境类
Context类也可以称之为上下文类,实际上就是拥有状态的对象,可以理解为状态管理器。 -
State:抽象状态类
抽象状态类可以是一个接口类或者抽象类,反正实现的话都是通过具体状态类。抽象状态类,封装环境类不同状态下的所有动作方法。 -
ConcreteState:具体状态类
具体实现类就比较容易理解了,就是继承抽象状态类,实现具体的方法,不一定所有的抽象方法都有重写,根据环境类状态的改变进行重写就好,其实也是根据状态改变改变动作方法。
5.8.3 模式适用场景
- 状态模式适用于行为随状态改变的业务场景,比如状态改变了,行为也会做成改变。
- 业务代码中很多条件的情况,加入一些代码有很多的if...else,并且经常改变,这种情况就可以使用状态模式进行编写。
5.8.4 业务应用场景:
-
比如OA的审批就可以应用状态模式,发起申请之后,审批状态可能有受理,批准等等状态,每个状态具有不一样的动作;
-
游戏的角色扮演,每次游戏版本升级都是会出现状态动作的改变,用状态模式进行设计,可以提高程序可拓展性;
5.8.5 简单实例
上下文类:
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
抽象状态类:
public abstract class State {
public void doAction(Context context);
}
具体状态类:
public class ConcreteState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
调用代码:
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
ConcreteState concreteState = new ConcreteState();
concreteState.doAction(context);
System.out.println(context.getState().toString());
}
}
5.8.6 状态模式分类
状态模式分为简单状态模式和可切换状态模式。
-
简单状态模式
简单状态模式就是指状态相对独立,具体状态类可以根据抽象状态类进行编程,也就是不需要用环境类中的setState方法改变状态 -
可切换状态的状态模式
可切换状态模式是状态可以变换的,状态变换了,具体状态类在调用时就要使用环境类的setState改变状态
5.9 策略模式
5.9.1 模式定义
策略模式:定义一系列算法,然后将每一个算法封装起来,并将它们可以互相替换。也就是将一系列算法封装到一系列策略类里面。策略模式是一种对象行为型模式。策略模式符合“开闭原则“
Strategy Pattern: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
5.9.2 模式角色
- Context :环境类
- Strategy:抽象策略类
- ConcreteStrategy:具体策略类
5.9.3 简单实例
不用策略模式的情况:
public class Context
{
……
public void method(String type)
{
......
if(type == "strategyA")
{
//算法A
}
else if(type == "strategyB")
{
//算法B
}
else if(type == "strategyC")
{
//算法C
}
......
}
……
}
抽象策略类:
public abstract class AbstractStrategy
{
public abstract void method();
}
具体策略类:
public class ConcreteStrategyA extends AbstractStrategy
{
public void method()
{
//算法A
}
}
环境类:
public class Context
{
private AbstractStrategy strategy;
public void setStrategy(AbstractStrategy strategy)
{
this.strategy= strategy;
}
public void method()
{
strategy.method();
}
}
客户端调用:
Context context = new Context();
AbstractStrategy strategy;
strategy = new ConcreteStrategyA();
context.setStrategy(strategy);
context.method();
5.9.4 策略模式和状态模式对比
相同点:
-
策略模式和状态模式都是属于行为型设计模式,也同样是对象行为型设计模式,非类行为型设计模式。
-
策略模式和状态模式有点类似,都符合”闭合原则“
-
两个设计模式都可以用于减少代码大量的if...else
不同点:
-
具体使用策略模式还是状态模式,可以通过环境类的状态而定,有很多状态的话,就使用状态模式。
-
使用策略模式时,环境类需要选择一个确定的策略类,也就是客户端调时需要关心具体状态,根据需要调用;而状态模式是不需要的,在状态模式里,环境类是要放在一个具体状态中的,也就是根据环境类的状态改变进行调用状态类的算法
对状态模式不是很熟悉,可以参考我以前写的一篇博客
https://blog.csdn.net/u014427391/article/details/85219470
5.10 模板方法模式
5.10.1 模式定义
模板方法模式就是在一个抽象类中定义一些骨架方法,然后通过类继承的方法,将一些方法延迟到继承类里。模板方法模式是一种类行为型模式,是一种比较常用的方法。不属于对象行为型模式,因为只是通过类继承实现。
Template Method Pattern: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
5.10.2 模式角色
- AbstractClass(抽象类)
- ConcreteClass(实现类)
5.10.3 模式分析
模板方法,将基本方法封装组合在一个抽象类中形成一个总算法或者说一个总行为的方法。
模板方法的组成部分:
- 抽象方法(Abstract Method)
- 具体方法(Concrete Method)
- 钩子方法(HookMethod)
抽象类代码:
public abstract class AbstractClass
{
public void templateMethod() //模板方法
{
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
public void operation1() //基本方法-具体方法
{
//实现代码
}
public abstract void operation2(); //基本方法-抽象方法
public void operation3() //基本方法-钩子方法
{
}
}
具体实现类代码:
public abstract class ConcreteClass
{
/**
* 基本方法-抽象方法
*/
public abstract void operation2(){
//具体实现
}
/**
* 基本方法-钩子方法
*/
public void operation3(){
//具体实现
}
}
子类不显性调用父类的方法,而是通过继承的方法来实现具体的业务方法,也就是说父类控制子类的调用,这种机制叫好莱坞原则。好莱坞原则的定义为:“不要给我们打电话,我们会给你打电话(Don‘t call us, we’ll call you)”。
5.10.4 具体例子
数据库操作的例子。数据库操作分为连接、打开、使用、关闭步骤。现在要使用mysql、oracle、db2等等关系型数据库进行数据库操作工具类的编写。而对于使用这几种不同的数据库,其实只是连接的代码不同,而其它操作的代码都是差不多的,所以可以使用模板方法进行代码复用。
ps:这个例子来自《设计模式》一书,稍微改了一点
模板方法
public abstract class DBOperator
{
//抽象方法
public abstract void connDB();
public void openDB()
{
System.out.println("打开数据库");
}
public void useDB()
{
System.out.println("使用数据库");
}
public void closeDB()
{
System.out.println("关闭数据库");
}
//模板方法
public void process()
{
connDB();
openDB();
useDB();
closeDB();
}
}
mysql数据库
public class DBOperatorMysql extends DBOperator
{
public void connDB()
{
System.out.println("使用JDBC-ODBC桥接连接Mysql数据库");
}
}
Oracle数据库
public class DBOperatorOracle extends DBOperator
{
public void connDB()
{
System.out.println("使用JDBC-ODBC桥接连接Oracle数据库");
}
}
调用
class Client
{
public static void main(String a[])
{
DBOperator db1;
db1=new DBOperatorMysql();
db1.process();
db1=new DBOperatorOracle();
db1.process();
}
}
5.10.5 模式应用场景
- Spring、Struts2框架的应用,比如框架的初始化就有应用
...
5.11 访问者模式
5.11.1 模式定义
访问者模式:表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。所以访问者模式是一种对象行为型模式。
5.11.2 模式角色
访问者模式包括如下角色:
- Vistor(抽象访问者)
- ConcreteVisitor(具体访问者)
- Element(抽象元素)
- ConcreteElement(具体元素)
- ObjectStructure(对象结构)
5.11.3 模式分析
访问者模式的对象结构存储了不同类型的元素对象,以供不同的访问者访问
访问者模式包括了两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层级结构,提供了抽象元素和具体元素
相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同的访问者以不同访问方式访问。
典型代码:
抽象访问者类
public abstract class Visitor
{
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
public void visit(ConcreteElementC elementC)
{
//元素ConcreteElementC操作代码
}
}
具体访问者类
public class ConcreteVisitor extends Visitor
{
public void visit(ConcreteElementA elementA)
{
//元素ConcreteElementA操作代码
}
public void visit(ConcreteElementB elementB)
{
//元素ConcreteElementB操作代码
}
}
抽象元素类
public interface Element
{
public void accept(Visitor visitor);
}
具体元素类
public class ConcreteElementA implements Element
{
public void accept(Visitor visitor)
{
visitor.visit(this);
}
public void operationA()
{
//业务方法
}
}
对象结构类
public class ObjectStructure
{
private ArrayList list=new ArrayList();
public void accept(Visitor visitor)
{
Iterator i=list.iterator();
while(i.hasNext())
{
((Element)i.next()).accept(visitor);
}
}
public void addElement(Element element)
{
list.add(element);
}
public void removeElement(Element element)
{
list.remove(element);
}
}
5.11.4 模式例子
本例子来自《设计模式》一书
实例一:购物车
顾客在超市中将选择的商品,如苹果、图书等放在购物车中,然后到收银员处付款。在购物过程中,顾客需要对这些商品进行访问,以便确认这些商品的质量,之后收银员计算价格时也需要访问购物车内顾客所选择的商品。此时,购物车作为一个ObjectStructure(对象结构)用于存储各种类型的商品,而顾客和收银员作为访问这些商品的访问者,他们需要对商品进行检查和计价。不同类型的商品其访问形式也可能不同,如苹果需要过秤之后再计价,而图书不需要。使用访问者模式来设计该购物过程。
抽象的访问者类
public abstract class Visitor
{
protected String name;
public void setName(String name)
{
this.name=name;
}
public abstract void visit(Apple apple);
public abstract void visit(Book book);
}
具体的访问者类:
public class Saler extends Visitor
{
public void visit(Apple apple)
{
System.out.println("收银员" + name + "给苹果过秤,然后计算其价格。");
}
public void visit(Book book)
{
System.out.println("收银员" + name + "直接计算书的价格。");
}
}
public class Customer extends Visitor
{
public void visit(Apple apple)
{
System.out.println("顾客" + name + "选苹果。");
}
public void visit(Book book)
{
System.out.println("顾客" + name + "买书。");
}
}
元素接口类:
public interface Product
{
void accept(Visitor visitor);
}
具体的元素类:
public class Apple implements Product
{
public void accept(Visitor visitor)
{
visitor.visit(this);
}
}
public class Book implements Product
{
public void accept(Visitor visitor)
{
visitor.visit(this);
}
}
对象结构类:
import java.util.*;
public class BuyBasket
{
private ArrayList list=new ArrayList();
public void accept(Visitor visitor)
{
Iterator i=list.iterator();
while(i.hasNext())
{
((Product)i.next()).accept(visitor);
}
}
public void addProduct(Product product)
{
list.add(product);
}
public void removeProduct(Product product)
{
list.remove(product);
}
}
客户端类:
public class Client
{
public static void main(String a[])
{
Product b1=new Book();
Product b2=new Book();
Product a1=new Apple();
Visitor visitor;
BuyBasket basket=new BuyBasket();
basket.addProduct(b1);
basket.addProduct(b2);
basket.addProduct(a1);
visitor=(Visitor)XMLUtil.getBean();
visitor.setName("张三");
basket.accept(visitor);
}
}
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil
{
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean()
{
try
{
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
config.xml
<?xml version="1.0"?>
<config>
<className>Saler</className>
</config>
5.11.5 模式应用
- java xml处理的DOM4j,通过访问者模式的方式来读取并解析XML文档,VisitorSupport是DOM4J提供的Visitor接口的默认适配器
public class MyVisitor extends VisitorSupport
{
public void visit(Element element)
{
System.out.println(element.getName());
}
public void visit(Attribute attr)
{
System.out.println(attr.getName());
}
},
....