《Java设计模式》总结

文章目录

一、设计模式介绍

1 概述

设计模式是从许多优秀的软件系统中总结出的成功的可复用的设计方案。 \color{red}{设计模式是从许多优秀的软件系统中总结出的成功的可复用的设计方案。} 设计模式是从许多优秀的软件系统中总结出的成功的可复用的设计方案。(建筑大师Alexander这样定义设计模式: 每一个设计模式描述一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次一次地使用该方案而不必做重复劳动。)

2 基本要素

  • 名称: 一个设计模式的名称高度概括了该模式的本质,有利于该行业统一术语,便于交流使用。
  • 问题: 描述应该在怎样的环境下使用该模式,解释设计问题和问题存在的前因后果。
  • 方案: 描述设计的组成部分、它们之间的相互关系及各自的职责和协作方式。
  • 效果: 描述模式的应用效果及使用模式应当权衡的问题。主要效果包括使用模式对系统的灵活性、扩充性和复用性的影响。

3 应用场景

  • 正确使用: 一个模式只是成功地解决了某个特定问题的设计方案,完全可以修改模式中的部分结构以符合自己的设计要求。
  • 反模式: 反模式就是从某些软件系统中总结出的不好的设计方案,反模式就是告诉我们如何采用一个不好的方案解决问题。

4 与框架的区别

框架不是模式,框架是针对某个领域,提供用于开发应用系统的类的集合 \color{red}{框架不是模式,框架是针对某个领域,提供用于开发应用系统的类的集合} 框架不是模式,框架是针对某个领域,提供用于开发应用系统的类的集合,框架的应用范围是很具体的,一个框架往往会包含多个设计模式。

二、模式分类(简单工厂不计入)

1 创建型模式

  • 工厂方法模式:Factory Method
  • 抽象工厂模式:Abstract Factory
  • 生成器模式:Builder
  • 原型模式:Prototype
  • 单件模式:Singleton

2 结构型模式

  • 适配器模式:Adapter
  • 组合模式:Composite
  • 代理模式:Proxy
  • 享元模式:Flyweight
  • 外观模式:Facade
  • 桥接模式:Bridge
  • 装饰模式:Decorator

3 行为型模式

  • 责任链模式:Chain of Responsibility
  • 命令模式:Command
  • 解释器模式:Interpreter
  • 迭代器模式:Iterator
  • 中介者模式:Mediator
  • 备忘录模式:Memento
  • 观察者模式:Observer
  • 状态模式:State
  • 策略模式:Strategy
  • 模板方法模式:Template Method
  • 访问者模式:Visitor

三、设计模式详解

1 单件模式 Singleton

a.定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。它是一种创建型模式。

b.类图

在这里插入图片描述

c.特点
  • 单件类中包含用自身声明的类变量,这个类变量是单件类的唯一实例。
  • 单件类的构造方法访问权限是private。
  • 单件类自己负责创建自己唯一的实例,并提供访问该实例的类方法(static方法)。
d.优缺点

优点:

  • 单件类的唯一实例由单件类本身控制,所以可以很好地控制用户何时访问它。
  • 由于在系统中只存在一个对象,因此可以节约系统资源,相对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统性能。

缺点:

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责”原则。因为单例类既充当了工厂角色,又充当了产品角色,包含一些业务方法,将实例的创建和本身的功能融为了一体。
e.适用场景
  • 当系统需要某个类只能有一个实例。
f.最佳实践

枚举单例方式 > 静态内部类方式(双检锁懒汉式)> 饿汉式 > 加锁懒汉式 > 懒汉式

  1. 饿汉式

    说明:

    • 类加载到内网后就实例化一个单例,JVM保证线程安全(类只加载一次)

    • 缺陷:不管实例是否使用,类加载时就完成实例化

    public class Mgr1 {
    
        private static final Mgr1 INSTANCE = new Mgr1();
    
        private Mgr1() {}
    
        public static Mgr1 getInstance() {
            return INSTANCE;
        }
    
        public static void main(String[] args) {
            Mgr1 m1 = Mgr1.getInstance();
            Mgr1 m2 = Mgr1.getInstance();
    
            System.out.println(m1 == m1);
        }
    }
    
  2. 懒汉式

    说明:

    • 解决了饿汉式不使用实例的时候也加载实例的问题
    • 缺陷:存在线程不安全问题
    public class Mgr2 {
    
        private static Mgr2 INSTANCE;
    
        private Mgr2() {}
    
        public static Mgr2 getInstance() {
            if (INSTANCE == null) {
    
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                INSTANCE = new Mgr2();
            }
            return INSTANCE;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> System.out.println(Mgr3.getInstance().hashCode())).start();
            }
        }
    }
    
  3. 懒汉式(加锁)

    说明:

    • 解决了懒汉式线程不安全问题
    • 缺陷:性能会有所下降
    public class Mgr3 {
    
        private static Mgr3 INSTANCE;
    
        private Mgr3() {}
    
        public static synchronized Mgr3 getInstance() {
            if (INSTANCE == null) {
    
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                INSTANCE = new Mgr3();
            }
            return INSTANCE;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> System.out.println(Mgr3.getInstance().hashCode())).start();
            }
        }
    }
    
  4. 懒汉式(双检锁)

    说明:

    • 解决了懒汉式的线程不安全问题
    • 解决了懒汉式(加锁)方式的性能下降问题:通过减少同步代码块的方式提交效率(将同步方法方式改为同步代码方式)
    public class Mgr4 {
    
        private static volatile Mgr4 INSTANCE;
    
        private Mgr4() {}
    
        public static Mgr4 getInstance() {
            // 首先判断实例是否存在(此处判断的意义:很多线程进来可以直接取用已经存在的实例而不用去阻塞等待锁,然后再进入锁里面的判断)
            if (INSTANCE == null) {
                // 可能存在多个线程进入这一步阻塞等待
                synchronized (Mgr4.class) {
                    // 第一个线程拿到锁,判断实例不为空则进行下一步
                    if (INSTANCE == null) {
    
                        // 此处是为了测试,实际上是不需要的
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        /*
                         * 由于编译器、JVM以及操作系统的优化,可能会出现指令重排(happen-before规则下的指令重排,即指令顺序优化排列但执行结果不变)
                         * new Mgr4()这一步大概有三个步骤:
                         * 1.在堆中开辟对象所需空间,分配内存地址
                         * 2.根据类加载的初始化顺序进行初始化
                         * 3.将内存地址返回给栈中的引用变量
                         *
                         * 由于指令重排的出现,这三条指令的执行顺序可能会被打乱,从而导致3和2的顺序调换
                         */
                        INSTANCE = new Mgr4();
                    }
                }
            }
            return INSTANCE;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> System.out.println(Mgr4.getInstance().hashCode())).start();
            }
        }
    }
    
  5. 静态内部类方式

    说明:

    • JVM保证单例(类只加载一次)
    • 加载外部类时不会加载内部类,这样可以实现懒加载
    public class Mgr5 {
    
        private Mgr5() {}
    
        private static class Mgr5Holder {
            private static final Mgr5 INSTANCE = new Mgr5();
        }
    
        public static Mgr5 getInstance() {
            return Mgr5Holder.INSTANCE;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> System.out.println(Mgr5.getInstance().hashCode())).start();
            }
        }
    }
    
  6. 枚举单例方式

    说明:

    • 不仅可以解决线程同步,还可以防止反序列化(枚举类自身没有构造方法; 枚举Enum是个抽象类,一旦一个类声明为枚举,实际上就是继承了Enum)
    public enum Mgr6 {
    
        // 枚举单例
        INSTANCE;
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(() -> System.out.println(Mgr6.INSTANCE.hashCode())).start();
            }
        }
    }
    

