JAVA 设计模式篇

JAVA 设计模式篇

  • 1、UML类图
  • 2、设计原则
    • 2.1、开闭原则
    • 2.2、里氏代换原则
    • 2.3、依赖倒转原则
    • 2.4、接口隔离原则
    • 2.5、迪米特法则
    • 2.6、合成复用原则
  • 3、设计模式
    • 3.1、单例模式
      • 3.1.1、单例模式实现——饿汉式
        • 3.1.1.1、静态变量实现
        • 3.1.1.2、静态方法实现
        • 3.1.1.3、枚举方式
      • 3.1.2、单例模式实现——懒汉式
        • 3.1.2.1、线程不安全
        • 3.1.2.2、线程安全
        • 3.1.2.3、双重检查锁
        • 3.1.2.4、静态内部类方式
      • 3.1.3、破坏单例模式
        • 3.1.3.1、通过序列化破坏
        • 3.1.3.2、通过反射破坏
        • 3.1.3.3、防止序列化影响单例模式
        • 3.1.3.4、防止反射影响单例模式
      • 3.1.4、JDK源码解析——Runtime类
    • 3.2、工厂方法模式
      • 3.2.1、简单工厂模式
      • 3.2.2、工厂方法模式
    • 3.3、抽象工厂模式
      • 3.3.1、模式扩展
    • 3.4、原型模式
      • 3.4.1、简易实现原型模式
      • 3.4.2、使用原型模式进行克隆
      • 3.4.3、深克隆
    • 3.5、建造者模式
      • 3.5.1、建造者模式案例
      • 3.5.2、使用场景
      • 3.5.3、模式扩展
      • 3.5.4、创建者模式对比
    • 3.6、代理模式
      • 3.6.1、静态代理
      • 3.6.2、JDK动态代理
      • 3.6.3、CGLIB动态代理
      • 3.6.4、区别
    • 3.7、适配器模式
      • 3.7.1、类适配器模式
      • 3.7.2、对象适配器模式
    • 3.8、装饰者模式
      • 3.8.1、装饰者模式简单案例
      • 3.8.2、优点
    • 3.9、桥接模式
      • 3.9.1、案例
    • 3.10、外观模式
      • 3.10.1、案例
      • 3.10.2、优劣
    • 3.11、组合模式
      • 3.11.1、案例
      • 3.11.2、组合模式分类
      • 3.11.3、优点
    • 3.12、享元模式
      • 3.12.1、案例
      • 3.12.3、优劣
    • 3.13、模板方法模式
      • 3.13.1、案例
      • 3.13.2、优劣
      • 3.13.3、JDK源码分析
    • 3.14、策略模式
      • 3.14.1、案例
      • 3.14.2、优劣
      • 3.14.3、JDK源码分析
    • 3.15、命令模式
      • 3.15.1、案例
      • 3.15.2、优劣
    • 3.16、责任链模式
      • 3.16.1、案例
      • 3.16.2、优劣
    • 3.17、状态模式
      • 3.17.1、案例
      • 3.17.2、优劣:
    • 3.18、发布订阅模式(观察者模式)
      • 3.18.1、案例
      • 3.18.2、优劣
      • 3.18.3、源码分析
    • 3.19、中介者模式
      • 3.19.1、案例
      • 3.19.2、优劣
    • 3.20、迭代器模式
      • 3.20.1、案例
      • 3.20.2、优劣
    • 3.21、访问者模式
      • 3.21.1、案例
      • 3.21.2、优劣
      • 3.12.3、分派
        • 3.21.3.1、动态分派
        • 3.21.3.2、静态分派
        • 3.21.3.3、双分派
    • 3.22、备忘录模式
      • 3.22.1、结构
      • 3.22.2、案例
      • 3.22.3、白箱备忘录模式
      • 3.22.4、黑箱备忘录模式
      • 3.22.5、优劣
    • 3.23、解释器模式
      • 3.23.1、案例
      • 3.23.2、优劣

1、UML类图

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。

类图就不做多概述了,在大学编程基础都会对此有所了解,而绘制类图只是为了更加清晰的表示类与类直接的关系

2、设计原则

Gitee源码地址:https://gitee.com/modify_lzq/service-lzq-design.git

2.1、开闭原则

在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。想要达到这样的效果,我们需要使用接口和抽象类。
git代码见 service-lzq-design\src\main\java\com\lzq\principle\openandclose

2.2、里氏代换原则

任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
git代码见 service-lzq-design\src\main\java\com\lzq\principle\richter

2.3、依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
git代码见 service-lzq-design\src\main\java\com\lzq\principle\dependency

2.4、接口隔离原则

客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
git代码见 service-lzq-design\src\main\java\com\lzq\principle\isolation

2.5、迪米特法则

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
git代码见 service-lzq-design\src\main\java\com\lzq\principle\dimit

2.6、合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

3、设计模式

JAVA 设计模式篇_第1张图片

3.1、单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

3.1.1、单例模式实现——饿汉式

类加载就会导致该单实例对象被创建

3.1.1.1、静态变量实现

该方式在成员位置声明HungryStyleVar类型的静态变量,并创建HungryStyleVar类的对象hungryStyleVar。hungryStyleVar对象是随着类的加载而创建的。

public class HungryStyleVar {

    /**
     * 静态成员变量
     */
    private static HungryStyleVar hungryStyleVar = new HungryStyleVar();

    private HungryStyleVar() {
    }

    public static HungryStyleVar getInstance() {
        return hungryStyleVar;
    }
}

3.1.1.2、静态方法实现

该方式在成员位置声明HungryStyleFunc类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。

public class HungryStyleFunc {
    /**
     * 静态成员变量
     */
    private static HungryStyleFunc hungryStyleFunc;

    static {
        hungryStyleFunc = new HungryStyleFunc();
    }

    private HungryStyleFunc() {
    }

    public static HungryStyleFunc getInstance() {
        return hungryStyleFunc;
    }
}

3.1.1.3、枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次

public enum LazyEnum {
    INSTANCE
}

class LazyEnumTest {
    public static void main(String[] args) {
        LazyEnum instance = LazyEnum.INSTANCE;
        LazyEnum instance1 = LazyEnum.INSTANCE;
        System.out.println(instance == instance1);
    }
}

