【笔记】设计模式

本文整理自 史上最全设计模式导学目录(完整版)

参考:https://blog.csdn.net/mnb65482/article/details/80458571


文章目录

设计模式(Design Pattern)

设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。

一、六大原则

1.单一职责原则(Single-Resposibility Principle)

高内聚
如果一个类承担的职责过多,那么这些职责就会相互依赖,一个职责的变化可能会影响另一个职责的履行。

2. 开闭原则(Open-Closed principle)

对扩展开放,对修改关闭
需求变更时,不必改动源代码,就能扩展它的行为,通常通过抽象类或接口来实现。

3.里式替换原则(Liskov-Substituion Principle)

子类型必须能够替换掉它们的基类型。正是子类型的可替换性,才使得使用基类型模块无需修改就可扩充。

4.依赖倒置原则(Dependecy-Inversion Principle)

抽象不应依赖于细节,细节应该依赖于抽象。
先有抽象程度比较高的实体,再有才是更加细节化的实体。

5.接口隔离原则(Interface-Segregation Principle)

多个专用接口优于一个单一的通用接口,本质上也是高内聚。

6.良性依赖原则

不会在实际中造成危害的依赖关系,都是良性依赖。

二、分类

设计模式共23种,分为三大类:

三、创建型

1.工厂方法模式(factory)

1.1 定义

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。

1.2例子
需求

日志记录器的设计:
该记录器可以通过多种途径保存系统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,Sunny公司的开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。
如何封装记录器的初始化过程并保证多种记录器切换的灵活性是Sunny公司开发人员面临的一个难题。

设计

在这里插入图片描述

实现
public interface Logger {
     void writeLog();
}

public class FileLogger implements Logger {

    public FileLogger() {
        System.out.println("FileLogger");
    }

    @Override
    public void writeLog() {
        System.out.println("FileLogger.writeLog()");
    }

}

public class DatabaseLogger implements Logger {

    public DatabaseLogger() {
        System.out.println("DatabaseLogger");
    }

    @Override
    public void writeLog() {
        System.out.println("DatabaseLogger.writeLog()");
    }

}
//日志记录器工厂
public interface LoggerFactory {

    Logger createLogger();

}

public class FileLoggerFactory implements LoggerFactory {

    @Override
    public Logger createLogger() {
        FileLogger logger = new FileLogger();
        return logger;
    }

}

public class DatabaseLoggerFactory implements LoggerFactory {

    @Override
    public Logger createLogger() {
        Logger logger = new DatabaseLogger();
        return logger;
    }

}
测试
public class Test {

    /**
     * LoggerFactory的具体类型,可以写在配置文件中,灵活使用
     * @param args
     */
    public static void main(String[] args) {
        LoggerFactory factory=new DatabaseLoggerFactory();
        Logger logger=factory.createLogger();
        logger.writeLog();
    }
}
1.3优缺点
优点:
  • 具体实现被隐藏,用户无需关心创建细节
  • 具体工厂类都具有同一抽象父类
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
  • 在添加新产品较多时,新增类较多,复杂性较高
  • 抽象层较多,增加理解难度

2.抽象工厂模式(Abstract Factory)

2.1 定义

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为“开闭原则”的倾斜性。

2.2例子
需求

Sunny软件公司欲开发一套界面皮肤库,可以对Java桌面软件进行界面美化。为了保护版权,该皮肤库源代码不打算公开,而只向用户提供已打包为jar文件的class字节码文件。用户在使用时可以通过菜单来选择皮肤,不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素,其结构示意图如图1所示:

图1 界面皮肤库结构示意图
该皮肤库需要具备良好的灵活性和可扩展性,用户可以自由选择不同的皮肤,开发人员可以在不修改既有代码的基础上增加新的皮肤。

设计

在这里插入图片描述

实现
public interface Button {

     void display();

}

public class SpringButton implements Button {

    public void display() {
        System.out.println("SpringButton.display()");
    }

}

public class SummerButton implements Button {

    public void display() {
        System.out.println("SummerButton.display()");
    }

}

public interface TextField {

    void display();

}

public class SpringTextField implements TextField {

    public void display() {
        System.out.println("SpringTextField.display()");
    }

}

public class SummerTextField implements TextField {

    public void display() {
        System.out.println("SummerTextField.display()");
    }

}
//抽象工厂
public interface AbstractFactory {

    Button createButton();

    TextField createTextField();

}
public class SpringFactory implements AbstractFactory {