2 策略模式 Strategy

a.定义

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。本模式使得算法可独立于使用它的客户而变化。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 策略模式是对算法的封装,它把算法的责任和算法本身分割开来,委派给不同的对象管理。
  • 在策略模式中,由客户端自己决定在什么情况下使用什么具体策略。
  • 策略模式仅仅封装算法,提供新算法的插入到已有系统中,以及老算法从系统中“退休”方案,策略模式并不决定在何时使用何种算法。
d.优缺点

优点:

  • 上下文和具体策略是松耦合关系。
  • 策略模式满足“开-闭原则”。
  • 策略模式提供了管理相关算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多种条件判断语句。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一种策略。
  • 策略模式将产生很多策略类,可以通过享元模式在一定程度上减少对象的数量。
e.适用场景
  • 一个类的方法中多种行为以多个条件语句出现。
  • 程序不希望暴露复杂的、与算法相关的数据结构。
  • 需要使用一个算法的不同变体。
f.扩展
  • 策略模式与状态模式
    • 可以通过环境类状态的个数来决定使用策略模式还是状态模式。
    • 策略模式的环境类自己选择一个具体策略类,具体策略无需关心环境类;而状态模式的环境类由于外在因素需要放入一个具体状态,以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
    • 使用策略模式,客户端需要知道所选的具体策略是哪一个;而使用状态模式客户端无需关系具体状态,环境类的状态会根据用户的操作自动切换。
    • 如果系统中某个对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。
  • 策略模式与继承
    • 使用继承,子类和父类中的其他代码(非重写方法)是紧耦合关系;而策略模式中子类和父类是松耦合关系。
    • 为了系统的扩展性和复用性,就得考虑面向对象的基本原则之一:“少用集成,多用组合”,而策略模式才用的就是组合的方式。
g.最佳实践
  1. Comparable(非策略模式)
  2. Comparator(策略模式)
public class StrategyContext {

    public static void main(String[] args) {
        // Comparable可简单理解为一个内比较器
        int baseNum = 10;
        Comparable<Integer> myComparable = num -> {
            if (baseNum < num) {
                return -1;
            } else if (baseNum > num) {
                return 1;
            }
            return 0;
        };
        System.out.println(myComparable.compareTo(12));

        Person basePerson = new Person("Wangwu", 28);
        Comparable<Person> personComparable = p -> {
            if (basePerson.getAge() > p.getAge()) {
                return 1;
            } else if (basePerson.getAge() < p.getAge()) {
                return -1;
            }
            return 0;
        };
        int comparableResult = personComparable.compareTo(new Person("Zhaoliu", 25));
        System.out.println("comparableResult: " + comparableResult);

        // Comparator可理解为一个外比较器,不改变自身对象,而用一个策略对象来改变它的行为
        Comparator<Integer> myComparator = (o1, o2) -> {
            if (o1 < o2) {
                return -1;
            } else if (o1 > o2) {
                return 1;
            }
            return 0;
        };
        int comparatorResult = myComparator.compare(10, 8);
        System.out.println(comparatorResult);

        Comparator<Person> personComparator = (o1, o2) -> {
            if (o1.getAge() > o2.getAge()) {
                return 1;
            } else if (o1.getAge() < o2.getAge()) {
                return -1;
            }
            return 0;
        };
        int personResult = personComparator.compare(new Person("Zhangsan", 18), new Person("Lisi", 23));
        System.out.println("personResult: " + personResult);
    }
}

@Data
@AllArgsConstructor
class Person {
    private String name;
    private int age;
}

3 简单工厂模式 Simple Factory

a.定义