3.1.2、单例模式实现——懒汉式

3.1.2.1、线程不安全

从上面代码我们可以看出该方式在成员位置声明LazyStyleVar类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取LazyStyleVar类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

public class LazyStyleVar {
    private LazyStyleVar() {
    }

    private static LazyStyleVar lazyStyleVar;

    public static LazyStyleVar getInstance() {
        if (lazyStyleVar == null) {
            lazyStyleVar = new LazyStyleVar();
        }
        return lazyStyleVar;
    }
}

3.1.2.2、线程安全

看完上述线程不安全的代码,可以发现当多线程进行执行时,getInstance方法进来之后两个lazyStyleVar 同时实例化,这样这两个线程得到的LazyStyleVar对象就不是同一个对象了,解决的方法也很简单,我们可以直接利用JVM锁,直接给方法加上synchronized关键字,用来表示同步。

3.1.2.3、双重检查锁

对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

public class LazyStyleLock {
    private LazyStyleLock() {
    }

    private static volatile LazyStyleLock lazyStyleLock;

    public static synchronized LazyStyleLock getInstance() {
        if (lazyStyleLock == null) {
            synchronized (LazyStyleLock.class) {
                if (lazyStyleLock == null) {
                    lazyStyleLock = new LazyStyleLock();
                }
            }
        }
        return lazyStyleLock;
    }
}

3.1.2.4、静态内部类方式

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

public class LazyStyleClass {
    private LazyStyleClass() {

    }

    private static class LazyStyleClassWithin {
        private static final LazyStyleClass lazyStyleClass = new LazyStyleClass();
    }

    public static LazyStyleClass getInstance() {
        return LazyStyleClassWithin.lazyStyleClass;
    }
}

3.1.3、破坏单例模式

3.1.3.1、通过序列化破坏

public class Singleton implements Serializable {
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}

class Test {
    public static void main(String[] args) throws Exception {
        Test.readObjectFromFile();
        Test.readObjectFromFile();
    }

    private static Singleton readObjectFromFile() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\data\\a.txt"));
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        return instance;
    }


    public static void writeObject2File() throws Exception {
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\data\\a.txt"));
        oos.writeObject(instance);
    }
}

3.1.3.2、通过反射破坏

public class Singleton {
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}

class ReflexTest {
    public static void main(String[] args) throws Exception {
        Class<Singleton> singletonClass = Singleton.class;
        Constructor declaredConstructors = singletonClass.getDeclaredConstructor();
        declaredConstructors.setAccessible(true);
        Singleton instance = (Singleton) declaredConstructors.newInstance();
        Singleton instance1 = (Singleton) declaredConstructors.newInstance();
        System.out.println(instance == instance1);
    }
}

3.1.3.3、防止序列化影响单例模式

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

public class Singleton implements Serializable {
    private static Singleton singleton = new Singleton();

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

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

    public Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws Exception {
        Test.writeObject2File();
        Test.readObjectFromFile();
        Test.readObjectFromFile();
    }

    private static Singleton readObjectFromFile() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\data\\a.txt"));
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        return instance;
    }


    public static void writeObject2File() throws Exception {
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\data\\a.txt"));
        oos.writeObject(instance);
    }
}

源码分析:为什么加上了readResolve方法就能保证单例模式不会被破坏

// 在代码当中我们读取对象的时候是通过ObjectInputStream对象的readObject进行读取的,我们直接进去看一下这个方法是怎么实现的
Singleton instance = (Singleton) ois.readObject();
// 进入之后他里面调用了该方法进行返回了一个object,我们看一下这个readObject0方法
Object obj = readObject0(false);
// 进去之后会有一个switch case进行分支判断,这里我们直接找对应的Object即可,直接进去readOrdinaryObject方法
case TC_OBJECT:
	return checkResolve(readOrdinaryObject(unshared));
// 进入之后只需要看哪里对该readResolve方法进行判断即可。
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
// 而正是通过该代码进行赋值将obj整给了rep。调用表示的可序列化类的readResolve方法并返回结果。如果此类描述符未与类关联,或者该类不可序列化或未定义readResolve,则引发UnsupportedOperationException。
Object rep = desc.invokeReadResolve(obj);
// 里面方法的实现大概看一下就知道其含义了。如果表示的类是可序列化或可外部化的,并且定义了一致的readResolve方法,则返回true。否则,返回false。
boolean hasReadResolveMethod() {
	requireInitialized();
	return (readResolveMethod != null);
}
// 而readOrdinaryObject方法最后表明的含义是如果单例对象之前已经被实例化过,那么rep就会指向之前实例化的单例对象。如果我们之前没有实例化单例对象,则rep会指向null。
// rep与obj进行比较,由于obj是反射获取的对象,当然与rep不等,于是将rep的值instance赋值给obj,将obj返回,返回对象instance也就保证了单例。

3.1.3.4、防止反射影响单例模式

这个是直接在构造方法当中对是否创建了对象进行判断限制

public class Singleton {
    private Singleton() {
        if (!flag) {
            throw new RuntimeException("对象已创建");
        } else {
            flag = true;
        }
    }

    private static boolean flag = false;

    private static class SingletonWithin {
        private static final Singleton singleton = new Singleton();
    }
}

3.1.4、JDK源码解析——Runtime类

直接进入到这个RunTime类可以看到这就是前面所说的单例模式的-饿汉式实现的单例。private修饰的构造方法以及提供了一个获取对应实例的一个getRuntime方法

JAVA 设计模式篇_第2张图片

3.2、工厂方法模式

当我们使用一下代码来进行实现功能的时候,我们会发现一些问题。

在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。

JAVA 设计模式篇_第3张图片

3.2.1、简单工厂模式

简单工厂模式就是将创建咖啡的功能给单独抽离出来,用一个工厂进行管理,这样做的优点在于封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。但是在后续增加新的类型还是要修改工厂类,还是违背了“开闭原则”

当然我们可以直接给createCoffee方法加上static静态,就可以直接通过类调用方法,不需要创建工厂类了

JAVA 设计模式篇_第4张图片