    @Override
    public Button createButton() {
        return new SpringButton();
    }

    @Override
    public TextField createTextField() {
        return new SpringTextField();
    }

}

public class SummerFactory implements AbstractFactory {

    @Override
    public Button createButton() {
        return new SpringButton();
    }

    @Override
    public TextField createTextField() {
        return new SpringTextField();
    }

}
测试
public class Test {

    /**
     * AbstractFactory的具体类型,可以写在配置文件中,灵活使用
     * @param args
     */
    public static void main(String[] args) {
        AbstractFactory factory = new SpringFactory();
        Button button = factory.createButton();
        button.display();
        TextField textField = factory.createTextField();
        textField.display();
    }

}
2.3优缺点
优点:
  • 具体实现被隐藏,用户无需关心创建细节
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  • 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
  • 增加新的产品等级结构很麻烦

在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为“开闭原则”的倾斜性。

3.单例模式(Singleton)

3.1定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

3.2实现核心

构造方法私有,静态变量保存

3.3饿汉式 线程安全

类初始化时直接创建对象,占用内存,无论是否使用

1.直接实例化饿汉式(简洁直观)
public class Singleton1 {
    public static final Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
    }
}
2.枚举式(最简洁)

用的时候加载,常量只有一份

public enum Singleton2 {
    INSTANCE
}
3.静态代码块饿汉式(适合复杂实例化)
public class Singleton3 {
    public static final Singleton3 INSTANCE;
    private String info;

    private Singleton3(String info) {
        this.info = info;
    }

    static {
        try {
            Properties p = new Properties();
            p.load(Singleton3.class.getClassLoader().getResourceAsStream("prop.properties"));
            INSTANCE = new Singleton3(p.getProperty("info"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3.4懒汉式(延迟加载)

1.简单懒汉 线程不安全
public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {
    }

    public static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
}

缺点:在多线程环境中,不能保证单例的状态,instance为变量,在并发的时候不能保证是单例
测试、以下代码线程不安全:

public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {
    }

    public static Singleton4 getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Singleton4();
        }
        return instance;
    }
}

2.DCL(Double checked locking)双检查锁机制 线程安全
public class Singleton5 {
    private volatile static Singleton5 instance;

    private Singleton5() {
    }

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

需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
测试、以下代码线程安全:

public class Singleton5 {
    private volatile static Singleton5 instance;

    private Singleton5() {
    }

