1、什么是设计模式

概念

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方法。

意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

正确使用设计模式具有以下优点:

  • 可以提高程序员的思维能力、编程能力和设计能力
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强

重要的是学习设计的思想和应用的场景

设计模式的基本要素

  • 模式名称
  • 问题
  • 解决方案
  • 效果

2、GoF23

23种设计模式

一种思维、一种态度、一种进步

创建型模式:

  1. 单例模式
  2. 工厂模式
  3. 抽象工厂模式
  4. 建造者模式
  5. 原型模式

结构型模式:

  1. 适配器模式
  2. 桥接模式
  3. 装饰模式
  4. 组合模式
  5. 外观模式
  6. 享元模式
  7. 代理模式

行为型模式:

  1. 模板方法模式
  2. 命令模式
  3. 迭代器模式
  4. 观察者模式
  5. 中介者模式
  6. 备忘录模式
  7. 解释器模式
  8. 状态模式
  9. 策略模式
  10. 职责链模式
  11. 访问者模式

3、OOP七大原则

  1. 开闭原则:对扩展开发,对修改关闭(不修改源代码,只进行扩展)
  2. 里氏替换原则:继承必须确保超类所拥有的性质在子类中依然成立(尽量不覆盖父类的方法)
  3. 依赖倒置原则:要面向接口编程,不要面向实现编程(降低耦合性)
  4. 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性(原子性,一个方法做一件事情)
  5. 接口隔离原则:要为各个类建立它们需要的专用接口
  6. 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话(降低耦合性)
  7. 合成复用原则:尽量使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

4、单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

注意:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

饿汉式,DCL 懒汉式

1、饿汉式单例

一上来就把对象加载了,可能造成内存浪费

// 饿汉式单例
public class Hungry { 
    // 一上来就初始化,可能造成浪费空间
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];

    // 让构造函数为 private,这样该类就不会被实例化
    private Hungry() { } 

    private final static Hungry hungry = new Hungry();

    // 获取唯一可用的对象
    public static Hungry getInstance() { 
        return hungry;
    } 
} 

2、懒汉式单例

// 懒汉式单例
public class LazyMan { 
    private LazyMan() { 

    } 

    private static LazyMan lazyMan;

    public static LazyMan getInstance() { 
        if(lazyMan == null) { 
            // 当对象为空时才进行创建
            lazyMan = new LazyMan();
        } 
        return lazyMan;
    } 
} 

在多线程并发情况下单例失败

// 懒汉式单例
public class LazyMan { 
    private LazyMan() { 
        System.out.println(Thread.currentThread().getName() + "ok");
    } 

    private static LazyMan lazyMan;

    public static LazyMan getInstance() { 
        if(lazyMan == null) { 
            lazyMan = new LazyMan();
        } 
        return lazyMan;
    } 

    public static void main(String[] args) { 
        // 创建10个线程并发访问
        for (int i = 0; i < 10; i++) { 
            new Thread(()->{  
                lazyMan.getInstance();
            } ).start();
        } 
    } 
} 

此时单例被破坏

image-20210608232629173

修改getInstance方法,加锁保证唯一

// 双重检测锁模式 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() { 
    // 加锁进行测试
    if(lazyMan == null) { 
        // 锁住得是整个对象,只有一个线程可以获取到
        synchronized (LazyMan.class) { 
            if(lazyMan == null) { 
                lazyMan = new LazyMan();
            } 
        } 
    } 
    return lazyMan;
} 

存在问题

lazyMan = new LazyMan(); // 不是原子性操作
/**
 * 执行步骤
 * 1、分配内存空间
 * 2、执行构造方法,初始化对象
 * 3、把这个对象指向这个空间
 *
 * 可能存在指令重排现象
 * 1、我们期望的顺序是上面的1 -> 2 -> 3
 * 2、而可能出现 1 -> 3 ->2 顺序,使用还没执行构造方法就把这个对象指向这个空间
 * 3、此时如果新来了一个线程B,测试获取到lazyMan判断为空,但实际上却未进行构造方法
 * 所以就会出现指令重排现象
 *
 * 解决方法
 * 1、在lazyMan对象前面加上volatile避免指令重排
 * private volatile static LazyMan lazyMan;
 */