又称静态工厂方法(Static Factory Method)模式,在简单工厂模式中,可以根据参数的不同返回不同的实例。简单工厂模式专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有相同的父类。简单工厂模式又称为静态工厂模式,它是一种创建模式。

b.类图

在这里插入图片描述

c.特点
  • 只需要传入一个正确的参数就可以获取用户所需要的对象,而无须知道其创建细节。
d.优缺点

优点:

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品的实例,客户端可以免除创建对象的责任,只需要消费产品;简单工厂模式通过这种方式实现了责任的分隔,它提供专门的工厂类来创建对象。
  • 客户端无需知道所创建的具体产品的类名,只需要知道具体产品类对应的参数即可,对于一些复杂的类名,可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,一定程度上提高了系统的灵活性。

缺点:

  • 由于工厂类集中了所有产品的创建逻辑,一旦不能正常使用,整个系统都要受到影响。
  • 使用简单工厂模式将会增加系统中类的个数,一定程度上增加了系统的复杂度和理解难度。
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  • 简单工厂模式由于使用了静态方法,造成工厂角色无法形成基于继承的等级结构。
e.适用场景
  • 工厂类负责创建的对象较少:由于负责创建对象较少,不会造成工厂类的逻辑太过复杂。
  • 客户端既不需要关心创建对象的细节,甚至连具体的类名都不需要记住,只需要知道具体类对象的参数即可。
f.最佳实践
  • 获取日期格式:java.text.DateFormat

    public final static DateFormat getDateInstance() {}
    public final static DateFormat getDateInstance(int style) {}
    public final static DateFormat getTimeInstance(int style, Locale aLocale) {}
    
  • 获取不同加密算法的密钥生成器:javax.crypto.KeyGenerator

    public static final KeyGenerator getInstance(String var0) {}
    public static final KeyGenerator getInstance(String var0, String var1) {}
    
  • 创建密码器:javax.crypto.Cipher

    public static final Cipher getInstance(String var0) {}
    public static final Cipher getInstance(String var0, Provider var1) {}
    

4 工厂方法模式 Factory Method

a.定义

定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。工厂方法模式又称为多态工厂模式,它是一种创建型模式。

b.类图

在这里插入图片描述

c.特点
  • 在一个接口或者抽象类中定义一个抽象方法,该方法返回某个类的子类的实例,该抽象类或接口让其子类或者实现该接口的类通过重写这个抽象方法返回某个子类的实例。
  • 一个具体工厂中,只有一个工厂方法或者一组重载的工厂方法。
d.优缺点

优点:

  • 使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦。
  • 工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。
  • 系统在加入新的产品时,只需要添加一个具体工厂和具体的产品就可以了,而无需做其他的修改,完全符合“开闭原则”。

缺点:

  • 在添加新产品时,需要添加新的具体产品类和具体工厂类,一定程度上增加了系统的复杂度,给系统带来一些额外的开销。
  • 考虑到系统的可扩展性,需要引入抽象层,增加了系统的理解难度,且在实现的时候可能会用到DOM、反射等技术,增加了系统的实现难度。
e.适用场景
  • 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合。
  • 用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用。
f.扩展
  • 使用多个工厂方法

    在抽象工厂角色中可以定义多个不同的工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,满足不同产品对象的需求。

  • 产品对象的重复使用

    工厂对象将已经创建过的产品保存到一个集合或数组中,然后根据客户端需求,对集合进行查询。

  • 多态性的丧失和模式的退化

    一般来说,工厂对象应该有一个抽象的父类型,如果等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,也就是发生了退化。当只有一个具体工厂,在具体工厂中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式也就退化成了简单工厂模式。

