设计模式笔记

205 阅读25分钟

创建型

一、单例模式(Singleton)

应用场景:确保一个类只有一个实例,并提供该实例的全局访问点。

使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

实现1 懒汉式-线程不安全

以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。

这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstancenull,那么会有多个线程执行 uniqueInstance = new Singleton(); 语句,这将导致实例化多次 uniqueInstance

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

实现2 饿汉式-线程安全

线程不安全问题主要是由于 uniqueInstance 被实例化多次,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。

但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。

public class Singleton {
	//静态变量保证只执行一次,直接实例化
	private static Singleton uniqueInstance = new Singleton();
    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
}

实现3 懒汉式-线程安全

只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance

但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static synchronized Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

实现4 双重校验锁-线程安全

uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。

双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句:第一个 if 语句用来避免 uniqueInstance 已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 uniqueInstance == null 时两个线程同时进行实例化操作。

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. uniqueInstance 指向分配的内存地址 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 13,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 因此使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

实现5 静态内部类实现

Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCESingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

public class Singleton {

    private Singleton() {
    }
	//静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}

实现6 枚举实现

public enum Singleton {

    INSTANCE;

    private String objName;


    public String getObjName() {
        return objName;
    }


    public void setObjName(String objName) {
        this.objName = objName;
    }


    public static void main(String[] args) {

        // 单例测试
        Singleton firstSingleton = Singleton.INSTANCE;
        firstSingleton.setObjName("firstName");
        System.out.println(firstSingleton.getObjName());
        Singleton secondSingleton = Singleton.INSTANCE;
        secondSingleton.setObjName("secondName");
        System.out.println(firstSingleton.getObjName());
        System.out.println(secondSingleton.getObjName());

        // 反射获取实例测试
        try {
            Singleton[] enumConstants = Singleton.class.getEnumConstants();
            for (Singleton enumConstant : enumConstants) {
                System.out.println(enumConstant.getObjName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。

该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。

应用

  1. Logger Classes
  2. Configuration Classes
  3. Accesing resources in shared mode
  4. Factories implemented as Singletons

二、工厂模式

实现1 简单工厂(Simple Factory)

例子:在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。

这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。

public interface Product {
}
public class ConcreteProduct implements Product {
}
public class ConcreteProduct1 implements Product {
}
public class ConcreteProduct2 implements Product {
}
public class SimpleFactory {

    public Product createProduct(int type) {
        if (type == 1) {
            return new ConcreteProduct1();
        } else if (type == 2) {
            return new ConcreteProduct2();
        }
        return new ConcreteProduct();
    }
}
public class Client {
    public static void main(String[] args) {
        SimpleFactory simpleFactory = new SimpleFactory();
        Product product = simpleFactory.createProduct(1);
        // do something with the product
    }
}

实现2工厂方法(Factory Method)

例子:定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。

在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。

下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。

public abstract class Factory {
    abstract public Product factoryMethod(); //工厂方法
    public void doSomething() {
        Product product = factoryMethod();
        // do something with the product
    }
}
public class ConcreteFactory extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}
public class ConcreteFactory1 extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct1();
    }
}
public class ConcreteFactory2 extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct2();
    }
}

实现3 抽象工厂(Abstract Factory)

例子:提供一个接口,用于创建 相关的对象家族 。 抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。

从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。

应用

工厂方法

  1. java.util.Calendar(opens new window)
  2. java.util.ResourceBundle(opens new window)
  3. java.text.NumberFormat(opens new window)
  4. java.nio.charset.Charset(opens new window)
  5. java.net.URLStreamHandlerFactory(opens new window)
  6. java.util.EnumSet(opens new window)
  7. javax.xml.bind.JAXBContext

抽象工厂