3、双重校验锁

在lazyMan对象前面加上volatile避免指令重排

private volatile static LazyMan lazyMan;
// 双重检测锁模式 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() { 
    // 加锁进行测试
    if(lazyMan == null) { 
        // 锁住得是整个对象,只有一个线程可以获取到
        synchronized (LazyMan.class) { 
            if(lazyMan == null) { 
                lazyMan = new LazyMan();
            } 
        } 
    } 
    return lazyMan;
} 

4、静态内部类

public class Holder { 
    private Holder() { } 

    public static Holder getInstance() { 
        return InnerClass.HOLDER;
    } 

    // 创建静态内部类
    public static class InnerClass { 
        private static final Holder HOLDER = new Holder();
    } 
} 

5、反射问题

破坏单例

// 使用反射破坏单例
public static void main(String[] args) throws Exception { 
    LazyMan instance = lazyMan.getInstance();
    // 反射获取构造器
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    // 无视私有构造器
    declaredConstructor.setAccessible(true);
    // 通过反射创建对象
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance);
    System.out.println(instance2);
} 

此时发现单例被破坏了

image-20210608234753958

解决破坏

// 在构造方法中加锁,变成三重检测
private LazyMan() { 
    synchronized (LazyMan.class) { 
        if(lazyMan != null) { 
            throw new RuntimeException("不要使用反射破坏单例");
        } else { 
            System.out.println(Thread.currentThread().getName() + "ok!");
        } 
    } 
} 

image-20210608235216716

如果两个对象都使用反射

// 使用反射破坏单例
public static void main(String[] args) throws Exception { 
    //        LazyMan instance = lazyMan.getInstance();
    // 反射获取构造器
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    // 无视私有构造器
    declaredConstructor.setAccessible(true);
    // 通过反射创建对象
    LazyMan instance = declaredConstructor.newInstance();
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance);
    System.out.println(instance2);
} 

单例又被破坏了

image-20210608235331847

加上标志位防止反射破坏单例

private static boolean xiaojiang = false;

private LazyMan() { 
    synchronized (LazyMan.class) { 
        if(xiaojiang == false) { 
            xiaojiang = true;
            System.out.println(Thread.currentThread().getName() + "ok!");
        } else { 
            throw new RuntimeException("不要使用反射破坏单例");
        } 
    } 
} 

此时又可以防止被反射破坏,标志位可以设置为密钥

image-20210608235637898

破坏关键字

// 使用反射破坏单例
public static void main(String[] args) throws Exception { 
    //        LazyMan instance = lazyMan.getInstance();

    // 获取私有字段
    Field xiaojiang = LazyMan.class.getDeclaredField("xiaojiang");
    xiaojiang.setAccessible(true);

    // 反射获取构造器
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    // 无视私有构造器
    declaredConstructor.setAccessible(true);
    // 通过反射创建对象
    LazyMan instance = declaredConstructor.newInstance();
    // 修改关键字的值
    xiaojiang.set(instance, false);
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance);
    System.out.println(instance2);
} 

此时单例再次被破坏

image-20210609000024315

思考:如何防止反射破坏单例呢?

通过分析newInstance()源码,发现如果使用的是枚举则无法通过反射破坏单例

image-20210609000302364