3.2.2、工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

这样做的好处在于我们只需要知道具体工厂的名称就可得到所要的产品,并不需要关注其是如何创建的,而且在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

但是每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

JAVA 设计模式篇_第5张图片

3.3、抽象工厂模式

抽象工厂是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

JAVA 设计模式篇_第6张图片

3.3.1、模式扩展

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

这里我们定义一个配置文件,将对应的全类名用一个变量进行接收 【bean.properties】

usa=com.lzq.model.factory.abs.UsaCoffee
latte=com.lzq.model.factory.abs.LatteCoffee

静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

public class CoffeeStaticFactory {
    private static Map<String, Coffee> map = new HashMap();

    static {
        Properties properties = new Properties();
        InputStream resourceAsStream = CoffeeStaticFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(resourceAsStream);
            Set<Object> keys = properties.keySet();
            for (Object key : keys) {
                String property = properties.getProperty((String) key);
                // 通过反射创建对象
                Class clazz = Class.forName(property);
                Coffee coffee = (Coffee) clazz.newInstance();
                map.put((String) key, coffee);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Coffee createCoffee(String name) {
        return map.get(name);
    }
}

class TestProperties {
    public static void main(String[] args) {
        Coffee usa = CoffeeStaticFactory.createCoffee("usa");
        System.out.println(usa.getName());
    }
}

3.4、原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式的克隆分为浅克隆和深克隆。

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

3.4.1、简易实现原型模式

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类

public class RealizeType implements Cloneable {
    public RealizeType() {
        System.out.println("new RealizeType");
    }

    @Override
    public RealizeType clone() {
        try {
            System.out.println("clone success");
            return (RealizeType) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

class RealizeTypeTest {
    public static void main(String[] args) {
        RealizeType r1 = new RealizeType();
        RealizeType r2 = r1.clone();
        System.out.println(r1 == r2);
    }
}

3.4.2、使用原型模式进行克隆

这是直接克隆原始类型,原始类型是存在栈当中,不会有任何影响

public class Citation implements Cloneable {
    private String name;

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

    public String getName() {
        return name;
    }

    public void show() {
        System.out.println("now name = " + name);
    }

    @Override
    public Citation clone() {
        try {
            return (Citation) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

class CitationTest {
    public static void main(String[] args) {
        Citation citation = new Citation();
        Citation clone = citation.clone();
        citation.setName("张三");
        clone.setName("李四");

        citation.show();
        clone.show();
    }
}

但是当我们进行克隆对象的时候,直接修改值只是做一个浅克隆,只改变了栈当中指向堆的指针,并不会新开一个对象

public class StudentPrototype implements Cloneable {
    private Student stu;

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    public void show() {
        System.out.println(stu.getName() + " <<<< ===== >>>> " + stu.getAddress());
    }

    @Override
    public StudentPrototype clone() {
        try {
            StudentPrototype clone = (StudentPrototype) super.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

class StudentPrototypeTest {
    public static void main(String[] args) {
        StudentPrototype studentPrototype = new StudentPrototype();
        Student student = new Student("张三", "长沙");
        studentPrototype.setStu(student);

        StudentPrototype clone = studentPrototype.clone();
        clone.getStu().setName("李四");
        
        studentPrototype.show();
        clone.show();
    }
}

3.4.3、深克隆

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

        StudentPrototype studentPrototype = new StudentPrototype();
        Student student = new Student("张三", "长沙");
        studentPrototype.setStu(student);
        //创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\data\\b.txt"));
        //将c1对象写出到文件中
        oos.writeObject(studentPrototype);
        oos.close();

        //创建对象出入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\data\\b.txt"));

        StudentPrototype clone = (StudentPrototype) ois.readObject();
        clone.getStu().setName("李四");

        studentPrototype.show();
        clone.show();

3.5、建造者模式

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

3.5.1、建造者模式案例

public abstract class BikeBuilder {
    protected Bike bike = new Bike();

    public abstract void buildFrame();

    public abstract void buildSeat();

    public abstract Bike createBike();
}

class MoBileBike extends BikeBuilder {

    @Override
    public void buildFrame() {
        bike.setFrame("mobile frame");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("mobile seat");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

class OfoBike extends BikeBuilder {

    @Override
    public void buildFrame() {
        bike.setFrame("ofo frame");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("ofo seat");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

class Director {
    private BikeBuilder builder;

    public Director(BikeBuilder builder) {
        this.builder = builder;
    }

    public Bike construct() {
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

class Test {
    public static void main(String[] args) {
        Director director = new Director(new MoBileBike());
        Bike bike = director.construct();
        System.out.println(bike.getFrame() + " <<<====>>> " + bike.getSeat());
    }
}

可以发现Director类使用来统一管理创建对象的,当然我们可以对该代码进行优化,将该方法抽离出来放到builder当中,

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

3.5.2、使用场景

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

3.5.3、模式扩展

在这里我们将构建类定义到对应实体内部,并且其内部进行构建对象,每次设置属性的时候都返回this,这种方式就可以使用链式编程,而实体的构造方法通过内部类进行构建,

public class Phone {
    private String cpu;
    private String screen;
    private String memory;

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                '}';
    }

    private Phone(PhoneBuilder phoneBuilder) {
        this.cpu = phoneBuilder.cpu;
        this.screen = phoneBuilder.screen;
        this.memory = phoneBuilder.memory;
    }

    public static final class PhoneBuilder {
        private String cpu;
        private String screen;
        private String memory;

        public PhoneBuilder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public PhoneBuilder screen(String screen) {
            this.screen = screen;
            return this;
        }

        public PhoneBuilder memory(String memory) {
            this.memory = memory;
            return this;
        }

        public Phone builder() {
            return new Phone(this);
        }
    }
}

class PhoneTest {
    public static void main(String[] args) {
        Phone phone = new Phone.PhoneBuilder().cpu("天玑9100").memory("16G").screen("LCD").builder();
        System.out.println(phone.toString());
    }
}

3.5.4、创建者模式对比

工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

3.6、代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

3.6.1、静态代理

通过一个简单的案例来进行说明,当我们进行买票的时候是需要到车站进行购买,但是我们还可以通过代售点进行购买,这个时候这个代售点就相当于一个车站的代理。这个例子其实就是典型的代理模式。

public interface SellInterface {
    /**
     * 售卖
     */
    void sell();
}

public class Station implements SellInterface {

    @Override
    public void sell() {
        System.out.println("station sell");
    }
}

public class StationProxy implements SellInterface {

    private Station station = new Station();

    @Override
    public void sell() {
        System.out.println("StationProxy sell");
        station.sell();
    }
}

class SellTest {
    public static void main(String[] args) {
        StationProxy proxy = new StationProxy();
        proxy.sell();
    }
}

测试类直接访问的是StationProxy类对象,也就是说StationProxy作为访问对象和目标对象的中介。同时也对sell方法进行了增强

3.6.2、JDK动态代理

Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

public class ProxyFactory {
    private Station station = new Station();

    public SellInterface getSellInterface() {
        SellInterface sellProxy = (SellInterface) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("接口方法名称:" + method.getName());
                        return method.invoke(station, args);
                    }
                }
        );
        return sellProxy;
    }
}

class Test {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellInterface sellInterface = proxyFactory.getSellInterface();
        sellInterface.sell();

        System.out.println(sellInterface.getClass());
        while (true) {
            
        }
    }
}

ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。我们通过Arthas查看代理类的结构:

JAVA 设计模式篇_第7张图片

我们将代理类代码摘出来,并且去掉一下不必要的代码:

public final class $Proxy0
        extends Proxy
        implements SellInterface {

    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        m3 = Class.forName("com.lzq.model.proxy.jdkproxy.SellInterface").getMethod("sell", new Class[0]);
    }

    public final void sell() {
        this.h.invoke(this, m3, null);
    }
}
  • 在测试类中通过代理对象调用sell()方法
  • 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
  • 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  • invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

3.6.3、CGLIB动态代理

首先引入对应依赖

<dependency>
    <groupId>cglibgroupId>
    <artifactId>cglibartifactId>
    <version>2.2.2version>
dependency>

CGLIB代理通过Enhancer对象设置其父类的字节码对象,与jdk代理相似,通过invokeSuper进行实现对应的代理增强

public class ProxyFactory implements MethodInterceptor {

    private Station station = new Station();

    public Station getProxyObject() {
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(station.getClass());
        enhancer.setCallback(this);
        Station obj = (Station) enhancer.create();
        return obj;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object invoke = methodProxy.invokeSuper(o, objects);
        return invoke;
    }
}

class Test {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.getProxyObject().sell();
        System.out.println(proxyFactory.getProxyObject().getClass());
        while (true) {

        }
    }
}

3.6.4、区别

jdk代理和CGLIB代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

动态代理和静态代理

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

3.7、适配器模式

在现在的生活当中,比方说现在的笔记本电脑,基本上不会搭载网线接口,而只是提供type-c接口,这个时候我们就需要一个拓展坞进行接口拓展,这个时候这个扩展坞就是适配器,这也就是我们所说的适配器模式

3.7.1、类适配器模式

类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。

public class SdAdapterTf extends TfCardImp implements SdCard {
    @Override
    public String readSD() {
        return readTF();
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("write tf");
        writeTF(msg);
    }
}

class Computer {
    public String readSD(SdCard sdCard) {
        if (sdCard == null) {
            throw new NullPointerException("sd card null");
        }
        return sdCard.readSD();
    }
}

class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        String msg = computer.readSD(new SdCardImp());
        System.out.println(msg);
        System.out.println("<<<<<<=========>>>>>");
        String msg1 = computer.readSD(new SdAdapterTf());
        System.out.println(msg1);
    }
}

3.7.2、对象适配器模式

这里我们定义的适配器只实现对应接口,在类当中使用使用类变量进行管理。还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可

class SdAdapterTfObject implements SdCard {
    private TfCard tfCard;

    public SdAdapterTfObject(TfCard tfCard) {
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
        return tfCard.readTF();
    }

    @Override
    public void writeSD(String msg) {
        tfCard.writeTF(msg);
    }
}

class ObjectTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        SdCard sdCard = new SdCardImp();
        System.out.println(computer.readSD(sdCard));
        System.out.println("<<<<<<======>>>>>");
        SdAdapterTfObject sdAdapterTfObject = new SdAdapterTfObject(new TfCardImp());
        String msg = sdAdapterTfObject.readSD();
        System.out.println(msg);
    }
}

3.8、装饰者模式

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦

3.8.1、装饰者模式简单案例

public abstract class FastFood {
    private float price;
    private String desc;

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

    public float getPrice() {
        return price;
    }

    public String getDesc() {
        return desc;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    /**
     * 计算总价
     *
     * @return float
     */
    public abstract float count();
}

class FriedRice extends FastFood {

    public FriedRice() {
        super(10, "炒饭");
    }

    @Override
    public float count() {
        return getPrice();
    }
}

class FriedNoodles extends FastFood {

    public FriedNoodles() {
        super(12, "炒面");
    }

    @Override
    public float count() {
        return getPrice();
    }
}

abstract class Garnish extends FastFood {
    private FastFood fastFood;

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }

    public Garnish(float price, String desc, FastFood fastFood) {
        super(price, desc);
        this.fastFood = fastFood;
    }
}

class Egg extends Garnish {

    public Egg(FastFood fastFood) {
        super(1, "鸡蛋", fastFood);
    }

    public float count() {
        return super.getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

class Bacon extends Garnish {
    public Bacon(FastFood fastFood) {
        super(3, "培根", fastFood);
    }

    public float count() {
        return super.getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

class Test {
    public static void main(String[] args) {
        FastFood fastFood = new FriedRice();
        System.out.println(fastFood.getDesc() + "===>>>>" + fastFood.count());

        fastFood = new Egg(fastFood);
        System.out.println(fastFood.getDesc() + "===>>>>" + fastFood.count());
    }
}

3.8.2、优点

装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

3.9、桥接模式

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

3.9.1、案例

开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等

public interface VideoFile {
    /**
     * 解码
     *
     * @param fileName
     */
    void decode(String fileName);
}

class AviFile implements VideoFile {

    @Override
    public void decode(String fileName) {
        System.out.println("avi 视频格式 " + fileName);
    }
}

class RmvbFile implements VideoFile {

    @Override
    public void decode(String fileName) {
        System.out.println("rmvb 视频格式 " + fileName);
    }
}

abstract class OperatingSystemVersion {
    protected VideoFile videoFile;

    public OperatingSystemVersion(VideoFile videoFile) {
        this.videoFile = videoFile;
    }

    /**
     * 播放视频
     *
     * @param fileName
     */
    public abstract void play(String fileName);
}

class Windows extends OperatingSystemVersion {

    public Windows(VideoFile videoFile) {
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

class Mac extends OperatingSystemVersion {

    public Mac(VideoFile videoFile) {
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

class Test {
    public static void main(String[] args) {
        OperatingSystemVersion sys = new Mac(new AviFile());
        sys.play("肖申克的救赎");
    }
}

而当后续我们需要新增一个视频格式只需要定义对应实体实现VideoFile接口。而对应的操作系统只需要继承OperatingSystemVersion类即可

3.10、外观模式

是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

3.10.1、案例

小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭

public class FacadeTest {
    public static void main(String[] args) {
        SmartAppliancesFacade smartAppliancesFacade = new SmartAppliancesFacade(new Light(), new Tv(), new AirCondition());
        smartAppliancesFacade.say("打开");
        smartAppliancesFacade.say("关闭");
    }
}

class Light {
    public void on() {
        System.out.println("open light");
    }

    public void off() {
        System.out.println("off light");
    }
}

class Tv {
    public void on() {
        System.out.println("open tv");
    }

    public void off() {
        System.out.println("off tv");
    }
}

class AirCondition {
    public void on() {
        System.out.println("open AirCondition");
    }

    public void off() {
        System.out.println("off AirCondition");
    }
}

class SmartAppliancesFacade {
    private Light light;
    private Tv tv;
    private AirCondition airCondition;
    private final static String OPEN = "打开";
    private final static String CLOSE = "关闭";

    public SmartAppliancesFacade(Light light, Tv tv, AirCondition airCondition) {
        this.light = light;
        this.tv = tv;
        this.airCondition = airCondition;
    }

    public void say(String msg) {
        if (OPEN.equals(msg)) {
            on();
        } else if (CLOSE.equals(msg)) {
            off();
        } else {
            System.out.println("说点人话");
        }
    }

    private void on() {
        light.on();
        tv.on();
        airCondition.on();
    }

    private void off() {
        light.off();
        tv.off();
        airCondition.off();
    }
}

3.10.2、优劣

优点:

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

缺点:

  • 不符合开闭原则,修改很麻烦

3.11、组合模式

是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

3.11.1、案例

一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当

public abstract class MenuComponent {
    protected String name;
    protected int level;

    public String getName() {
        return name;
    }

    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }

    public void print() {
        throw new UnsupportedOperationException();
    }
}

class Menu extends MenuComponent {
    private List<MenuComponent> list = new ArrayList<>();

    public Menu(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        list.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        list.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int i) {
        return list.get(i);
    }

    @Override
    public void print() {
        System.out.println("<<--------------------------------------------------------------------->>");
        System.out.println(name);
        System.out.println("<<<<< ========= >>>>>");
        for (MenuComponent item : list) {
            item.print();
        }
    }
}

class MenuItem extends MenuComponent {
    public MenuItem(String name, int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
        System.out.println(name);
    }
}

class Test {
    public static void main(String[] args) {
        MenuComponent menu = new Menu("菜单1", 2);
        menu.add(new MenuItem("节点1", 3));
        menu.add(new MenuItem("节点3", 3));
        menu.add(new MenuItem("节点2", 3));

        MenuComponent menu1 = new Menu("菜单2", 2);
        menu1.add(new MenuItem("节点4", 3));
        menu1.add(new MenuItem("节点5", 3));
        menu1.add(new MenuItem("节点6", 3));

        MenuComponent menu2 = new Menu("菜单3", 2);
        menu2.add(new MenuItem("节点7", 3));
        menu2.add(new MenuItem("节点8", 3));
        menu2.add(new MenuItem("节点9", 3));

        MenuComponent menuComponent = new Menu("菜单树", 1);
        menuComponent.add(menu);
        menuComponent.add(menu1);
        menuComponent.add(menu2);
        menuComponent.print();
    }
}

3.11.2、组合模式分类

透明组合模式

透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 add、remove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

安全组合模式

在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

3.11.3、优点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

3.12、享元模式

运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

享元(Flyweight )模式中存在以下两种状态:

  • 内部状态,即不会随着环境的改变而改变的可共享部分。
  • 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

3.12.1、案例

public abstract class AbstractBox {
    /**
     * 获取形状
     *
     * @return String
     */
    protected abstract String getShape();

    public void display(String color) {
        System.out.println("形状 :" + getShape() + " , 颜色:" + color);
    }
}

class IBox extends AbstractBox {

    @Override
    public String getShape() {
        return "I";
    }
}

class LBox extends AbstractBox {

    @Override
    public String getShape() {
        return "L";
    }
}

class OBox extends AbstractBox {

    @Override
    public String getShape() {
        return "O";
    }
}

class BoxFactory {
    private static HashMap<String, AbstractBox> map;

    private BoxFactory() {
        map = new HashMap<>();
        map.put("i", new IBox());
        map.put("o", new OBox());
        map.put("l", new LBox());
    }

    public static final BoxFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory();
    }

    public AbstractBox getBox(String key) {
        return map.get(key);
    }
}

class Test {
    public static void main(String[] args) {
        AbstractBox ibox = BoxFactory.getInstance().getBox("i");
        ibox.display("blue");
        AbstractBox lbox = BoxFactory.getInstance().getBox("l");
        lbox.display("red");
    }
}

3.12.3、优劣

优点
极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态

缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

3.13、模板方法模式

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

3.13.1、案例

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。并且为防止恶意操作,一般模板方法都加上 final 关键词。

public abstract class AbstractClass {
    public final void cookProcess() {
        this.pourOil();
        this.heatOil();
        this.pourVegetable();
        this.pourSauce();
        this.fry();
    }

    public void pourOil() {
        System.out.println("倒油");
    }

    public void heatOil() {
        System.out.println("热油");
    }

    /**
     * 加菜
     */
    public abstract void pourVegetable();

    /**
     * 加配料
     */
    public abstract void pourSauce();

    public void fry() {
        System.out.println("炒啊炒啊炒到熟啊");
    }
}

class ConcreteClassBaoCai extends AbstractClass {

    @Override
    public void pourVegetable() {
        System.out.println("下包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("加辣椒");
    }
}

class ConcreteClassCaiXin extends AbstractClass {

    @Override
    public void pourVegetable() {
        System.out.println("下菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("加蒜蓉");
    }
}

class Test {
    public static void main(String[] args) {
        ConcreteClassBaoCai baoCai = new ConcreteClassBaoCai();
        baoCai.cookProcess();
    }
}

3.13.2、优劣

优点:

  • 提高代码复用性
  • 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

3.13.3、JDK源码分析

无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,但是在三个参数的read方法当中调用了无参的抽象的 read() 方法
JAVA 设计模式篇_第8张图片

3.14、策略模式

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

3.14.1、案例

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。

public interface StrategyInterface {
    /**
     * 活动内容
     */
    void show();
}

class StrategyA implements StrategyInterface {

    @Override
    public void show() {
        System.out.println("买一送一");
    }
}

class StrategyB implements StrategyInterface {

    @Override
    public void show() {
        System.out.println("满200元减50元");
    }
}

class StrategyC implements StrategyInterface {

    @Override
    public void show() {
        System.out.println("满1000元加一元换购任意200元以下商品");
    }
}

class SalesMan {
    private StrategyInterface strategyInterface;

    public SalesMan(StrategyInterface strategyInterface) {
        this.strategyInterface = strategyInterface;
    }

    public void salesManShow() {
        strategyInterface.show();
    }
}

class Test {
    public static void main(String[] args) {
        SalesMan salesMan = new SalesMan(new StrategyB());
        salesMan.salesManShow();
    }
}

3.14.2、优劣

优点:

  • 策略类之间可以自由切换:由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展:增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

3.14.3、JDK源码分析

在JDK源码当中Arrays.sort使用的就是策略模式,为什么怎么说呢,接着往下看

    public void arrays() {
        Integer[] a = {3, 5, 6, 7, 96, 43};
        Arrays.sort(a, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
    }

sort方法实现如下,我们只需要关注传递进来的Comparator,也就是前面的都可以跳过,看最后一行TimSort.sort

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

进去之后我们同样只关注Comparator,

int initRunLen = countRunAndMakeAscending(a, lo, hi, c);

最后进入到countRunAndMakeAscending,可以看到不管是if还是else条件都执行了其compare方法,而这个方法是我们自己编写的,我们将排序算法抽离出来进行传递,这也就是策略模式。

        if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

3.15、命令模式

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。

3.15.1、案例

服务员: 就是调用者角色,由她来发起命令。
大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。

public class Order {
    private int no;
    private Map<String, Integer> foodDic = new HashMap<String, Integer>();

    public int getNo() {
        return no;
    }

    public Map<String, Integer> getFoodDic() {
        return foodDic;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public void setFoodDic(String name, Integer num) {
        this.foodDic.put(name, num);
    }
}

class SeniorChef {
    public void makeFood(int num, String foodName) {
        System.out.println(num + "份" + foodName);
    }
}

interface Command {
    /**
     * 的执行方法
     */
    void execute();
}

class OrderCommand implements Command {
    private SeniorChef receiver;
    private Order order;

    public OrderCommand(SeniorChef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }

    @Override
    public void execute() {
        System.out.println(order.getNo() + " 订单开始");
        Set<String> keys = order.getFoodDic().keySet();
        for (String key : keys) {
            receiver.makeFood(order.getFoodDic().get(key), key);
        }
        System.out.println(order.getNo() + " 订单结束");
    }
}

class Waitor {
    private ArrayList<Command> commands;

    public Waitor() {
        this.commands = new ArrayList<>();
    }

    public void setCommand(Command cmd) {
        commands.add(cmd);
    }

    public void orderUp() {
        System.out.println("新订单来了.......");
        for (int i = 0; i < commands.size(); i++) {
            Command cmd = commands.get(i);
            if (cmd != null) {
                cmd.execute();
            }
        }
    }
}

class Test {
    public static void main(String[] args) {
        Order order = new Order();
        order.setNo(1);
        order.setFoodDic("辣椒炒肉", 1);
        order.setFoodDic("紫菜蛋花汤", 1);

        Order order2 = new Order();
        order2.setNo(2);
        order2.setFoodDic("烤鱿鱼", 3);
        order2.setFoodDic("可乐", 1);


        SeniorChef seniorChef = new SeniorChef();
        OrderCommand orderCommand = new OrderCommand(seniorChef, order);
        OrderCommand orderCommand2 = new OrderCommand(seniorChef, order2);

        Waitor waitor = new Waitor();
        waitor.setCommand(orderCommand);
        waitor.setCommand(orderCommand2);

        waitor.orderUp();
    }
}

3.15.2、优劣

优点:
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。
系统结构更加复杂。

3.16、责任链模式

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

3.16.1、案例

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

public class LeaveRequest {
    private String name;
    private int num;
    private String content;
	
	// 构造方法、get方法省略
}

/**
 * 抽象处理者类
 */
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.handleLeave(leave);
            //如果还有上级 并且请假天数超过了当前领导的处理范围
            if (null != this.nextHandler && leave.getNum() > this.numEnd) {
                this.nextHandler.submit(leave);
            } else {
                System.out.println("流程结束");
            }
        }
    }

    protected abstract void handleLeave(LeaveRequest leave);
}

class GroupLeader extends Handler {
    public GroupLeader() {
        super(NUM_ONE, NUM_THREE);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println("GroupLeader ===>>>> name : " + leave.getName() + "  ,num : " + leave.getNum() + " , content: " + leave.getContent());
    }
}

class Manager extends Handler {

    public Manager() {
        super(NUM_THREE, NUM_SEVEN);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println("Manager ===>>>> name : " + leave.getName() + "  ,num : " + leave.getNum() + " , content: " + leave.getContent());
    }
}

class GeneralManager extends Handler {

    public GeneralManager() {
        super(NUM_SEVEN);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println("GeneralManager  ===>>>> name : " + leave.getName() + "  ,num : " + leave.getNum() + " , content: " + leave.getContent());
    }
}

class Test {
    public static void main(String[] args) {
        LeaveRequest leaveRequest = new LeaveRequest("月月鸟", 1, "不想上班");
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);

        groupLeader.submit(leaveRequest);
    }
}

3.16.2、优劣

优点:

  • 降低了对象之间的耦合度:该模式降低了请求发送者和接收者的耦合度。
  • 增强了系统的可扩展性:可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性:当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接:一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担:每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

3.17、状态模式

当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时

3.17.1、案例

见git代码块

3.17.2、优劣:

优点:

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

缺点:

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好。

3.18、发布订阅模式(观察者模式)

它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

并且这里还可以了解一下js的发布订阅:js设计模式-观察者模式与发布订阅模式

3.18.1、案例

public interface Observer {
    /**
     * 更新内容
     */
    void update(String message);
}

interface Subject {
    /**
     * 增加订阅者
     */
    public void attach(Observer observer);

    /**
     * 删除订阅者
     */
    public void detach(Observer observer);

    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}

class SubscriptionSubject implements Subject {

    private List<Observer> observerList = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void notify(String message) {
        for (Observer observer : observerList) {
            observer.update(message);
        }
    }
}

class User implements Observer {
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " | " + message);
    }
}

class Test {
    public static void main(String[] args) {
        SubscriptionSubject subscriptionSubject = new SubscriptionSubject();
        User user1 = new User("黄月");
        User user2 = new User("黄月月");
        User user3 = new User("黄月月鸟");

        subscriptionSubject.attach(user1);
        subscriptionSubject.attach(user2);
        subscriptionSubject.attach(user3);

        subscriptionSubject.notify("send one message");
    }
}

3.18.2、优劣

优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

缺点:

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

3.18.3、源码分析

在JDK当中实现了观察者模式,我们查看java.util.Observable这个类的源码:

	// 里面存了两个全局变量,分别就是用来表示是否改变、以及被观察者集合
    private boolean changed = false;
    private Vector<Observer> obs;
    // 构造方法就是实例化一个obs
    // 关键方法1 将obs加入订阅者
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    // 删除订阅者
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    // 调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

3.19、中介者模式

又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

3.19.1、案例

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

public abstract class BaseMediator {
    /**
     * 交流方法
     */
    public abstract void constact(String message, BasePerson person);
}

abstract class BasePerson {
    protected String name;
    protected BaseMediator mediator;

    public BasePerson(String name, BaseMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
}

/**
 * 租户类
 */
class Tenant extends BasePerson {

    public Tenant(String name, BaseMediator mediator) {
        super(name, mediator);
    }

    /**
     * 与中介联系
     */
    public void constact(String message) {
        mediator.constact(message, this);
    }

    /**
     * 获取信息
     */
    public void getMessage(String message) {
        System.out.println("租房者" + name + "获取到的信息:" + message);
    }
}

class HouseOwner extends BasePerson {

    public HouseOwner(String name, BaseMediator mediator) {
        super(name, mediator);
    }

    /**
     * 与中介联系
     */
    public void constact(String message) {
        mediator.constact(message, this);
    }

    /**
     * 获取信息
     */
    public void getMessage(String message) {
        System.out.println("房主" + name + "获取到的信息:" + message);
    }
}

class MediatorStructure extends BaseMediator {
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    public Tenant getTenant() {
        return tenant;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    @Override
    public void constact(String message, BasePerson person) {
        if (person == houseOwner) {
            tenant.getMessage(message);
        } else {
            houseOwner.getMessage(message);
        }
    }
}

class Test {
    public static void main(String[] args) {
        MediatorStructure mediator = new MediatorStructure();
        Tenant tenant = new Tenant("黄月月", mediator);
        HouseOwner houseOwner = new HouseOwner("黄月", mediator);
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);
        tenant.constact("需要租三室的房子");
        houseOwner.constact("我这有三室的房子,你需要租吗?");
    }
}

3.19.2、优劣

优点:

  • 松散耦合:中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。
  • 集中控制交互:多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
  • 一对多关联转变为一对一的关联:没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

缺点:

  • 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

3.20、迭代器模式

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

3.20.1、案例

定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现

public class StudentIteratorImpl implements StudentIterator {
    private List<Student> list;
    private int position = 0;

    public StudentIteratorImpl(List<Student> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return position < list.size();
    }

    @Override
    public Student next() {
        Student currentStudent = list.get(position);
        position++;
        return currentStudent;
    }
}

class Student {
    private String no;
    private String name;
	// get set 方法 构造方法 tostring方法都省略了,直接用开发工具生成
}

interface StudentIterator {
    /**
     * 是否有下一个节点
     */
    boolean hasNext();

    /**
     * 得到下一个节点
     */
    Student next();
}

interface StudentAggregate {
    /**
     * 添加
     */
    void addStudent(Student student);

    /**
     * 删除
     */
    void removeStudent(Student student);

    /**
     * 获取迭代器
     */
    StudentIterator getStudentIterator();
}

class StudentAggregateImpl implements StudentAggregate {
    private List<Student> list = new ArrayList<Student>();

    @Override
    public void addStudent(Student student) {
        list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
        list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}

class Test {
    public static void main(String[] args) {
        StudentAggregateImpl studentAggregate = new StudentAggregateImpl();
        studentAggregate.addStudent(new Student("1", "月月"));
        studentAggregate.addStudent(new Student("2", "黄月月"));
        studentAggregate.addStudent(new Student("3", "黄月"));
        studentAggregate.addStudent(new Student("4", "黄月月鸟"));
        StudentIterator iterator = studentAggregate.getStudentIterator();
        while (iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println(student.toString());
        }
    }
}

3.20.2、优劣

优点:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

缺点:

  • 增加了类的个数,这在一定程度上增加了系统的复杂性。

3.21、访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

3.21.1、案例

宠物分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

public class Test {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Cat());
        home.add(new Dog());
        home.action(new Owner());
    }
}

interface Person {
    /**
     * 喂猫
     */
    void feed(Cat cat);

    /**
     * 喂狗
     */
    void feed(Dog dog);
}

class Owner implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("主人 喂猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人 喂狗");
    }
}

class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("其他人 喂猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("其他人 喂狗");
    }
}

interface Animal {
    /**
     * 实现方法
     */
    void accept(Person person);
}

class Cat implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("喵喵喵.....");
    }
}

class Dog implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("汪汪汪.....");
    }
}

class Home {
    private List<Animal> nodeList = new ArrayList<Animal>();

    public void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    /**
     * 添加操作
     */
    public void add(Animal animal) {
        nodeList.add(animal);
    }
}

3.21.2、优劣

优点:

  • 扩展性好:在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  • 分离无关行为:通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

缺点:

  • 对象结构变化很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

3.12.3、分派

变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

3.21.3.1、动态分派

通过方法的重写支持动态分派。这就是多态,运行执行的是子类中的方法。Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

public class Dynamic {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();

        Animal a1 = new Cat();
        a1.execute();
    }
}

class Animal {
    public void execute() {
        System.out.println("Animal");
    }
}

class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("dog");
    }
}