  1. javax.xml.parsers.DocumentBuilderFactory(opens new window)
  2. javax.xml.transform.TransformerFactory(opens new window)
  3. javax.xml.xpath.XPathFactory

三 生成器模式(Builder)

生成器模式又称建造者模式,简单的来说,就是讲一个复杂的对象拆分成一个一个小的对象,然后通过排列组合的方式生成不同的复杂对象。 封装一个对象的构造过程,并允许按步骤构造。

生成器模式涉及4个关键角色: 产品(Product):最终要构建的对象。 抽象生成器(Builder):为创建产品角色对象的各个部分指定抽象接口,一般至少包含有两个方法,一个是建造产品,一个是返回产品。 具体生成器(ConcreteBuilder):实现抽象建造者角色。并且根据不同的情形对于产品创建给出具体的方法。并且能够在创建完成后返回产品实例。 指挥者(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

实现1

创建共享单车 生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质 的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。 这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和 OfoBuilder是具体的建造者;Director是指挥者。

public class Bike {
    private String frame; //车架
    private String seat;

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }
}
public abstract class Builder {
    protected Bike mBike = new Bike();
    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
}
//指挥类
public class Director {
    private Builder mBuilder;
    public Director(Builder builder){
        mBuilder = builder;
    }
    public Bike construct(){
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}
//摩拜单车builder类
public class MobikeBuilder extends Builder {
    @Override
    public void buildFrame() {
        mBike.setFrame("铝合金车架");
    }

    @Override
    public void buildSeat() {
        mBike.setSeat("真皮车座");
    }

    @Override
    public Bike createBike() {
        return mBike;
    }
}
public class OfoBuilder extends Builder {
    @Override
    public void buildFrame() {
        mBike.setFrame("碳纤维车架");
    }

    @Override
    public void buildSeat() {
        mBike.setSeat("橡胶车座");
    }