6、枚举的单例模式

  1. 创建枚举类

    package com.xj.single;
    
    // 枚举本身也是一个class类
    public enum EnumSingle { 
        INSTANCE;
    
        public EnumSingle getInstance(){ 
            return INSTANCE;
        } 
    } 
    
    class Test { 
        public static void main(String[] args) { 
            EnumSingle instance = EnumSingle.INSTANCE;
            System.out.println(instance);
        } 
    } 
  2. 通过查看该枚举源码,发现具有一个无参构造方法

    image-20210609001149134

  3. 通过反射进行测试

    public static void main(String[] args) throws Exception { 
        EnumSingle instance = EnumSingle.INSTANCE;
        System.out.println(instance);
        // 通过反射获取构造方法
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance2);
    } 

    不出意外报错了,但报错内容并不是上面提到的不能破坏枚举的单例,而是当前类不具有无参构造函数

    image-20210609001548440

  4. 手动反编译该类测试

    javap -p EnumSingle.class

    image-20210609001751856

    同样在这里也有一个无参构造,但是实际上却不存在

  5. 使用jad反编译工具,将应用程序复制到当前文件夹中

    image-20210609002257370

  6. 执行反编译

    image-20210609002355968

  7. 发现已经成功反编译出源码

    image-20210609002418719

  8. 发现这里面使用的是有参构造器

    image-20210609002524412

  9. 修改反射的构造方法

    public static void main(String[] args) throws Exception { 
        EnumSingle instance = EnumSingle.INSTANCE;
        System.out.println(instance);
        // 通过反射获取有参构造方法
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance2);
    } 
  10. 再次执行查看,此时报错提示不能使用反射破坏单例

    image-20210609002647387

5、工厂模式

1、基本概念

作用

实现创建者和调用者的分离

OOP七大原则

  • 开闭模式(不修改源代码,只进行扩展)
  • 依赖倒置原则(面向接口)
  • 迪米特法则(降低耦合,加中间类)

核心本质

  • 实例化对象不使用new,用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦

三种模式

  • 简单工厂模式:用来生产同一等级结构中的任意产品
  • 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
  • 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又成为其他工厂的工厂

2、Java实现

默认的接口和实现类方法如下

  1. 创建接口Car

    public interface Car { 
        void name();
    } 
  2. 创建两个实现类WulingTesla

    public class Wuling implements Car { 
        @Override
        public void name() { 
            System.out.printlb("五菱宏光");
        } 
    } 
    
    public class Tesla implements Car { 
        @Override
        public void name() { 
            System.out.printlb("特斯拉");
        } 
    } 
  3. 创建消费者Consumer

    public class Consumer { 
        public static void main(String[] args) { 
            Car car = new Wuling();
            Car car2 = new Tesla();
    
            car.name();
            car2.name();
        } 
    } 

从上面的例子中可以成功输入内容,但是需要我们使用new自己创建了两个对象,如果对于复杂的实现类,则需要我们了解该接口的所有内容。

而工厂模式的核心是:实例化对象不使用new,用工厂方法代替。

简单工厂模式

  1. 创建工厂类CarFactory

    public class CarFactory { 
        public static Car getCar(String car) { 
            if(car.equals('五菱')) { 
                return new Wuling();
            } else if(car.equals('特斯拉')) { 
                return new Tesla();
            } else { 
                return null;
            } 
        } 
    } 
  2. 使用工厂创建,使用工厂就不需要考虑创建对象需要的参数

    public class Consumer { 
        public static void main(String[] args) { 
            Car car = CarFactory.getCar('五菱');
            Car car = CarFactory.getCar('特斯拉');
    
            car.name();
            car2.name();
        } 
    } 
  3. 但是如果此时我们新增了一个车类Dazhong

    public class Dazhong implements Car { 
        @Override
        public void name() { 
            System.out.printlb("大众");
        } 
    } 
  4. 此时如果我们要使用这台车的话,就需要去修改工厂的代码,从而能够通过工厂获取大众,但这样就不符合开闭原则

image-20210519174503482

工厂方法模式

符合开闭原则的方法

创建一个工厂接口,一种类型的产品对应一个自己的工厂,所以每增加一种产品就要创建一个新的产品工厂,从而符合开闭原则

  1. 创建工厂接口

    public interface CarFactory { 
        public Car getCar();
    } 
  2. 创建对应的工厂实现类

    public class TeslaFactory implements CarFactory { 
        @Override
        public Car getCar() { 
            return new Tesla();
        } 
    } 
    
    public class WulinFactory implements CarFactory { 
        @Override
        public Car getCar() { 
            return new Wulin();
        } 
    } 
  3. 通过对应的工厂获取产品

    public class Customer { 
        public static void main(String[] args) { 
            Car car = new WulinFactory().getCar();
            Car car1 = new TeslaFactory().getCar();
            car.name();
            car1.name();
        } 
    } 