class Cat extends Animal {
    @Override
    public void execute() {
        System.out.println("cat");
    }
}

3.21.3.2、静态分派

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。所以这里打印的都是Animal

public class Static {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();

        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
    }
}

class Animal {
}

class Dog extends Animal {
}

class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }

    public void execute(Dog d) {
        System.out.println("dog");
    }
}

3.21.3.3、双分派

客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。

public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();

        Execute exe = new Execute();
        a.accept(exe);
        d.accept(exe);
        c.accept(exe);
    }
}

class Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

class Dog extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

class Cat extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

class Execute {
    public void execute(Animal a) {
        System.out.println("animal");
    }

    public void execute(Dog d) {
        System.out.println("dog");
    }

    public void execute(Cat c) {
        System.out.println("cat");
    }
}

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

3.22、备忘录模式

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

3.22.1、结构

备忘录模式的主要角色如下:

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
    备忘录有两个等效的接口:

窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

3.22.2、案例

游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

3.22.3、白箱备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。

public class Test {
    public static void main(String[] args) {
        System.out.println("<<<<<-----------start--------->>>>>");
        GameRole gameRole = new GameRole();
        gameRole.stateDisplay();

        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

        System.out.println("<<<<<-----------fight--------->>>>>");
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("<<<<<-----------replay--------->>>>>");
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

class GameRole {
    private int vit;
    private int atk;
    private int def;

    public GameRole() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    public RoleStateMemento saveState() {
        return new RoleStateMemento(vit, atk, def);
    }

    public void recoverState(RoleStateMemento roleStateMemento) {
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }

    public void stateDisplay() {
        System.out.println("角色生命力:" + vit);
        System.out.println("角色攻击力:" + atk);
        System.out.println("角色防御力:" + def);
    }
    // get set方法省略
}

class RoleStateMemento {
    private int vit;
    private int atk;
    private int def;
	// get set方法、 构造方法 省略
}

class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;
	// get set方法省略
}

3.22.4、黑箱备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。

将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在 GameRole 里面;在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。

public class Test {
    public static void main(String[] args) {
        System.out.println("<<<<<-----------start--------->>>>>");
        GameRole gameRole = new GameRole();
        gameRole.stateDisplay();

        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setMemento(gameRole.saveState());

        System.out.println("<<<<<-----------fight--------->>>>>");
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("<<<<<-----------replay--------->>>>>");
        gameRole.recoverState(roleStateCaretaker.getMemento());
        gameRole.stateDisplay();
    }
}

interface Memento {
}

class GameRole {
    private int vit;
    private int atk;
    private int def;
	// get set方法省略
    public GameRole() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    public RoleStateMemento saveState() {
        return new RoleStateMemento(vit, atk, def);
    }