    @Override
    public Bike createBike() {
        return mBike;
    }
}
public class Test {
    public static void main(String[] args) {
        showBike(new OfoBuilder());
        showBike(new MobikeBuilder());
    }
    private static void showBike(Builder builder){
        Director director = new Director(builder);
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

实现2

StringBuilder实现

应用

  1. java.lang.StringBuilder(opens new window)
  2. java.nio.ByteBuffer(opens new window)
  3. java.lang.StringBuffer(opens new window)
  4. java.lang.Appendable(opens new window)
  5. Apache Camel builders

四、原型模式(Prototype)

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。 原型模式包含如下角色: 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

原型模式的克隆分为浅克隆和深克隆。 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原 有属性所指向的对象的内存地址。 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽 象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。

实现1 浅拷贝

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。

//奖状类
public class Citation implements Cloneable {
    private String name;
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    public void show(){
        System.out.println(name+"同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation c1 = new Citation();
        c1.setName("张三");
        //复制奖状
        Citation c2 = c1.clone();
        //将奖状的名字修改李四
        c2.setName("李四");
        c1.show(); //张三同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
        c2.show(); //李四同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
    }
}

实现2 深拷贝

使用深拷贝需要使用对象流。

public class Student implements Serializable {
    private String name;
    private String address;

    public Student(){

    }

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
//奖状类
public class Citation implements Cloneable, Serializable {
    private Student student;
    public Student getStudent(){
        return student;
    }
    public void setStudent(Student student){
        this.student = student;
    }
    public void show(){
        System.out.println(student.getName()+"同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation c1 = new Citation();
        Student student1 = new Student("张三", "西安");
        c1.setStudent(student1);
        //复制奖状
        Citation c2 = c1.clone();
        //获取c2奖状所属学生对象
        Student student2 = c2.getStudent();
        student2.setName("李四");
        //判断student1和student2是否同一个对象
        System.out.println("student1和student2是否同一个对象吗?"+(student1 == student2)); //true
        c1.show(); //李四同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
        c2.show(); //李四同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
    }
}

student1对象和student2对象是同一个对象,就会产生将student1对象中name属性值改为“李四”,两个 Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的 引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。

public class CitationTest2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation c1 = new Citation();
        Student student1 = new Student("张三", "西安");
        c1.setStudent(student1);

        //创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));
        //将c1对象写出到文件中
        oos.writeObject(c1);
        oos.close();

        //创建对象出入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));
        //读取对象
        Citation c2 = (Citation) ois.readObject();

        //获取c2奖状所属学生对象
        Student student2 = c2.getStudent();
        student2.setName("李四");
        //判断student1和student2是否同一个对象
        System.out.println("student1和student2是否同一个对象吗?"+(student1 == student2)); //false
        c1.show(); //张三同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
        c2.show(); //李四同学:在2020学年第一学期中表现优秀,被评为三好学 生。特发此状!
    }
}

注意:Citation类和Student类必须实现Serializable接口,否则会抛 NotSerializableException异常。

应用

  1. java.lang.Object#clone()

行为型

五、责任链(Chain Of Responsibility)

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。 Handler:定义处理请求的接口,并且实现后继链(successor) 职责链模式主要包含以下角色: 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请 求,如果可以处理请求则处理,否则将该请求转给它的后继者。 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节 和请求的传递过程。

实现1

现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需 要部门经理同意;请求3天到7天还需要总经理同意才行。

//请假条
public class LeaveRequest {
    private String name; //姓名
    private int num; //请假天数
    private String content; //请假内容

    public LeaveRequest(String name, int num, String content) {
        this.name = name;
        this.num = num;
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public int getNum() {
        return num;
    }

    public String getContent() {
        return content;
    }
}
//处理者抽象类
public abstract class Handler {
    protected final static int NUM_ONE = 1;
    protected final static int NUM_THREE = 3;
    protected final static int NUM_SEVEN = 7;

    //该领导处理的请假天数区间
    private int numStart;
    private int numEnd;

    //领导上面还有领导
    private Handler nextHandler;

    //设置请假天数范围 上不封顶
    public Handler(int numStart){
        this.numStart = numStart;
    }

    //设置请假天数范围
    public Handler(int numStart,int numEnd){
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    //设置上级领导
    public void setNextHandler(Handler nextHandler){
        this.nextHandler = nextHandler;
    }

    //提交请假条
    public final void submit(LeaveRequest leave){
        if( 0 == this.numStart ){
            return;
        }
        //如果请假天数达到该领导者的处理要求
        if(leave.getNum() >= this.numStart ){
            this.handlerLeave(leave);
            //如果还有上级,并且请假天数超过了当前领导的处理范围
            if( null != this.nextHandler && leave.getNum() > numEnd ){
                this.nextHandler.submit(leave); //继续提交
            }else{
                System.out.println("流程结束");
            }
        }
    }

    //各级领导处理请假条方法
    protected abstract void handlerLeave(LeaveRequest leave);
}
//小组长
public class GroupLeader extends Handler {
    public GroupLeader(){
        //小组长处理1-3天的请假
        super(Handler.NUM_ONE,Handler.NUM_THREE);
    }

    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("小组长审批:同意。");
    }
}
//部门经理
public class Manager extends Handler {
    public Manager(){
        //部门经理处理3-7天的请假
        super(Handler.NUM_THREE,Handler.NUM_SEVEN);
    }

    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("部门经理审批:同意。");
    }
}
//总经理
public class GeneralManager extends Handler {
    public GeneralManager(){
        //部门经理处理7天以上的请假
        super(Handler.NUM_SEVEN);
    }

    @Override
    protected void handlerLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("总经理审批:同意。");
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        //请假条来一张
        LeaveRequest leave = new LeaveRequest("小花",5,"身体不适");
        //各位领导
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();

        groupLeader.setNextHandler(manager);//小组长的领导是部门经理
        manager.setNextHandler(generalManager);//部门经理的领导是总经理
        // 之所以在这里设置上级领导,是因为可以根据实际需求来更改设置,如果实战中上级领 导人都是固定的,则可以移到领导实现类中。

        // 提交申请
        groupLeader.submit(leave);
    }
}
//打印结果
小花请假5天,身体不适。
小组长审批:同意。
小花请假5天,身体不适。
部门经理审批:同意。
流程结束

应用

  1. java.util.logging.Logger#log()(opens new window)
  2. Apache Commons Chain(opens new window)
  3. javax.servlet.Filter#doFilter()

六、命令模式(Command)

命令模式包含以下主要角色: 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调 用接收者的功能来完成命令要执行的操作。 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收 者,只要它能够实现命令要求实现的相应功能。 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很 多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用 命令对象的入口。

实现1

例子: 将命令封装成对象中,具有以下作用:

  1. 使用命令来参数化其它对象
  2. 将命令放入队列中进行排队
  3. 将命令的操作记录到日志中
  4. 支持可撤销的操作

待完成

应用

  1. java.lang.Runnable(opens new window)
  2. Netflix Hystrix(opens new window)
  3. javax.swing.Action

七、解释器模式(Interpreter)

待完成

八、迭代器模式(Iterator)

九、中介者模式(Mediator)

十、备忘录模式(Memento)

十一、观察者模式(Observer)

例子:气象站有湿度感应装置、温度感应装置、气压感应装置,我们面板需要实时显示湿度、温度、气压这三种数据。

实现1

public interface Subject {
    //注册观察者
    void registerObserver(Observer observer);
    //移除观察者
    void removeObserver(Observer observer);
    //通知观察者更新
    void notifyObservers();
}
public interface Observer {
    void update(float temp,float humidity,float pressure);
}
public class WeatherData implements Subject {
    //观察者集合
    private ArrayList observersList;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observersList = new ArrayList();
    }
    @Override
    public void registerObserver(Observer o){
        observersList.add(o);
    }
    @Override
    public void removeObserver(Observer o){
        int i = observersList.indexOf(o);
        observersList.remove(i);
    }

    @Override
    public void notifyObservers(){
        for (int i = 0; i < observersList.size(); i++) {
            Observer observer = (Observer) observersList.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();
    }
}
public class CurrentConditionsDisplay implements Observer,DisplayElement {
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    @Override
    public void display() {
        System.out.println("CurrentConditionsDisplay{" +
                "temperature=" + temperature +
                ", humidity=" + humidity +
                ", pressure=" + pressure +
                '}');

    }
}
public class Test {
    public static void main(String[] args) {
        //主题Subject
        WeatherData weatherData = new WeatherData();
        //观察者Observer
        CurrentConditionsDisplay display1 = new CurrentConditionsDisplay(weatherData);
        CurrentConditionsDisplay display2 = new CurrentConditionsDisplay(weatherData);

        //主题更新
        weatherData.setMeasurements(80,65,23);
        weatherData.setMeasurements(100,60,50);
    }
}

实现2

Java API内置观察者模式。java.util包内含最基本Observer接口与Observable类。

//主题Subject
public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData(){

    }
    public void measurementsChanged(){
        setChanged();  //Observable类实现的方法
        notifyObservers(); ////Observable类实现的方法
    }
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();;
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
//观察者Observer
public class CurrentConditionDisplay implements Observer {
    Observable observable;
    private float temperature;
    private float humidity;
    private float pressure;
    public CurrentConditionDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable observable, Object arg) {
        if(observable instanceof WeatherData){
            WeatherData weatherData = (WeatherData) observable;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    }

    public void display() {
        System.out.println("CurrentConditionDisplay{" +
                "temperature=" + temperature +
                ", humidity=" + humidity +
                ", pressure=" + pressure +
                '}');

    }
}
public class Test {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        currentConditionDisplay.update(weatherData,null);
        weatherData.setMeasurements(30,50,40);
        weatherData.setMeasurements(20,40,50);
    }
}

缺点