g.最佳实践
  • 获取数据库连接

    Connection connection = DriverManager.getConnection("jdbc:microsoft:sqlserver://loc
    alhost:1433; DatabaseName=DB;user=sa;password=123456");
    

5 抽象工厂模式 Abstract Factory

a.定义

提供一个创建一系列或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式又称为工具箱(Kit或ToolKit模式),它是一种创建型模式。

b.类图

在这里插入图片描述

c.特点
  • 在一个抽象类或接口中定义若干个抽象方法,这些抽象方法分别返回某个类的实例,该抽象类或接口让其子类或实现该接口的类重写这些抽象方法为用户提供一系列相关的对象。
d.优缺点

优点:

  • 抽象工厂模式可以为用户创建一系列相关的对象,使用户和创建这些对象的类脱耦。
  • 使用抽象工厂模式可以方便为用户配置一系列对象。用户使用不同的具体工厂就能得到一组相关的对象,同时也能避免用户混用不同系列中的对象。
  • 在抽象工厂模式中,可以随时为用户增减“具体工厂”为用户提供一组相关的对象。
  • 应用抽象工厂模式可以实现高内聚低耦合的目的。
  • 增加新的具体工厂或产品族无需修改已有系统,符合“开闭原则”。

缺点:

  • 在添加新的产品对象时,难以扩展抽象工厂来生产新的产品,因为抽象工厂中规定了所有可能被创建的产品集合,对接口进行扩展会涉及到抽象工厂角色及所有子类的修改。
  • 开闭原则的倾斜性(增加新的工厂及产品族容易,增加新的等级结构麻烦)。
e.适用场景
  • 系统需要为用户提供多个对象,但不希望用户直接使用new运算符实例化这些对象,即希望用户和创建对象的类脱耦。
  • 系统需要为用户提供多个对象,以便用户联合使用它们,但又不希望用户来决定对象是如何关联的。
  • 系统需要为用户提供一系列对象,但只需要用户知道这些对象有哪些方法可用,不需要用户知道这些对象的创建过程。
  • 系统提供的工厂所需生产的具体产品不是一个简单的对象,而是多个位于不同产品的等级结构中属于不同类型的具体产品。
f.扩展
  • 开闭原则的倾斜性

    抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,但不能为新的产品等级结构的增加提供这样的方便。

  • 工厂模式的退化

    当抽象工厂模式中每一个具体工厂只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式就退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。

g.最佳实践
  • 软件系统中需要更换界面主题,要求界面中的按钮、文本框、背景色等一起改变。

6 外观模式 Facade

a.定义

为子系统中的一组接口提供一个统一的界面,该模式定义了一个高层接口,这个接口使得这个子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 为子系统提供一个称作外观的类,该外观类的实例负责与子系统中类的实例打交道。
  • 其目的在于降低系统的复杂度。
d.优缺点

优点:

  • 使客户和子系统中的类无耦合,并且是子系统使用起来更加方便。
  • 外观类只提供一个更加简洁的界面,并不影响用户直接使用子系统中的类。
  • 子系统中任何类对其方法的内容进行修改, 不影响外观类的代码。

缺点:

  • 不能很好地限制客户端使用子系统类,如果对客户端访问子系统做太多限制则减少了可变性和灵活性。
  • 在不引入抽象外观类的情况下,增加新的子系统类可能需要修改外观类或客户端的代码,违背了“开闭原则”。
e.适用场景
  • 对于一个复杂的系统,需要为用户提供一个简单的交互操作。
  • 不希望客户端代码和子系统的代码存在耦合,以提高子系统的独立性和可移植性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,从而降低层之间的耦合度。
f.扩展
  • 一个系统有多个外观类

    在外观模式中,通常情况下只需要一个外观类,并且此外观类只需要一个实例,即它是一个单例类。

  • 不要试图通过外观类为子系统增加新的行为

    外观类的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统中加入新的行为。新的行为应该通过修改原来的子系统类或增加新的子系统类来实现。

  • 外观模式和迪米特法则

    外观模式创造出一个外观对象,使得客户端所涉及的属于一个子系统中的协作伙伴减到最少, 使得客户端与子系统内部对象的相互作用被外观对象所取代,外观对象降低了客户端与子系统的耦合度。外观模式是代码重构以便达到“迪米特法则”要求的一个强有力的武器。

  • 抽象外观类的引入

    外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或移除子系统时需要修改外观类,可以通过引入外观抽象类在一定程度上解决该问题,客户端针对抽象外观类进行编程。

g.最佳实践
  • SLF4J日志框架

7 中介者模式 Mediator

a.定义

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 将对象之间的交互封装在称作中介者的对象中,中介者使各对象不需要显式地相互引用,这些对象只包含中介者的引用。
  • 中介者承担两方面的职责:
    • 中转作用(结构性):有了中介者,各具体同事之间不需要再显式地相互引用,需要通信的时候通过中介者即可。
    • 协调作用(行为性):中介者可以进一步对同事之间的关系进行封装,同事可以一致地与中介者进行交互,而不需要指明中介者应该怎么做,中介者通过自身的内部协调,对同事的请求做进一步处理,将同事成员之间的关系行为进行分离和封装。
d.优缺点

优点:

  • 可以避免许多对象为了之间的通信而相互显式地引用,否则不仅系统难于维护,也使其他系统难以复用这些对象。
  • 可以通过中介者将原本分布于多个对象之间的交互行为集中到一起。当这些对象需要改变相互之间的通信行为时,只需要使用一个具体中介者即可。
  • 具体中介者使各个具体同事之间完全解耦,修改任何一个具体同事的代码不会影响到其他同事。
  • 具体中介者集中了各同事之间的交互细节,使系统比较清楚地知道整个系统的同时是如何交互的。
  • 当一些对象想要相互通信,但又无法相互包含对方的引用时,可以使用中介者模式。

缺点:

  • 在具体中介类中包含了各同事之间的交互细节,可能导致中介者类异常复杂,使得系统难以维护。
e.适用场景
  • 系统中存在复杂的引用关系,产生的相互依赖关系结构混乱也难以理解。
  • 一个对象引用了其他很多对象,导致难以复用该对象。
f.扩展
  • 中介者模式与迪米特法则

    在中介者模式中,通过创建一个中介者对象,将系统中有关的对象中所引用的其他对象减到最少,这是迪米特法则的一个典型应用。

  • 中介者模式与GUI开发

    中介者模式可以很方便地应用到图形界面(GUI)开发中,在比较复杂的界面中可能存在多个组件之间的交互行为。我们可以将这些交互的组件作为具体的同事,将它们之间的引用和控制关系交由中介者负责,在一定程度上简化系统的交互。

g.最佳实践
  • MVC架构中的控制器

    Controller作为一种中介者,它负责控制视图对象View和模型对象Model之间的交互。如在Struts中,Action就作为JSP页面与业务对象之间的中介者。

  • 消息中间件MQ

8 责任链模式 Chain of Responsibility

a.定义

通过使用多个对象来处理请求,从而避免发送者和接收者之间的耦合关系。将这多个对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 将用户的请求分派给许多对象,这些对象被组织成一个责任链,即每个对象含有后继对象的引用;并要求每个对象能处理用户请求就做处理,否则将用户请求传递给下一个对象。
d.优缺点

优点:

  • 责任链中的对象只和自己的后继是低耦合关系,和其他对象毫无关联,这使得编写处理者对象以及创建责任链变得非常容易。
  • 当在处理者中分配职责时,责任链给应用程序更多的灵活性。
  • 应用程序可以动态地添加、删除、处理者或者重新指派处理者的职责。
  • 应用程序可以动态地改变处理者之间的先后顺序。
  • 使用责任链的用户不必知道处理者的信息,也不会知道到底是哪个对象处理了请求。
e.适用场景
  • 有许多对象可以处理用户的请求,希望程序在运行期间自动确定处理用户请求的那个对象。
  • 希望用户不必明确指定接收者的情况下,向多个接受者的某一个提交请求。
  • 程序希望动态地指定可处理用户请求的对象的集合。
f.最佳实践
  • 过滤器Filter
  • 拦截器Interceptor

9 装饰模式 Decorator

a.定义

动态地给对象增加一些额外的职责。就功能来说,装饰模式相比生成子类更为灵活。装饰模式又称为包装器,它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • “具体组件”和“装饰”都是”抽象组件“的子类,因此”抽象组件“声明的对象既可以存放”被装饰者“的引用也可以存放”装饰者“的引用。
  • 由于”装饰“是”抽象组件“的子类,因此也可以把”装饰者“作为一个”被装饰者“,这意味着可以使用具体装饰类来”装饰“具体装饰类的实例。
d.优缺点

优点:

  • 被装饰者和装饰者是松耦合关系。
  • 装饰模式满足”开闭原则“。不必修改具体组件,就可以增加新的针对改具体组件的具体装饰。
  • 可以使用使用多个不同的具体装饰类以及这些类的组合来装饰具体组件的实例,得到更为强大的对象。

缺点:

  • 使用装饰模式对系统进行设计时会产生很多装饰类和小对象,这些装饰类和小对象的产生将增加系统的复杂度,加大学习和理解的难度。
  • 对于多次装饰的对象,调试时寻找错误需要逐级排查,较为繁琐。
e.适用场景
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给对象增加功能,且这些功能能够被动态地撤销。
  • 当不能采用继承的方式对系统进行扩充或采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两种:第一种是系统中存在大量的独立的扩展,为支持每一种组合将产生大量的子类,是的子类的数目呈爆炸式增长;第二种是因为类定义不能被继承,如被final修饰的类。
f.扩展
  • 装饰模式与继承
    • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而继承是一种耦合度比较大的静态关系,无法在程序运行时进行扩展。
    • 使用装饰模式来实现扩展比继承更加灵活。
  • 装饰模式的简化
    • 一个装饰类的接口或抽象类必须与被装饰类的接口或抽象类保持一致,对于客户端来说,不论是装饰之前的对象还是装饰之后的对象都能一致对待。
    • 尽量保持具体构件类作为一个“轻”类,也就是不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展;如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。
g.最佳实践

10 观察者模式 Observer

a.定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。装饰模式又称为发布订阅模式,它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 建立一种对象与对象之间的依赖关系,一个对象发生改变时自动通知到其他对象,其他对象作出相应的反应。发生改变的对象称为目标,而被通知的对象称为观察者。
  • 一个观察者目标可以对应多个观察者,而这些观察者之间之间没有联系,观察者目标可以根据需要增减观察者,使得系统更易于扩展。
d.优缺点

优点:

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了消息更新传递机制,抽象了更新接口,使得不同的表示层作为具体的观察者角色。
  • 在观察目标和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式满足“开闭原则”。

缺点:

  • 如果一个观察目标对象有很多个观察者,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者与观察目标之间存在循环依赖,会触发它们之间的循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道观察目标是如何发生变化的,而仅仅只是知道观察目标发生了变化。
e.适用场景
  • 一个模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变导致其他对象也发生改变,但不知道具体有多少对象发生改变,可以降低对象之间的耦合度。
  • 一个对象改变必须通知其他对象,但不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,可以使用观察者模式创建一种链式触发机制。
f.扩展
  • 观察者模式中的推数据和拉数据
    • 推数据方式是指具体主题将变化后的数据全部交给具体观察者,即将变化后的数据传递给具体观察者用户更新数据的方法的参数。当具体主题认为具体观察者需要这些变换后的全部数据时往往采用推数据的方式。
    • 拉数据方式是指具体主题不将变化后的数据交给具体观察者,而是提供了获得这些数据的方法,具体观察者在得到通知之后,可以调用具体主题提供的方法得到数据,但需要自己判断数据是否发生变化。当具体主题不知道具体观察者是否需要这些变换后的数据时往往采用拉数据的方式。
  • MVC模式
    • MVC是一种架构模式,它包含三种角色:模式(Model)、视图(View)、控制器(Controller)。观察者模式可以用来实现MVC模式,观察目标就是M,观察者就是V,C充当两者之间的中介者(Mediator)。当模型层数据发生变化时,视图层将自动更新。
g.最佳实践
  • 在JDK的java.util包中,提供了Observable类和Observer接口,它们构成了Java语言对观察者模式的支持。
  • Listener
  • java.awt

11 组合模式 Composite

a.定义

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使用户对单个对象和组合对象的使用具有一致性。它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 组合模式是关于怎样将对象形成树形结构来表现整体和部分层次结构的成熟模式。
  • 组合模式的关键在于无论是个体对象还是组合对象,都实现了相同的接口或都是同一个抽象类的子类。
d.优缺点

优点:

  • 组合模式包含了个体对象和组合对象,并形成树形结构,使用户可以方便地处理个体对象和组合对象。
  • 组合对象和个体对象实现了相同的接口,用户一般无需区分个体对象和组合对象。
  • 当增加新的Composite节点和Leaf节点时,用户的重要代码不需要做修改,符合“开闭原则”。
e.适用场景
  • 当想要表示对象的部分-整体层次结构。
  • 希望用户用一致的方式处理个体对象和组合对象。
f.最佳实践

12 享元模式 Flyweight

a.定义

运用享元模式可以有效地支持大量的细粒度对象的复用。它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 享元模式通过共享技术实现相同或相似对象的重用。
  • 在享元模式中可以共享的相同内容成为内部状态,而那些需要外部环境来设置的不能共享的内容称为外部状态。
  • 在享元模式中通常会出现工厂模式,一般需要创建一个享元工厂来负责维护一个享元池,用于存储具有相同内部状态的享元对象。
  • 在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。
d.优缺点

优点:

  • 它可以极大地减少内存中对象的数量,使得相同或相似对象在内存中只保存一份。
  • 享元模式的外部对象相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境下被共享。

缺点:

  • 享元模式使系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象状态外部化,而读取外部状态使得运行时间变长。
e.适用场景
  • 一个系统中有大量相同或相似的对象,由于这些对象的大量使用,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此应当在多次重复使用享元对象时才值得使用享元模式。
f.扩展
  • 单纯享元模式和复合享元模式
    • 单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可以共享,不存在非共享具体享元类。
    • 复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
  • 享元模式和其他模式联用
    • 在享元模式的享元工厂中,通常提供一个静态的工厂方法用于返回享元对象。
    • 在一个系统中,通常只有唯一一个享元工厂,因此享元工厂可以使用单例模式来设计。
    • 享元模式可以结合组合模式形成复合享元模式,统一对享元对象设置外部状态。
g.最佳实践
  • Java中的String类型

13 代理模式 Proxy

a.定义

为其他对象提供一种代理以控制对这个对象的访问。它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 在代理模式中,代理对象与被代理对象实现了相同的接口,也就是说代理对象和它所代理的对象向用户公开了相同的方法。当代理对象确定它所代理的对象能调用相同的方法时,就把实际的方法调用委派给它所代理的对象。
d.优缺点

优点:

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化处理并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

缺点:

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
e.适用场景
  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机,也可以在另一台主机,远程代理又叫做使节(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,可以先创建一个相对较小的对象来表示,真实对象应该只在需要的时候才被真正地创建。如图片代理,用户通过浏览器访问网页时,在代理对象的方法中先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端;当需要浏览大图片时,再将大图片在新网页中显示出来;如果用户在浏览大图时加载工作还没完成,可以再启动一个线程爱显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
  • 写时复制(Copy-on-Write,简称COW)代理:它是虚拟代理的一种,把复制操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深度克隆是一个开销较大的操作,COW代理可以让这个操作延迟到对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同的使用权限。
  • 缓冲(Cache)代理:为一个目标操作的结果提供临时的存储空间, 以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标,不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时提供一些额外的操作,如将次对象被调用次数记录下来等。
  • 在应用程序启动时,可以使用代理对象代替真实的对象初始化,节省了内存的占用,并大大加速了系统启动的时间。
f.扩展
  • 动态代理:
    • 动态代理代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。
    • 在传统代理模式中,真实的主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类的个数急剧增加。如何减少传统代理模式下系统中类的个数和如何在事先不知道真实主题角色的情况下使用代理,是动态代理解决的问题。
g.最佳实践
  • EJB、Web Service等分布式技术都是代理模式的应用。

14 迭代器模式 Iterator

a.定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 将遍历集合的任务交给一个称作迭代器的对象。
d.优缺点

优点:

  • 用户使用迭代器遍历集合中元素,而不需要知道这些对象在集合中是如何表示和存储的。
  • 用户可以同时使用多个迭代器遍历一个集合。
e.适用场景
  • 让用户访问一个集合中的对象,但不想暴露对象在集合中的存储结构。
  • 希望对遍历不同的集合提供一个统一的接口。
f.扩展
  • 迭代器的next()和集合的get(int index)方法
    • 有些集合的存储结构不是顺序结构,如LinkedList使用列表链表存储,而有些集合的存储结构是顺序结构,如ArrayList使用数组存储;因此链表调用get(int index)方法访问数据的速度比数组调用get(int index)返回数据的速度要慢。
    • 当用户需要遍历集合中的对象时,应用使用该集合提供的迭代器而不是让集合本身来遍历其中的对象,因为迭代器遍历集合的方法在找到集合中一个对象的同时也得到带遍历的后继对象的引用,所以速度较快。
g.最佳实践
  • JDK中的集合就是迭代器模式的应用。

15 访问者模式 Visitor

a.定义

表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 不改变类的情况下可有效地增加其上的操作。使用“双重分派”技术:被访问者首先调用accept方法接收访问者,而被接收的访问者在调用visit方法访问当前对象。
d.优缺点

优点:

  • 可以在不改变一个集合中元素的类的情况下,增加新的施加于该元素上的新操作。
  • 可以将集合中各元素的某些操作集中到访问者中,不仅便于集合的维护,也有利于集合中元素的复用。
e.适用场景
  • 一个对象结构(比如某个集合)中,包含很多对象,相对集合中的对象增加一些新的操作。
  • 需要对集合中的对象进行很多不同的并且不相关的操作,而又不想修改对象的类。
f.最佳实践
  • 各种语言的编译器。

16 生成器模式 Builder

a.定义

将一个复杂对象的构建与它的表示分离,是同样的构建过程可以创建不同的表示。生成器模式又称为创建者模式,它是一种创建型模式。

b.类图

在这里插入图片描述

c.特点
  • 将一个含有多个组件对象的创建分成若干个步骤,并将这些步骤封装在一个称作生成器的接口中。
d.优缺点

优点:

  • 生成器模式将对象的构造封装在具体的生成器中,用户使用不同的生成器就可以得到该对象的不同表示。
  • 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无需了解该对象的具体组件。
  • 生成器将对象的构造过程分解成若干步骤,是程序可以更加精细地有效地控制整个对象的构造。
  • 生成器模式将对象的构造过程与创建该对象类解耦,使对象的创建更加灵活有弹性。
  • 当增加新的具体生成器时,不必修改指挥者的代码,即该模式满足开-闭原则。

缺点:

  • 生成器模式所创建的对象一般具有较多共同点,其组成部分相似,若产品之间的差异较大则不宜使用生成器模式,因此其使用范围受到一定限制。
  • 如果产品的内部变化复杂,可能需要定义很多的具体生成器来实现这种变化,导致系统变得很庞大。
e.适用场景
  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  • 需要生成的产品对象属性相互依赖,需要指定其生成顺序。
  • 对象的创建过程独立于该对象的类。在生成器模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在生成器类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
f.扩展
  • 生成器模式简化:
    • 省略抽象生成器角色:若系统中只存在一个具体生成器,则可以省略抽象生成器。
    • 省略指挥者角色:在具体生成器角色只有一个的情况下,若抽象生成器角色已经被省略,那么还可以省略指挥者角色,让Builder角色扮演指挥者和生成器双重角色。
  • 生成器模式与抽象工厂模式:
    • 生成器模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
    • 抽象工厂模式中,客户端实例化工厂类,然后调用工厂类方法获取所需产品对象;而在建造者模式中,客户端不直接调用建造者的方法,而是通过指挥类来指导如何生成对象包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整对象。
    • 抽象工厂模式可以看做汽车配件生产工厂,生产一个产品族的产品;而建造者模式就是一个汽车组装工厂,通过对部件的组装可以得到一辆完整的汽车。
g.最佳实践
  • 小技巧:链式编程

17 适配器模式 Adapter

a.定义

将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于借口不兼容而不能一起工作的那些类可以一起工作。适配器也称为包装器,它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 该模式中涉及有目标、被适配者以及适配器,其关键是建立一个适配器,这个适配器实现了目标接口并包含有被适配者的引用。
d.优缺点

优点:

  • 将目标类和被适配者类解耦,通过一个适配器来重用现有的适配者类,而无须修改原有代码;
  • 增加了类的透明性和复用性,将具体实现封装在被适配者中,对客户端透明,而且提高了被适配者类的复用;
  • 灵活性和扩展性较好,通过配置文件可以灵活切换适配器,也可以在不修改原有代码基础上增加新的适配器,符合“开闭原则”。
  • 类适配器:
    • 由于适配器是被适配者类的子类,因此可以在适配器中置换一些被适配者类的方法,使得适配器更灵活;
  • 对象适配器:
    • 一个对象可以把不同的被适配者对象适配到同一个目标,也就是说同一个适配器可以把被适配者类及其子类均适配到同一个目标。

缺点:

  • 类适配器:
    • 对于Java/C#等支持单继承的语言,一次最多适配一个被适配者类,且目标对象类只能为抽象类,其使用有一定的局限性,不能将一个被适配者类和它的子类都适配到目标接口;
  • 对象适配器:
    • 与类适配器相比,想要置换被适配者类的方法不容易。
e.适用场景
  • 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的类。
f.扩展
  • 默认适配器或缺省适配器:
    • 当不需要全部实现接口提供的方法时,可以先设计一个抽象类,给出该接口的默认实现(可以是空方法),然后适配器有选择地覆盖该抽象类的方法来实现需求,也成为单接口适配器模式
g.最佳实践
  • Java中IO操作,如InputStreamReader是InputStream和Reader的适配器
  • JDBD通过JDBC-ODBC-Bridge -> ODBC访问SQLServer,JDBC-ODBC-Bridge是JDBC和ODBC的适配器
  • IteratorEnumeration是Iterator和Enumeration的适配器

18 桥接模式 Bridge

a.定义

将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式又称为柄体模式或接口模式,它是一种结构型模式。

b.类图

在这里插入图片描述

c.特点
  • 桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码量。
d.优缺点

优点:

  • 分离抽象接口及其实现部分;
  • 桥接模式有时类似于多继承方案,但多继承方案违背了“单一职责原则”复用性较差,且继承结构中类的数量庞大,桥接模式是比多继承方案更好的解决方案。
  • 桥接模式提高了系统的可扩充性,在两个变化维度的任意扩展一个维度,都不需要修改原有系统;
  • 实现细节对用户透明。

缺点:

  • 桥接模式会增加系统的理解与设计难度,由于聚合关系建立在抽象层,要求开发者对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
e.适用场景
  • 系统需要在构件的抽象化和具体化之间增加更多的灵活性,避免在两者之间建立静态继承关系,可通过桥接模式建立关联关系;
  • 抽象角色和具体角色可以以继承的方式独立扩展而互不影响,系统需要对抽象化角色和具体化角色进行动态耦合;
  • 一个类存在两个变化的维度,且两个维度都需要进行独立扩展;
  • 不希望使用继承而使得系统中类的个数激增的时候,可以使用桥接模式。
f.扩展
  • 桥接模式和适配器模式的联用:
    • 桥接模式和适配器模式通常用于系统设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其拆分为抽象化和具体化的两个角色,使它们分别进行变化;而在初步设计完成之后,当发现系统与已有类无法系统工作时,可以采用适配器模式。
    • 有时候在设计之初也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。
g.最佳实践
  • AWT中的Peer架构

19 命令模式 Command

a.定义

将一个请求封装成一个对象,从而使用户可用不同的请求对客户进行参数化;对请求进行排队或记录请求日志,以及支持可撤销的操作。命令模式又称为动作模式或事务模式,它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 对命令进行封装,将发出命令的责任和执行命令的责任分割开来。
d.优缺点

优点:

  • 降低系统的耦合度;
  • 新的命令很容易加入到系统中;
  • 比较容易设计一个命令队列和宏命令;
  • 可以比较方便地实现对请求的Undo和Redo。

缺点:

  • 使用命令模式可能会使系统中出现过多的具体命令类。
e.适用场景
  • 系统需要调用者和请求接收者解耦,使得调用者和接收者不直接交互;
  • 系统需要在不同的时间指定请求、将请求排队和执行请求;
  • 系统需要支持undo撤销操作和redo重做操作;
  • 系统需要将一组命令组合在一起,即支持宏命令。
f.扩展
  • 宏命令:又称为组合命令,它是命令模式和组合模式联用的产物。宏命令也是一个具体的命令,它包含一系列其他命令对象的引用,在调用宏命令的execute方法的时候,将递归调用它所包含的每个成员命令对象的execute方法,一个宏命令的成员对象也可以是宏命令。
g.最佳实践
  • 多次undo/redo:Command+Chain of Responsibility
  • 宏命令:Command+Composite

20 原型模式 Prototype

a.定义

用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。它是一种创建型模式。

b.类图

在这里插入图片描述

c.特点
  • 将一个对象定义为原型,并为其提供复制自己的方法。

  • 实现原型模式需要实现表记性接口Cloneable。

  • 一般会重写Object.clone()方法,为了将protected修饰符改为public。

  • 需要区分浅克隆和深克隆。

d.优缺点

优点:

  • 当创建类的代价过大时,使用原型模式复制一个已有的实例可以提高创建实例的效率。
  • 可以动态保存当前对象的状态。
  • 可以在运行时创建新的对象而无须创建一系列的类和继承机构。
  • 可以动态地添加、删除原型的复制品。
e.适用场景
  • 程序需要从一个对象出发,得到若干个和该对象状态相同且可独立变化状态的对象。
  • 对象的创建需要独立于它的构造过程和表示。
f.扩展
  • 对象克隆的两种方式:
    • 实现Cloneable接口+重写Object.clone()方法。
    • 实现Serializable+对象流:先将要克隆对象写入ObjectOutputStream,再使用ObjectInputStream从ObjectOutputStream中读取对象。
      • 注意:使用对象流把一个对象写入文件时,不仅要保证该对象是序列化的,而且该对象的成员对象也必须是序列化的。
g.最佳实践
  • Object.clone()

21 备忘录模式 Memento

a.定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 需要保存状态的对象称为“原发者”,负责保存原发者状态的对象称为“备忘录”,负责管理备忘录的对象称为“负责人”。
  • 备忘录模式是关于怎样保存对象状态的成熟模式,其关键是提供一个备忘录对象,该备忘录负责存储一个对象的状态,程序可以在磁盘或者内存中保存这个备忘录。
d.优缺点

优点:

  • 把原发者的内部状态保存起来,使只有很“亲密的”对象才可以访问备忘录中的数据。
  • 强调了类设计的单一原则,将状态的刻画和保存分开。
e.适用场景
  • 必须保存一个对象在某一时刻的全部或部分状态,以便在需要的时候可以恢复该对象先前的状态。
  • 一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己的内部状态。
f.最佳实践
  • 游戏的关卡
  • 办公软件的撤销操作

22 模板方法模式 Template Method

a.定义

定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义改算法的某些特定步骤。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 一个抽象类中定义一个算法的骨架,即将若干个方法集成到一个方法中,则称该方法为一个模板方法,简称为模板。
  • 模板方法中可以包含抽象方法(原语操作),也可以包含非抽象方法。
  • 钩子方法:抽象模板中定义的具体方法,给出了空实现或默认实现,并允许子类重新这个具体方法。若不想子类重写,可将钩子方法定义为final修饰。
d.优缺点

优点:

  • 在抽象模板中可以定义模板方法(给出具体算法步骤),但又不限制步骤的细节,具体模板实现算法细节不会改变整个算法骨架。
  • 可通过钩子方法对某些具体步骤进行挂钩,具体模板可以通过钩子选择算法骨架中的某些步骤。
e.适用场景
  • 设计者需要给出一个算法的具体步骤,并将某些步骤的具体实现留给子类来实现。
  • 需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。
f.最佳实践
  • WindowListener: windowClosing()/windowXXX()(系统自动调用)
  • ASM: ClassVisitor

23 状态模式 State

a.定义

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 将对象的状态封装成独立的类,对象调用方法时可以委托当前对象所具有的状态调用相应的方法。
d.优缺点

优点:

  • 使用一个类封装对象的一种状态,很容易增加新的状态。
  • 在状态模式中,环境(Context)中不必出现大量的if语句,Context变得更清晰和容易理解。
  • 用户程序可以很方便地切换的Context的实例状态。
  • 当状态对象没有实例变量时,环境(Context)的各个实例可以共享一个状态对象。
e.适用场景
  • 一个对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为。
  • 需要开发大量条件分支语句来决定操作行为,且这些条件恰好表示对象的一种状态。
f.最佳实践
  • 线程状态变换

24 解释器模式 Interpreter

a.定义

给定一种语言,定义它语言的一种表示,再定义一个解释器,并用这个解释器使用该表示来解释语言中的句子。它是一种行为型模式。

b.类图

在这里插入图片描述

c.特点
  • 解释器模式是关于怎样实现一个简单语言的成熟模式,其关键是将每一个语法规则表示成一个类。
d.优缺点

优点:

  • 将每一个语法规则表示为一个类,便于实现简单的语言,且较容易扩展或改变语言的行为。
  • 通过在类结构中增加新的方法,可以在解释的同时增加新的行为。
e.适用场景
  • 当有一个简单的语言需要解释执行,并且可以将该语言的每一个规则表示成一个类时。
f.最佳实践
  • Python语言解释器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值