    public static Singleton5 getInstance() {
        if (instance == null) {
            synchronized (Singleton5.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}
3.静态内部类形式 线程安全
public class Singleton6 {

    private Singleton6() {
    }

    private static class Inner {
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance() {
        return Inner.INSTANCE;
    }
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。

那么,静态内部类又是如何实现线程安全的呢?首先,我们先了解下类的加载时机。

类加载时机:JAVA虚拟机在有且仅有的5种场景下会对类进行初始化。

  1. 遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
  3. 当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
  5. 当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

这5种情况被称为是类的主动引用,注意,这里《虚拟机规范》中使用的限定词是"有且仅有",那么,除此之外的所有引用类都不会对类进行初始化,称为被动引用。静态内部类就属于被动引用的行列。

3.5测试类

public class TestSingleton {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 2; i++) {
            Singleton1 singleton1 = Singleton1.INSTANCE;
            System.out.println("singleton1第" + i + "次:" + singleton1);
            Singleton2 singleton2 = Singleton2.INSTANCE;
            System.out.println("singleton2第" + i + "次:" + singleton2);
            Singleton3 singleton3 = Singleton3.INSTANCE;
            System.out.println("singleton3第" + i + "次:" + singleton3);

            Singleton6 singleton6 = Singleton6.getInstance();
            System.out.println("singleton6第" + i + "次:" + singleton6);
        }

        Callable<Singleton4> c = new Callable<Singleton4>() {
            @Override
            public Singleton4 call() throws Exception {
                return Singleton4.getInstance();
            }
        };
        ExecutorService e = Executors.newFixedThreadPool(2);
        Future<Singleton4> f1 = e.submit(c);
        Future<Singleton4> f2 = e.submit(c);
        Singleton4 s41 = f1.get();
        Singleton4 s42 = f2.get();
        System.out.println("Singleton4结果:");
        System.out.println(s41 == s42);
        e.shutdown();

        Callable<Singleton5> c2 = new Callable<Singleton5>() {
            @Override
            public Singleton5 call() throws Exception {
                return Singleton5.getInstance();
            }
        };
        ExecutorService e2 = Executors.newFixedThreadPool(2);
        Future<Singleton5> f3 = e2.submit(c2);
        Future<Singleton5> f4 = e2.submit(c2);
        Singleton5 s51 = f3.get();
        Singleton5 s52 = f4.get();
        System.out.println("Singleton5结果:");
        System.out.println(s51 == s52);
        e2.shutdown();

    }
}

console

singleton1第0次:com.dyn.demo.springclouddemo.Singleton.Singleton1@3a71f4dd
singleton2第0次:INSTANCE
singleton3第0次:com.dyn.demo.springclouddemo.Singleton.Singleton3@85ede7b
singleton6第0次:com.dyn.demo.springclouddemo.Singleton.Singleton6@5674cd4d
singleton1第1次:com.dyn.demo.springclouddemo.Singleton.Singleton1@3a71f4dd
singleton2第1次:INSTANCE
singleton3第1次:com.dyn.demo.springclouddemo.Singleton.Singleton3@85ede7b
singleton6第1次:com.dyn.demo.springclouddemo.Singleton.Singleton6@5674cd4d
Singleton4结果:
false
Singleton5结果:
true

四、结构型

1.适配器模式(Adapter)

1.1 定义

将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。
适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

1.2对象适配器
需求

Sunny软件公司在很久以前曾开发了一个算法库,里面包含了一些常用的算法,例如排序算法和查找算法,在进行各类软件开发时经常需要重用该算法库中的算法。在为某学校开发教务管理系统时,开发人员发现需要对学生成绩进行排序和查找,该系统的设计人员已经开发了一个成绩操作接口ScoreOperation,在该接口中声明了排序方法sort(int[]) 和查找方法search(int[], int),为了提高排序和查找的效率,开发人员决定重用算法库中的快速排序算法类QuickSort和二分查找算法类BinarySearch,其中QuickSort的quickSort(int[])方法实现了快速排序,BinarySearch 的binarySearch (int[], int)方法实现了二分查找。

由于某些原因,现在Sunny公司开发人员已经找不到该算法库的源代码,无法直接通过复制和粘贴操作来重用其中的代码;部分开发人员已经针对ScoreOperation接口编程,如果再要求对该接口进行修改或要求大家直接使用QuickSort类和BinarySearch类将导致大量代码需要修改。

Sunny软件公司开发人员面对这个没有源码的算法库,遇到一个幸福而又烦恼的问题:如何在既不修改现有接口又不需要任何算法库代码的基础上能够实现算法库的重用?

设计

在这里插入图片描述

实现
//抽象成绩操作类:目标接口
public interface ScoreOperation {
    public int[] sort(int array[]); //成绩排序
    public int search(int array[],int key); //成绩查找
}
// 适配器
public class OperationAdapter implements ScoreOperation {

    BinarySearch binarySearch;

    QuickSort quickSort;

    public OperationAdapter() {
        binarySearch = new BinarySearch();
        quickSort = new QuickSort();
    }

    @Override
    public int[] sort(int[] array) {
        return quickSort.quickSort(array);
    }

    @Override
    public int search(int[] array, int key) {
        return binarySearch.binarySearch(array, key);
    }

}
//快速排序类:适配者
public class QuickSort {
    public int[] quickSort(int array[]) {
        sort(array,0,array.length-1);
        return array;
    }

    public void sort(int array[],int p, int r) {
        int q=0;
        if(p<r) {
            q=partition(array,p,r);
            sort(array,p,q-1);
            sort(array,q+1,r);
        }
    }

    public int partition(int[] a, int p, int r) {
        int x=a[r];
        int j=p-1;
        for (int i=p;i<=r-1;i++) {
            if (a[i]<=x) {
                j++;
                swap(a,j,i);
            }
        }
        swap(a,j+1,r);
        return j+1;
    }

    public void swap(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}

//二分查找类:适配者
public class BinarySearch {
    public int binarySearch(int array[],int key) {
        int low = 0;
        int high = array.length -1;
        while(low <= high) {
            int mid = (low + high) / 2;
            int midVal = array[mid];
            if(midVal < key) {
                low = mid +1;
            }
            else if (midVal > key) {
                high = mid -1;
            }
            else {
                return 1; //找到元素返回1
            }
        }
        return -1;  //未找到元素返回-1
    }
}
测试
public class Test {

    public static void main(String[] args) {
        ScoreOperation operation = new OperationAdapter();
        int[] scores = {84, 76, 50, 69, 90, 91, 88, 96}; //定义成绩数组
        for (int i : scores) {
            System.out.print(i + ",");
        }
        System.out.println();
        scores = operation.sort(scores);
        for (int i : scores) {
            System.out.print(i + ",");
        }
    }

}
1.3缺省适配器

当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。

设计

在这里插入图片描述

实现
public interface ServiceInterface {
    void test1();
    void test2();
    void test3();
    void test4();
}

public abstract class AbstractServiceClass implements ServiceInterface {

    public void test1() {
    }

    public void test2() {
    }

    public void test3() {
    }

    public void test4() {
    }

}

public class ConcreteServiceClass extends AbstractServiceClass {

    public void test1() {
    }

}
1.4优缺点
优点:
  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
  • 不能多继承,一次最多只能适配一个适配者类,不能同时适配多个适配者
  • 类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

2.装饰器模式(Decorator)

2.1 定义

动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。

2.2例子
需求

图形界面构件库的设计
Sunny软件公司基于面向对象技术开发了一套图形界面构件库VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特效显示效果,如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能,如图

在这里插入图片描述
如何提高图形界面构件库性的可扩展性并降低其维护成本是Sunny公司开发人员必须面对的一个问题。

设计

在这里插入图片描述

实现
public interface Component {

    public void display();

}

public class TextBox implements Component{

    @Override
    public void display() {
        System.out.println("TextBox.display()");
    }

}

public class Window implements Component {

    public void display() {

        System.out.println("Window.display()");

    }

}
//构件装饰类:抽象装饰类
public class ComponentDecorator implements Component {

    private Component component;  //维持对抽象构件类型对象的引用

    public ComponentDecorator(Component component)  //注入抽象构件类型的对象
    {
        this.component = component;
    }

    public void display() {
        component.display();
    }

}

//黑色边框装饰类:具体装饰类
public class BlackBorderDecorator extends ComponentDecorator {

    public BlackBorderDecorator(Component component) {

        super(component);

    }

    public void display() {

        this.setBlackBorder();
        super.display();

    }

    public void setBlackBorder() {

        System.out.println("为构件增加黑色边框!");

    }

}

//滚动条装饰类:具体装饰类
public class ScrollBarDecorator extends ComponentDecorator {

    public ScrollBarDecorator(Component component) {
        super(component);
    }

    public void display() {

        this.setScrollBar();

        super.display();

    }

    public void setScrollBar() {

        System.out.println("为构件增加滚动条!");

    }

}
测试
public class Test {

    public static void main(String[] args) {
        Component component = new Window(); //定义具体构件

        Component componentSB = new ScrollBarDecorator(component); //定义装饰后的构件

        componentSB.display();

    }

}
2.3优缺点
优点:
  • 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  • 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  • 增加新的功能很方便,无须修改已有系统,符合“开闭原则”。
缺点:
  • 比继承更加灵活,也更容易出错

在以下情况下可以考虑使用装饰模式:

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。

不能采用继承的情况主要有两类:

  • 第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;
  • 第二类是因为类已定义为不能被继承(如Java语言中的final类)。

3.外观模式(Facade)

3.1定义

为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

3.2例子
需求

文件加密模块设计
某软件公司欲开发一个可应用于多个软件的文件加密模块,该模块可以对文件中的数据进行加密并将加密之后的数据存储在一个新文件中,具体的流程包括三个部分,分别是读取源文件、加密、保存加密之后的文件,其中,读取文件和保存文件使用流来实现,加密操作通过求模运算实现。这三个操作相对独立,为了实现代码的独立重用,让设计更符合单一职责原则,这三个操作的业务代码封装在三个不同的类中。

如果在应用实例“文件加密模块”中需要更换一个加密类,不再使用原有的基于求模运算的加密类CipherMachine,而改为基于移位运算的新加密类NewCipherMachine

设计

在这里插入图片描述

实现
public class FileReader {

    public String Read(String fileNameSrc) {
        return "";
    }

}

public class FileWriter {

    public void Write(String encryptStr, String fileNameDes) {

    }

}

public class CipherMachine {

    // 数据加密
    public String Encrypt(String plainText) {
        return "";
    }

}

public class NewCipherMachine {

    // 数据加密
    public String Encrypt(String plainText) {
        return "";
    }

}
public abstract class AbstractEncryptFacade {

    public abstract void fileEncrypt(String fileNameSrc, String fileNameDes);
}

public class EncryptFacade extends AbstractEncryptFacade{

    //维持对其他对象的引用
    private FileReader reader;
    private CipherMachine cipher;
    private FileWriter writer;

    public EncryptFacade()
    {
        reader = new FileReader();
        cipher = new CipherMachine();
        writer = new FileWriter();
    }

    //调用其他对象的业务方法
    public void fileEncrypt(String fileNameSrc, String fileNameDes)
    {
        String plainStr = reader.Read(fileNameSrc);
        String encryptStr = cipher.Encrypt(plainStr);
        writer.Write(encryptStr, fileNameDes);
    }
}

public class NewEncryptFacade extends AbstractEncryptFacade {

    private FileReader reader;

    private NewCipherMachine cipher;

    private FileWriter writer;

    public NewEncryptFacade() {
        reader = new FileReader();
        cipher = new NewCipherMachine();
        writer = new FileWriter();
    }

    public void fileEncrypt(String fileNameSrc, String fileNameDes) {
        String plainStr = reader.Read(fileNameSrc);
        String encryptStr = cipher.Encrypt(plainStr);
        writer.Write(encryptStr, fileNameDes);
    }

}
测试
public class Test {

    public static void main(String[] args) {
        AbstractEncryptFacade ef = new EncryptFacade();
        ef.fileEncrypt("src.txt", "des.txt");

        AbstractEncryptFacade ef2 = new NewEncryptFacade();
        ef2.fileEncrypt("src.txt", "des.txt");

    }

}
3.3优缺点
优点:
  • 子系统与客户端间解耦
  • 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
缺点:
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
适用场景:

​ (1) 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。

​ (2) 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。

​ (3) 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

4.代理模式(Proxy)

4.1定义

给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理

  • 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
  • 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
4.2例子
需求

某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:

  1. 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;
  2. 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。

该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。

设计

在这里插入图片描述

实现
public interface Searcher {

    String doSearch(String userId, String keyword);
}

public class RealSearcher implements Searcher{

    @Override
    public String doSearch(String userId, String keyword) {
        return "001";
    }

}

//扩展功能 模拟实现登录验证
public class AccessValidator {

    public boolean Validate(String userId) {
        return true;
    }

}

//扩展功能 模拟实现日志记录
public class Logger {

    public void Log(String userId) {
        System.out.println("用户" + userId + "查询次数加1!");
    }

}

// 代理类
public class ProxcySearcher implements Searcher {

    private RealSearcher searcher = new RealSearcher(); //维持一个对真实主题的引用

    private AccessValidator validator;

    private Logger logger;

    @Override
    public String doSearch(String userId, String keyword) {
        //如果身份验证成功,则执行查询
        if (this.validate(userId)) {
            System.out.println(userId+"用户验证通过");
            String result = searcher.doSearch(userId, keyword); //调用真实主题对象的查询方法
            this.log(userId); //记录查询日志
            return result; //返回查询结果
        } else {
            return null;
        }
    }

    //创建访问验证对象并调用其Validate()方法实现身份验证
    public boolean validate(String userId) {
        validator = new AccessValidator();
        return validator.Validate(userId);
    }

    //创建日志记录对象并调用其Log()方法实现日志记录
    public void log(String userId) {
        logger = new Logger();
        logger.Log(userId);
    }

}

测试
public class Test {

    public static void main(String[] args) {
        Searcher searcher = new ProxcySearcher();
        String result = searcher.doSearch("杨过", "玉女心经");
    }

}
4.3优缺点
优点:
  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
4.4动态代理

对于静态代理,一个代理只能代理一个对象,如果有多个对象需要被代理,就需要很多代理类,造成代码的冗余。

动态代理是相对于静态代理而提出的设计模式。在 Spring 中,有两种方式可以实现动态代理 —— JDK 动态代理和 CGLIB 动态代理。

4.4.1JDK 动态代理

JDK 代理的对象是动态生成的。JDK 代理的条件是被代理对象必须实现接口。

// 1.定义接口
public interface Action {
	public void move();
}
// 2.定义被代理对象
public class TargetAction implements Action{
    @Override
    public void move(){
        System.out.println("被代理对象TargetAction的move方法被调用");
    }
}

// 3.实现InvocationHandler接口,定义调用者
public class ActionInvocationHandler implements InvocationHandler{
    private Action target;
    public Object bind(Action target){
        this.target = target;
        // 通过反射机制,创建一个代理对象实例并返回,用户进行方法调用时使用
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
        Object result = null;
        // 在动态代理类中,在被代理的方法前后可以加业务逻辑,不会破坏原方法
        // 调用前处理
       
        // 真正的业务
        result = method.invoke(target,args);
       
        // 调用后处理
        
        return result;
    }
}

// 4.测试
public class Test{
    @Test
    public void testProxy(){
        //被代理对象
        TargetAction action = new TargetAction();
        // 动态代理类对象
        ActionInvocationHandler handler = new ActionInvocationHandler();
        // 代理对象
        Action proxy = handler.bind(action);
        proxy.move();
    }
}

动态代理类的生成过程可以归纳为以下一个步骤:

  1. 根据接口信息,新生成一个代理类的.java文件
  2. 根据.java,编译生成.class文件
  3. classloader读取class文件信息到jvm
  4. 新建对象,设置InvocationHandler参数。
4.4.2CGLIB 动态代理

在上面实现的 JDK 动态代理的方式中,不难发现 JDK 动态代理有一个缺点,即被代理类必须实现接口。这显然不能满足开发过程中的需要。有没有可能实现接口,直接就对 Java 类进行代理呢?这就需要 CGLIB 发挥作用了。

// 1.定义被代理对象
public class TargetAction {
    @Override
    public void move(){
        System.out.println("被代理对象TargetAction的move方法被调用");
    }
}

// 2.实现MethodInterceptor 接口,重写intercept方法
public class ActionInvocationHandler implements MethodInterceptor{

    @Override
    public Object intercept(Object obj,Method method,Object[] objects,MethodProxy methodProxy)throws Throwable{
        Object result = null;
        // 在动态代理类中,在被代理的方法前后可以加业务逻辑,不会破坏原方法
        // 调用前处理
       
        // 真正的业务
        result = methodProxy.invokeSuper(obj,objects);
       
        // 调用后处理
        
        return result;
    }
}
// 3.定义代理类创建器
public class CglibProxyCreator{
    public static <T> T create( Class<T> clazz, MethodInterceptor callbackinvoker){
        // 增强者
        Enhancer enhancer = new Enhancer();
        // 设置代理类
        enhancer.setSuperClass(clazz);
        // 设置Invoker
        enhancer.setCallback(callbackinvoker);
        return (T)enhancer.create();
    }
}

// 4.测试
public class Test{
    @Test
    public void testProxy(){
        //被代理对象
        TargetAction action = new TargetAction();
        // 代理对象
        TargetAction proxy = CglibProxyCreator.create(TargetAction.class,new ActionInvocationHandler(action));
        proxy.move();
    }
}

五、行为型

1.观察者模式(Observer)

1.1 定义

定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
观察者模式的别名包括发布-订阅(P ublish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

1.2例子
需求

微博消息推送

设计

在这里插入图片描述

实现
// 对象
public abstract class Subject {

    //定义一个观察者集合用于存储所有观察者对象
    protected List<Observer> observers = new ArrayList();

    //注册方法,用于向观察者集合中增加一个观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }

    //注销方法,用于在观察者集合中删除一个观察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    //声明抽象通知方法
    public abstract void notifyMethod(String message);

}

public class ConcreteSubject extends Subject {

    //实现通知方法
    public void notifyMethod(String message) {
        //遍历观察者集合,调用每一个观察者的响应方法
        for (Object obs : observers) {
            ((Observer) obs).update(message);
        }
    }

}
//抽象观察者
public interface Observer {

    //声明响应方法
    public void update(String message);

}

// 具体观察者
public class ConcreteObserver implements Observer {

    // 微博用户名
    private String name;

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

    //实现响应方法
    public void update(String message) {
        System.out.println("to " + name + ":" + message);
    }

}
测试
public class Test {

    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        // 创建观察者
        ConcreteObserver observer1 = new ConcreteObserver("dyn");
        ConcreteObserver observer2 = new ConcreteObserver("wxy");
        ConcreteObserver observer3 = new ConcreteObserver("jzy");

        // 将观察者放入观察者集合,订阅消息
        subject.attach(observer1);
        subject.attach(observer2);
        subject.attach(observer3);

        // 推送消息
        subject.notifyMethod("Jolin微博更新了");
    }

}

console

to dyn:Jolin微博更新了
to wxy:Jolin微博更新了
to jzy:Jolin微博更新了
1.3优缺点
优点:
  • 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  • 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码
缺点:
  • 观察者过多时,全部通知到比较耗时
  • 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值