  1. Observer类是Deprecated,不推荐使用。
  2. Observer是一个类而不是一个接口,并且java不支持多重继承,实现起来不方便。

定义了对象之间的一对多依赖,让多个观察者(Observer)同时监听某一主题对象(Subject)。当主题对象变化时,所有观察者都会收到通知并更新。 核心代码是在抽象类中有一个ArrayList用来存放观察者们。 主题对象(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。 观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。

优点:观察者和被观察者是抽象耦合的;建立了一套触发机制 缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

实际例子

  1. java.util.Observer(opens new window)
  2. java.util.EventListener(opens new window)
  3. javax.servlet.http.HttpSessionBindingListener(opens new window)
  4. RxJava

十二、状态模式(State)

十三、策略模式(Strategy)

例子:设计不同多种鸭子
MallardDuck 绿头鸭,会fly,会Quack呱呱叫;
RedheadDuck 红头鸭,会fly,会Quack呱呱叫;
RubberDuck 橡皮鸭子,不会fly,会Squeak橡皮鸭吱吱叫;
DecoyDuck 诱饵鸭,不会fly,MuteQuack什么都不会做,不会叫;

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。(其实类似于if-else模式)

设计原则

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 多用组合,少用继承。
  4. 对修改关闭,对扩展开放(符合开闭原则) 原理是用多态实现的。

改进1

所有的鸭子都继承Duck父类,重写Duck的方法

缺点

每个鸭子子类有各自相同的方法和不同的方法,不同方法都要自己重写覆盖。而且有些鸭子子类无需父类中的方法,但父类已经声明了方法,导致子类也继承了父类的方法。例如Duck中定义fly()方法,Mallard DuckRedhead Duck会飞,而Rubber Duck不会飞,但是继承了Duck类的fly方法,这显然不是我们期望的。

改进2

在上面例子的基础上,把FlyableQuackable定义为接口,子类选择性实现这些接口。

缺点

每个鸭子子类需要自己实现flyquack的方法,代码无法复用。

改进3

public interface QuackBehavior {
    void quack();
}

正常鸭子策略

public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("正常鸭子呱呱叫");
    }
}

橡皮鸭子策略

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("橡皮鸭子吱吱叫");
    }
}