    public void recoverState(Memento memento) {
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }

    public void stateDisplay() {
        System.out.println("角色生命力:" + vit);
        System.out.println("角色攻击力:" + atk);
        System.out.println("角色防御力:" + def);
    }

    private class RoleStateMemento implements Memento {
        private int vit;
        private int atk;
        private int def;
        // get set方法、 构造方法 省略
    }
}

class RoleStateCaretaker {
    private Memento memento;
	// get set方法省略
}

3.22.5、优劣

优点:

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点:

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

3.23、解释器模式

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

3.23.1、案例

这里通过一个加减法的功能进行实现说明

public abstract class AbstractExpression {
    /**
     * 计算
     */
    public abstract int interpret(Context context);
}

class Variable extends AbstractExpression {
    private String name;

    public Variable(String name) {
        this.name = name;
    }

    @Override
    public int interpret(Context context) {
        return context.getValue(this);
    }

    @Override
    public String toString() {
        return name;
    }
}

/**
 * 加法
 */
class Plus extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " + " + right.toString() + ")";
    }
}

/**
 * 减法
 */
class Minus extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        return left.interpret(context) - right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " - " + right.toString() + ")";
    }
}

class Context {
    private Map<Variable, Integer> map = new HashMap<Variable, Integer>();

    public void assign(Variable var, Integer value) {
        map.put(var, value);
    }

    public Integer getValue(Variable var) {
        return map.get(var);
    }
}

class Test {
    public static void main(String[] args) {
        Context context = new Context();

        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");

        context.assign(a, 1);
        context.assign(b, 2);
        context.assign(c, 3);
        context.assign(d, 4);

        AbstractExpression
                expression = new Minus(a, new Plus(c, new Minus(b, d)));
        int res = expression.interpret(context);
        System.out.println(expression.toString());
        System.out.println(res);
    }
}

3.23.2、优劣

优点:

  • 易于改变和扩展文法:由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
  • 实现文法较为容易:在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
  • 增加新的解释表达式较为方便:如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。

缺点:

  • 对于复杂文法难以维护:在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。
  • 执行效率较低:由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

你可能感兴趣的:(#,Java,java,设计模式)