所以在需要增加的时候就可以新建对应的工厂,加一层

image-20210707213631400

小结:

  • 简单工厂模式(静态工厂模式)
    • 虽然某种程度上不符合设计原则,但实际使用最多
  • 工厂方法模式
    • 不修改已有类的前提下,通过增加新的工厂类实现扩展
  • 抽象工厂模式
    • 不可以增加产品,可以增加产品族

6、抽象工厂模式

定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。

优点:具体产品在应用层的代码隔离,无需关心创建的细节。将一个系列的产品统一到一起创建

缺点:规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难。增加了系统的抽象性和理解难度。

image-20210707214714696

  1. 创建产品接口

    public interface Phone { 
        void name();
        void call();
    } 
    
    public interface Router { 
        void name();
        void openWifi();
    } 
  2. 创建接口实现类

    public class XiaomiPhone implements Phone { 
        @Override
        public void name() { 
            System.out.println("小米手机");
        } 
    
        @Override
        public void call() { 
            System.out.println("小米打电话");
        } 
    } 
    
    public class HuaweiPhone implements Phone { 
        @Override
        public void name() { 
            System.out.println("华为手机");
        } 
    
        @Override
        public void call() { 
            System.out.println("华为打电话");
        } 
    } 
    
    public class XiaomiRouter implements Router { 
    
        @Override
        public void name() { 
            System.out.println("小米路由器");
        } 
    
        @Override
        public void openWifi() { 
            System.out.println("打开小米Wifi");
        } 
    } 
    
    public class HuaweiRouter implements Router { 
        @Override
        public void name() { 
            System.out.println("华为路由器");
        } 
    
        @Override
        public void openWifi() { 
            System.out.println("打开华为Wifi");
        } 
    } 
  3. 创建抽象工厂接口

    // 抽线工厂接口
    public interface Factory { 
        // 获取手机
        Phone getPhone();
        // 获取路由器
        Router getRouter();
    } 
  4. 创建对应品牌的工厂

    public class XiaomiFactory implements Factory { 
        @Override
        public Phone getPhone() { 
            return new XiaomiPhone();
        } 
    
        @Override
        public Router getRouter() { 
            return new XiaomiRouter();
        } 
    } 
    
    public class HuaweiFactory implements Factory { 
        @Override
        public Phone getPhone() { 
            return new HuaweiPhone();
        } 
    
        @Override
        public Router getRouter() { 
            return new HuaweiRouter();
        } 
    } 
  5. 客户端进行使用

    public class Client { 
        public static void main(String[] args) { 
            // 获取小米工厂
            Factory xiaomiFactory = new XiaomiFactory();
            Phone phone1 = xiaomiFactory.getPhone();
            Router router1 = xiaomiFactory.getRouter();
            phone1.name();
            router1.name();
    
            // 获取华为工厂
            Factory huaweiFactory = new HuaweiFactory();
            phone1 = huaweiFactory.getPhone();
            router1 = huaweiFactory.getRouter();
            phone1.call();
            router1.openWifi();
        } 
    } 

但是如果要增加修改抽象工厂生产电脑的功能,则会需要修改很多代码,不符合开闭原则。

7、建造者模式

建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。

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

主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

例如:

  • 工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
  • 汽车购买者(用户):只需要说出需要的型号,直接购买,不需要知道汽车怎么组装的

需要有:

  1. 产品Product,最终生成的东西
  2. 抽象构建者Builder,进行相应方法的定义并返回一个产品
  3. 具体实现者Worker
  4. 指挥者Diretor,用于指定构建的顺序