诱饵鸭子策略

public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("诱饵鸭不会叫");
    }
}

使用多态

public class Duck {
    private QuackBehavior quackBehabior;
    public void performQuack(){
        if(quackBehabior != null){
            quackBehabior.quack();
        }
    }
    public void setQuackBehabior(QuackBehavior quackBehavior){
        this.quackBehabior = quackBehavior;
    }
}
public class Test {
    public static void main(String[] args) {
        Duck duck = new Duck();
        //使用正常鸭策略
        duck.setQuackBehabior(new Squeak());
        duck.performQuack();
        //使用橡皮鸭策略
        duck.setQuackBehabior(new Quack());
        duck.performQuack();
        //使用诱饵鸭策略
        duck.setQuackBehabior(new MuteQuack());
        duck.performQuack();
    }
}

总结

策略模式中包含三部分: 策略(Strategy):策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法。 具体策略(ConcreteStrategy):具体策略是实现策略接口的类,具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体算法。 上下文(Context):上下文是依赖于策略接口的类,即上下文包含有策略声明的变量。上下文中提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法。

Strategy接口定义了一个算法族,均实现了behavior()方法。 Context是使用到该算法族的类,它的dosomething()方法会调用behavior(),setStrategy(Strategy)方法可以动态地改变strategy对象,即动态改变Context使用的算法。

优点:符合开闭原则,增加程序的可扩展性和可维护性;避免多个if-else判断 缺点:客户端必须知道所有策略类,并自行决定使用哪一个策略类;此外每增加一个策略就要增加一个类,类数目逐渐增多

与状态模式的比较

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。

实际例子

  1. java.util.Comparator#compare()
  2. javax.servlet.http.HttpServlet
  3. javax.servlet.Filter#doFilter()
  4. Spring框架中的Resourse以及初始化接口InstantiationStrategy

十四、模板方法模式(Template Method)

十五、访问者模式(Visitor)

结构型

十六、适配器模式(Adapter)

十七、桥接模式(Bridge)

十八、组合模式(Composite)

十九、装饰者模式(Decorator)

例子:设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。

设计原则

  1. 类应该对扩展开发,对修改关闭;

实现1

下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 MilkWhip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。

//饮料接口
public interface Beverage {
    double cost();
}
//装饰器基类
public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;
}
//浓咖啡
public class HouseBlend implements Beverage {
    @Override
    public double cost() {
        return 2;
    }
}
//独家咖啡
public class DarkRoast implements Beverage {
    @Override
    public double cost() {
        return 4;
    }
}
//加牛奶配料
public class MilkWhip extends CondimentDecorator {
    public MilkWhip(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return 6 + beverage.cost() ;
    }
}
//摩卡 即加巧克力
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }

    @Override
    public double cost() {
        return 8 + beverage.cost();
    }
}
public class Test {
    public static void main(String[] args) {
        Beverage beverage= new HouseBlend();  //4
        beverage = new Mocha(beverage);       //+8
        beverage = new MilkWhip(beverage);        //+6
        System.out.println(beverage.cost());  //总共18元
    }
}

总结

装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。

应用

  1. java.io.BufferedInputStream(InputStream)
  2. java.io.DataInputStream(InputStream)
  3. java.io.BufferedOutputStream(OutputStream)
  4. java.util.zip.ZipOutputStream(OutputStream)
  5. java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap

二十、享元模式(Flyweight)

二十一、代理模式(Proxy)

参考

CS-Notes 设计模式