代码实现

  1. 创建产品类

    // 最终构建出来的产品
    public class Product { 
        private String BuildA;
        private String BuildB;
        private String BuildC;
        private String BuildD;
    
        public String getBuildA() { 
            return BuildA;
        } 
    
        public void setBuildA(String buildA) { 
            BuildA = buildA;
        } 
    
        public String getBuildB() { 
            return BuildB;
        } 
    
        public void setBuildB(String buildB) { 
            BuildB = buildB;
        } 
    
        public String getBuildC() { 
            return BuildC;
        } 
    
        public void setBuildC(String buildC) { 
            BuildC = buildC;
        } 
    
        public String getBuildD() { 
            return BuildD;
        } 
    
        public void setBuildD(String buildD) { 
            BuildD = buildD;
        } 
    
        @Override
        public String toString() { 
            return "Product{ " +
                    "BuildA='" + BuildA + '\'' +
                    ", BuildB='" + BuildB + '\'' +
                    ", BuildC='" + BuildC + '\'' +
                    ", BuildD='" + BuildD + '\'' +
                    '} ';
        } 
    } 
  2. 抽象建造类

    // 抽象的建造者
    public abstract class Builder { 
        abstract void buildA();
        abstract void buildB();
        abstract void buildC();
        abstract void buildD();
    
        // 返回一个产品
        abstract Product getProduct();
    } 
  3. 具体实现者

    // 建造者的实现类,需要有一个产品
    public class Worker extends Builder { 
        private Product product;
    
        public Worker() { 
            this.product = new Product();
        } 
    
        @Override
        void buildA() { 
            product.setBuildA("地基");
            System.out.println("地基");
        } 
    
        @Override
        void buildB() { 
            product.setBuildB("钢筋水泥");
            System.out.println("钢筋水泥");
        } 
    
        @Override
        void buildC() { 
            product.setBuildC("铺电线");
            System.out.println("铺电线");
        } 
    
        @Override
        void buildD() { 
            product.setBuildD("粉刷");
            System.out.println("粉刷");
        } 
    
        @Override
        Product getProduct() { 
            return product;
        } 
    } 
  4. 指挥者,可以返回一个产品

    public class Director { 
        // 指挥房子的建造顺序
        public Product build(Builder builder) { 
            builder.buildA();
            builder.buildB();
            builder.buildC();
            builder.buildD();
    
            return builder.getProduct();
        } 
    } 
  5. 调用测试

    public class Client { 
        public static void main(String[] args) { 
            // 创建指挥者
            Director director = new Director();
            // 指定一个工人来制造产品
            Builder builder = new Worker();
            // 创建房子
            Product product = director.build(builder);
            System.out.println(product.toString());
        } 
    } 

    image-20210708121004969

    同时也可以通过改修指挥者的代码顺序控制制造过程。同时也可以修改对应的实现类来创建产品。

让用户担任指挥者(不再需要一个指挥者类)

通过静态内部类方式实现零件无序装配构造,这个方式使用更加灵活,更符合定义。内部类有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产不同复杂的产品。

  1. 创建产品类

    public class Product { 
        private String buildA = "薯条";
        private String buildB = "汉堡";
        private String buildC = "可乐";
        private String buildD = "甜点";
    
        public String getBuildA() { 
            return buildA;
        } 
    
        public void setBuildA(String buildA) { 
            this.buildA = buildA;
        } 
    
        public String getBuildB() { 
            return buildB;
        } 
    
        public void setBuildB(String buildB) { 
            this.buildB = buildB;
        } 
    
        public String getBuildC() { 
            return buildC;
        } 
    
        public void setBuildC(String buildC) { 
            this.buildC = buildC;
        } 
    
        public String getBuildD() { 
            return buildD;
        } 
    
        public void setBuildD(String buildD) { 
            this.buildD = buildD;
        } 
    
        @Override
        public String toString() { 
            return "Product{ " +
                    "buildA='" + buildA + '\'' +
                    ", buildB='" + buildB + '\'' +
                    ", buildC='" + buildC + '\'' +
                    ", buildD='" + buildD + '\'' +
                    '} ';
        } 
    } 
  2. 抽象建造者

    // 建造者
    public abstract class Builder { 
        // 返回类型位Builder,支持链式编程
        abstract Builder builderA(String msg);
        abstract Builder builderB(String msg);
        abstract Builder builderC(String msg);
        abstract Builder builderD(String msg);
        // 返回一个产品
        abstract Product getProduct();
    } 
  3. 具体实现者

    public class Worker extends Builder { 
        private Product product;
    
        public Worker() { 
            this.product = new Product();
        } 
    
        @Override
        Builder builderA(String msg) { 
            product.setBuildA(msg);
            return this;
        } 
    
        @Override
        Builder builderB(String msg) { 
            product.setBuildB(msg);
            return this;
        } 
    
        @Override
        Builder builderC(String msg) { 
            product.setBuildC(msg);
            return this;
        } 
    
        @Override
        Builder builderD(String msg) { 
            product.setBuildD(msg);
            return this;
        } 
    
        @Override
        Product getProduct() { 
            return product;
        } 
    } 
  4. 客户

    public class Customer { 
        public static void main(String[] args) { 
            // 建造者(服务员)
            Builder builder = new Worker();
            // 返回一个默认套餐
            Product product = builder.getProduct();
            System.out.println(product);
            // 客户自定义套餐,链式编程
            Product product1 = builder.builderA("鸡米花").builderB("鸡肉卷").getProduct();
            System.out.println(product1);
        } 
    } 

    image-20210708123511048

应用场景:

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性
  • 隔离复杂对象创建和使用,并使得相同的创建过程可以创建不同的产品
  • 适合于一个具有较多的零件(属性)的产品(对象)的创建过程

8、原型模式

克隆

Prototype

Cloneable 接口

clone() 方法

步骤:

  1. 实现一个接口 cloneable
  2. 重写一个方法 clone()

代码实现(浅拷贝)

  1. 创建原型类

    // 原来的一个类,需要实现Cloneable接口
    // 重写克隆方法
    public class proto implements Cloneable{ 
        private String name;
        private Date greateTime;
    
        @Override
        protected Object clone() throws CloneNotSupportedException { 
            return super.clone();
        } 
    
        public proto() { 
    
        } 
    
        public proto(String name, Date greateTime) { 
            this.name = name;
            this.greateTime = greateTime;
        } 
    
        public String getName() { 
            return name;
        } 
    
        public void setName(String name) { 
            this.name = name;
        } 
    
        public Date getGreateTime() { 
            return greateTime;
        } 
    
        public void setGreateTime(Date greateTime) { 
            this.greateTime = greateTime;
        } 
    
        @Override
        public String toString() { 
            return "proto{ " +
                    "name='" + name + '\'' +
                    ", greateTime=" + greateTime +
                    '} ';
        } 
    } 
  2. 进行拷贝

    public class Test { 
        public static void main(String[] args) throws CloneNotSupportedException { 
            // 创建一个原型
            Date date = new Date();
            Proto proto = new Proto("原型1", date);
            System.out.println(proto.toString());
            System.out.println(proto.hashCode());
            // 克隆
            Proto clone = (Proto) proto.clone();
            System.out.println(clone.toString());
            System.out.println(clone.hashCode());
        } 
    } 

    image-20210709112400743

    此时可以发现这是两个对象,并且通过浅拷贝的方式创建了第二个对象

  3. 如果修改了时间,则会发现这两个对象均发生变化,所以指向的是同一个时间对象

    public class Test { 
        public static void main(String[] args) throws CloneNotSupportedException { 
            // 创建一个原型
            Date date = new Date();
            String name = "原型1";
            Proto proto = new Proto(name, date);
            // 克隆
            Proto clone = (Proto) proto.clone();
            // 修改时间的值
            date.setTime(8888888);
            // 输出原型的信息
            System.out.println(proto.toString());
            System.out.println(proto.hashCode());
            // 输出克隆的信息
            System.out.println(clone.toString());
            System.out.println(clone.hashCode());
        } 
    } 

    image-20210709112949744

深拷贝

  1. 修改clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException { 
        Proto clone = (Proto) super.clone();
        // 进行深拷贝
        clone.greateTime = (Date) this.greateTime.clone();
        return clone;
    } 
  2. 再次进行测试

    image-20210709114106541

9、适配器模式

属于结构型模式

  • 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题

类适配器

  1. 创建source类(要被适配的类)

    // 网线
    public class Source { 
        // 定义一个上网的方法
        public void request() { 
            System.out.println("网线连接上网");
        } 
    } 
  2. 创建适配器接口

    // 类适配器接口
    public interface Targetable { 
        public void connect();
    } 
  3. 创建适配实现类

    // 类适配器,实现接口并连接网线
    public class Adapter extends Source implements Targetable { 
        @Override
        public void connect() { 
            super.request();
        } 
    } 
  4. 创建使用对象

    // 电脑,使用适配器连接网络
    public class Computer { 
        // 上网的具体实现,需要一个适配器
        public void net(Targetable targetable) { 
            targetable.connect();
        } 
    } 
  5. 使用测试

    public static void main(String[] args) { 
        // 创建一个电脑
        Computer computer = new Computer();
        // 创建一个网线
        Source source = new Source();
        // 创建适配器
        Targetable adpter = new Adapter();
        // 连接网络
        computer.net(adpter);
    } 

    image-20210709225027389

对象适配器

  1. 只需要修改适配器实现类即可,把继承改为引入

    // 对象适配器(通过引入Source对象)
    public class Adapter implements Targetable { 
        private Source source;
    
        public Adapter(Source source) { 
            this.source = source;
        } 
    
        @Override
        public void connect() { 
            source.request();
        } 
    } 
  2. 修改测试方法

    public static void main(String[] args) { 
        // 创建一个电脑
        Computer computer = new Computer();
        // 创建一个网线
        Source source = new Source();
        // 创建适配器
        Targetable adpter = new Adapter(source);
        // 连接网络
        computer.net(adpter);
    } 

    这样就可以连接不同的网线了

image-20210709225649644

10、桥接模式Bridge

桥接模式是将抽象部分与它的实现部分分离,使得他们都可以独立地变化。它是一种对象结构型模式。

实现 品牌 和 类型 的联系。并且使用 组合 的方式代替 继承。

按照之前的方式,则会造成多层继承关系,以及需要创建出多个类。

image-20210709231251108

使用桥接模式之后,可以通过组合的方法,将如下类进行拼接

image-20210709231601702

代码实现

  1. 创建一个品牌接口

    // 品牌接口
    public interface Brand { 
        void info();
    } 
  2. 创建品牌实现类

    // 苹果品牌
    public class Apple implements Brand { 
        @Override
        public void info() { 
            System.out.print("苹果");
        } 
    } 
    
    // 联想品牌
    public class Lenovo implements Brand { 
        @Override
        public void info() { 
            System.out.print("联想");
        } 
    } 
  3. 创建电脑抽象类

    // 抽象电脑类型
    public abstract class Computer { 
        // 引入品牌,使用protected类型,可以被字类继承使用
        protected Brand brand;
    
        public Computer(Brand brand) { 
            this.brand = brand;
        } 
    
        public void info() { 
            brand.info();
        } 
    } 
  4. 创建不同电脑类型

    // 笔记本电脑
    class Laptop extends Computer { 
    
        public Laptop(Brand brand) { 
            super(brand);
        } 
    
        @Override
        public void info() { 
            super.info();
            System.out.println("笔记本");
        } 
    } 
    
    // 台式电脑
    class Desktop extends Computer { 
    
        public Desktop(Brand brand) { 
            super(brand);
        } 
    
        @Override
        public void info() { 
            super.info();
            System.out.println("台式电脑");
        } 
    } 
  5. 进行组合使用

    public static void main(String[] args) { 
        // 联想品牌
        Brand lenovo = new Lenovo();
        // 苹果品牌
        Brand apple = new Apple();
    
        // 联想台式电脑
        Desktop desktop = new Desktop(lenovo);
        desktop.info();
    
        // 苹果笔记本电脑
        Laptop laptop = new Laptop(apple);
        laptop.info();
    } 

    image-20210709233220690

    通过桥接模式,就不需要为每种品牌创建对应的产品,而可以通过组合的方式,生成不同品牌的电脑。

好处分析

  • 桥接模式类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来

劣势分析

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性

场景

  • Java语言通过Java虚拟机实现了平台的无关性(程序 和 系统)
  • AWT中的Peer架构
  • JDBC驱动程序也是桥接模式的应用之一(JDBC 连接 Mysql、Oracle)