设计模式【java提高】

前言

暑假持续学习中ing

推荐

【【狂神说Java】通俗易懂的23种设计模式教学(停更)-哔哩哔哩】
【狂神说笔记——23种设计模式】

设计模式

只有七种

简介

什么是设计模式

  • 设计模式 是前辈们对代码开发经验的总结, 是解决特定问题的一系列套路, 他不是语法规定, 而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
  • 1995年,GoF合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称[GoF设计模式]

在这里插入图片描述

学习设计模式的意义

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

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

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

设计模式的基本要素

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

GoF 23

  • Gof23
    • 一种思维, 一种态度, 一种进步
  • 创建型模式:
    • 单例模式, 工厂模式, 抽象工厂模式, 建造者模式, 原型模式
  • 结构型模式:
    • 适配器模式, 桥接模式, 装饰模式, 组合模式, 外观模式, 享元模式, 代理模式
  • 行为型模式:
    • 模板方法模式, 命令模式, 迭代器模式, 观察者模式, 中介者模式, 备忘录模式, 解释器模式, 状态模式, 策略模式, 职责链模式, 访问者模式

OOP 七大原则

  • 开闭原则: 对扩展开放, 对修改关闭
  • 里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立
  • 依赖倒置原则: 要面向接口编程, 不要面向实现编程
  • 单一职责原则: 控制类的粒度大小, 将对象解耦, 提高其内聚性
  • 接口隔离原则: 要为各个类建立它们需要的专用接口
  • 迪米特法则: 只与你的直接朋友交谈,不跟"陌生人"说话
  • 合成复用原则: 尽量先使用组合或者聚合等关联关系实现,其次才考虑使用继承关系来实现

1、单例模式

简介

JUC并发编程【java提高】中详细的单例模式

饿汉式、DCL懒汉式

说到volatile的防止指令重排,那么volatile的内存屏障在哪里使用的最多,就是单例模式了。

饿汉式

1)饿汉式

饿汉式的问题:可能会浪费内存
饿汉式一上来就会把所有的东西加载到内存,对象就已经存在了,对象没有使用的话,可能会浪费内存

扫描二维码关注公众号,回复: 14446135 查看本文章

主要特点有:

构造函数私有,避免在外部被创建对象
提前创建好对象
提供公有的接口,返回在类内部提前创建好的对象
静态变量随着类的加载就已经实例化了,跟是否调用静态方法没有关系
饿汉式加载时就会初始化,懒汉式只有在获取单例的时候才会初始化
类加载时,成员变量会被初始化,局部变量不会

package com.kuang.single;

//饿汉式单例
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 Hungry(){
    
    

    }
    private final static Hungry HUNGRY=new Hungry();

    public static Hungry getInstance(){
    
    
        return HUNGRY;
    }
}

DCL懒汉式

2)DCL懒汉式

针对饿汉式单例的浪费内存的问题,提出了懒汉式单例,要用的时候再创建对象

package com.kuang.single;

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

    }
    private static LazyMan lazyman;//还没有创建对象,只是声明,没有new

    public static LazyMan getInstance(){
    
    
      if(lazyman==null)
       {
    
    
         lazyman=new LazyMan();//如果这个对象为空,就实例化这个对象
       }
      return lazyman;
    }
}


在多个线程的情况下,懒汉式单例可能会出现安全问题,就是线程1进入了if判断,并开始构造对象

package com.kuang.single;

//懒汉式单例
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) {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    
                LazyMan.getInstance();
            }).start();
        }
    }
}

可以看到,有2个线程调用了构造函数,这说明程序中现在有2个Lazyman对象,就不是单例了,所以不安全
在这里插入图片描述
双重锁机制

package com.kuang.single;

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

    private static LazyMan lazyMan;

    //双重锁机制 DCL懒汉式
    public static LazyMan getInstance(){
    
    
        if (lazyMan==null){
    
    
            synchronized (LazyMan.class){
    
    
                if (lazyMan==null){
    
    
                    lazyMan=new LazyMan();
                }
            }
        }
        return lazyMan;
    }

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

可以看到只创建了一个对象
在这里插入图片描述
但是还是有可能出现问题

创建对象的过程在极端情况下肯定是会出现问题的,因为不是原子性操作,会经历
1、分配内存空间,
2、执行构造方法(初始化对象)
3、把对象指向分配的空间

但是可能会发生指令重排,可能会按132的顺序执行,就是先分配内存空间,然后用空对象先占用内存空间,占用之后再执行构造方法

如下图,很有可能A执行了13还没执行2,但是现在lazyman已经不是null了,如果现在进来一个B线程,外层判断不为空,那么B线程会直接返回lazyman,但lazyman实际上还没有完成构造,所以不安全(new只是把应用加上了,但是堆还没有创建完,return就会有问题)
在这里插入图片描述
所以要用volatile修饰防止指令重排(防止第二个线程抢先执行,抢先返回一个尚未初始化完成的引用)
所以这里是同步代码块保证了操作的原子性,volatile禁止了指令重排
指令重排的原理是为了提升CPU多段流水的效率,但并不是指令任意重排,处理器必须能正确处理指令依赖关系保障程序得出正确的执行结果。

总结:synchronized保证的是if判断和new一个对象能同时成功或同时失败,但是new一个对象不是原子操作,执行13后,第二个线程认为已经new对象成功了,最上面的if判断不等于null

静态内部类

3)静态内部类

在一个类里面写一个静态的类
首先只要单例一定要先构造器私有
加载外部类时,不会加载静态内部类
线程安全且懒加载
但是静态内部类单例也是不安全的,因为反射可以破坏单例

//静态内部类
public class Holder {
    
    
    private Holder(){
    
    

    }
    public static Holder getInstance(){
    
    
        return InnerClass.holder;
    }
    public static class InnerClass{
    
    
        private static final Holder holder = new Holder();
    }
}

测试:

package com.kuang.single;

//静态内部类
public class Holder {
    
    
    private Holder(){
    
    
        System.out.println(Thread.currentThread().getName()+"OK");
    }
    public static Holder getInstance(){
    
    
        return InnerClass.HOLDER;
    }
    public static class InnerClass{
    
    
        private static final Holder HOLDER=new Holder();
    }

    public static void main(String[] args) {
    
    
        for(int i=0;i<10;i++)
        {
    
    
            new Thread(()->{
    
    
                Holder.getInstance();
            }).start();
        }
    }

}

可以看到,内存中只有一个实例,就是只有一个线程进入了构造函数,因为静态类只加载一次

在这里插入图片描述

单例不安全

单例不安全,因为反射

但是只要有反射,任何私有的都是纸老虎,我们以DCL的单例为例,来试试反射

	//反射!
    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.hashCode());
        System.out.println(instance2.hashCode());
    }

结果:
在这里插入图片描述
可以破解:

 	private  LazyMan(){
    
    
        synchronized (LazyMan.class){
    
    
            if (lazyMan!=null){
    
    
                throw new RuntimeException("不用试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }

结果:
在这里插入图片描述
相当于在DCL的基础上又在构造函数里面加了一重检测
完整代码如下:

package com.kuang.single;

import java.lang.reflect.Constructor;

//懒汉式单例
public class LazyMan {
    
    
    private  LazyMan(){
    
    
        synchronized (LazyMan.class){
    
    
            if (lazyMan!=null){
    
    
                throw new RuntimeException("不用试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan;

    //双重锁机制 DCL懒汉式
    public static LazyMan getInstance(){
    
    
        if (lazyMan==null){
    
    
            synchronized (LazyMan.class){
    
    
                if (lazyMan==null){
    
    
                    lazyMan=new LazyMan();
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 A
                     * B
                     */
                }
            }
        }
        return lazyMan;
    }

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

    //反射!
    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.hashCode());
        System.out.println(instance2.hashCode());
    }


}

现在我们不用getInstance()去获取对象,而是直接通过反射创建两个对象

	 //反射!
    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();
        LazyMan instance = declaredConstructor.newInstance();

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

可以发现,单例又被破坏了,
因为构造函数里面判断的是
在这里插入图片描述
但是注意,我们用反射new 的对象跟类里面的lazyman对象肯定是不一样的啊,没有调用getInstance(),类里面的lazyman就一直为空,所以单例又被破坏了
在这里插入图片描述
解决方法,用个标志位

 private static boolean flag=false;

    private  LazyMan(){
    
    
        synchronized (LazyMan.class){
    
    
            if (flag==false){
    
    
                flag=true;
            }else {
    
    
                throw new RuntimeException("不用试图使用反射破坏异常");
            }
//            if (lazyMan!=null){
    
    
//                throw new RuntimeException("不用试图使用反射破坏异常");
//            }
        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }

在这里插入图片描述
但是,再牛逼的加密也会解密
来我们继续破坏单例,我们把这个flag字段给它破坏了

//反射!
    public static void main(String[] args) throws Exception {
    
    
//        LazyMan instance = LazyMan.getInstance();

        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();
        
        flag.set(instance,false);

        LazyMan instance2 = declaredConstructor.newInstance();

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

结果:可以发现单例又被破坏了
在这里插入图片描述

完整代码:

package com.kuang.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;


//懒汉式单例
public class LazyMan {
    
    

    private static boolean flag=false;

    private  LazyMan(){
    
    
        synchronized (LazyMan.class){
    
    
            if (flag==false){
    
    
                flag=true;
            }else {
    
    
                throw new RuntimeException("不用试图使用反射破坏异常");
            }
//            if (lazyMan!=null){
    
    
//                throw new RuntimeException("不用试图使用反射破坏异常");
//            }
        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan;

    //双重锁机制 DCL懒汉式
    public static LazyMan getInstance(){
    
    
        if (lazyMan==null){
    
    
            synchronized (LazyMan.class){
    
    
                if (lazyMan==null){
    
    
                    lazyMan=new LazyMan();
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 A
                     * B
                     */
                }
            }
        }
        return lazyMan;
    }

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

    //反射!
    public static void main(String[] args) throws Exception {
    
    
//        LazyMan instance = LazyMan.getInstance();

        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();

        flag.set(instance,false);

        LazyMan instance2 = declaredConstructor.newInstance();

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


}

那怎么解决呢?我们点进去反射的newInstance()看看呢

我们可以看到,如果类是一个枚举类型的话,就会告诉你不能使用反射破坏枚举,枚举是jdk 1.5 开始出现的,自带单例模式

在这里插入图片描述

枚举单例

4)枚举

枚举本身也是一个类

package com.kuang.single;

//enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
    
    
    INSTANCE;
    public static EnumSingle getInstance()
    {
    
    
        return INSTANCE;
    }
}
class Test{
    
    
    public static void main(String[] args) {
    
    
        EnumSingle  instance1=EnumSingle.INSTANCE;
        EnumSingle  instance2=EnumSingle.INSTANCE;
        EnumSingle  instance3=EnumSingle.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);
    }
}


在这里插入图片描述
我们来试试用反射破坏枚举单例

在这里插入图片描述

package com.kuang.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
    
    
    INSTANCE;
    public static EnumSingle getInstance()
    {
    
    
        return INSTANCE;
    }
}
class Test{
    
    
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        EnumSingle  instance1=EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}


下面的错误提示是枚举类没有空参的构造方法
也就是下面这句话出错了idea骗了我们

  		//NoSuchMethodException: com.kuang.single.EnumSingle.<init>()
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);

正常破坏单例是应该报错不能使用反射破坏枚举
在这里插入图片描述

通过反编译我们可以看到,这个枚举本身也是一个class,它继承了一个枚举类
然而构造器还是空参的啊,说明我们还是被骗了
在这里插入图片描述

现在我们用jad.exe反编译试试
下载地址
Java反编译工具Jad的使用
jad.exe复制到.class文件夹下

在这里插入图片描述

我们把class字节码生成java文件看看
在这里插入图片描述

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.kuang.single;


public final class EnumSingle extends Enum
{
    
    

    public static EnumSingle[] values()
    {
    
    
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
    
    
        return (EnumSingle)Enum.valueOf(com/kuang/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
    
    
        super(s, i);
    }

    public static EnumSingle getInstance()
    {
    
    
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
    
    
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
    
    
            INSTANCE
        });
    }
}

可以看到,不是无参构造器哦,而是有参构造器,有一个String,一个Int
在这里插入图片描述
现在我们修改反射代码

//        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);

在这里插入图片描述
完整代码:

package com.kuang.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
    
    
    INSTANCE;
    public static EnumSingle getInstance()
    {
    
    
        return INSTANCE;
    }
}
class Test{
    
    
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        EnumSingle  instance1=EnumSingle.INSTANCE;
        //NoSuchMethodException: com.kuang.single.EnumSingle.<init>()
//        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}


2、工厂模式

简介

  • 作用:

    • 实现了创建者和调用者的分离
    • 详细分类
      • 简单工厂模式
      • 工厂方法模式
      • 抽象工厂模式
  • OOP七大原则

    • 开闭原则: 一个软件的实体应当对扩展开放, 对修改关闭
    • 依赖倒转原则: 要针对接口编程, 不要针对实现编程
    • 迪米特法则: 只与你直接的朋友通信, 而避免和陌生人通信
  • 核心本质

  • 实例化对象不使用new, 用工厂方法代替

  • 将选择实现类, 创建对象统一管理和控制. 从而将调用者跟我们的实现类解耦

  • 三种模式:

  • 简单工厂模式

    • 用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码)
  • 工厂方法模式

    • 用来生产同一等级结构中的固定产品(支持增加任意产品)
  • 抽象工厂模式

    • 围绕一个超级工厂创建其他工厂, 该超级工厂又称为其他工厂的工厂

简单工厂模式

代码实现

factory/simple

Car

package factory.simple;

public interface Car {
    
    
    void name();
}

WuLing

package factory.simple;

public class Wuling implements Car{
    
    
    @Override
    public void name() {
    
    
        System.out.println("五菱宏光!");
    }
}

Tesla

package factory.simple;

public class Tesla implements Car{
    
    
    @Override
    public void name() {
    
    
        System.out.println("特斯拉!");
    }
}

原来的方法
还需要我们自己把车建出来

Consumer

package factory.simple;

public class Consumer {
    
    
    public static void main(String[] args) {
    
    
    	 //接口,所有的实现类!工厂
        Car car =new Wuling();
        Car car2 =new Tesla();

        car.name();
        car2.name();
    }
}

CarFactory

package factory.simple;

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;
        }

    }
}

现在的方法

Consumer

package factory.simple;

public class Consumer {
    
    
    public static void main(String[] args) {
    
    
        //接口,所有的实现类!工厂
//        Car car =new Wuling();
//        Car car2 =new Tesla();

        //2.使用工厂创建
        Car car = CarFactory.getCar("五菱");
        Car car2 = CarFactory.getCar("特斯拉");
        
        car.name();
        car2.name();
    }
}

但是
我们现在在加一个车

Dazhong

package factory.simple;

public class Dazhong implements Car{
    
    
    @Override
    public void name() {
    
    
        System.out.println("大众");
    }
}

我们就需要在工厂中在加一个if else语句来实现!!!
在这里插入图片描述
我们会想到方法二
在这里插入图片描述

	//方法二
    public static Car getWuling(){
    
    
        return new Wuling();
    }
    public static Car getTesla(){
    
    
        return new Tesla();
    }

但是,还是需要改变原有代码

所以,简单工厂模式还叫静态工厂模式

//静态工厂模式
//增加一个新的产品,如果你不修改代码,做不到!

这就有了工厂方法模式

图解

在这里插入图片描述

工厂方法模式

代码实现

factory/method

Car

package factory.method;

public interface Car {
    
    
    void name();
}

WuLing

package factory.method;


public class Wuling implements Car {
    
    
    @Override
    public void name() {
    
    
        System.out.println("五菱宏光!");
    }
}

Tesla

package factory.method;

public class Tesla implements Car {
    
    
    @Override
    public void name() {
    
    
        System.out.println("特斯拉!");
    }
}


把CarFactory也改成接口

CarFactory

package factory.method;

//工厂方法模式
public interface CarFactory {
    
    
    Car getCar();
}

对于每个车,自己都建造工厂

WulingFactory

package factory.method;

public class WulingFactory implements CarFactory{
    
    
    @Override
    public Car getCar() {
    
    
        return new Wuling();
    }
}

TeslaFactory

package factory.method;

public class TeslaFactory implements CarFactory{
    
    

    @Override
    public Car getCar() {
    
    
        return new Tesla();
    }
}

Consumer

package factory.method;

public class Consumer {
    
    
    public static void main(String[] args) {
    
    
        Car car = new WulingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();

        car.name();
        car2.name();
    }
}

这时候,我们如果有新车的话

MoBai

package factory.method;

public class MoBai implements Car{
    
    

    @Override
    public void name() {
    
    
        System.out.println("摩拜单车");
    }
}

这时候,我们只需要建个对应的工厂就好了

package factory.method;

public class MoBaiFactory implements CarFactory{
    
    

    @Override
    public Car getCar() {
    
    
        return new MoBai();
    }
}

Consumer

		Car car3 = new MoBaiFactory().getCar();
        car3.name();

但是,我们发现我们每多一个需求,就需要扩展一个工厂类
这样工厂类,就会越来越庞大
我们有了抽象工厂模式

图解

在这里插入图片描述

对比

		//结构复杂度: simple
        //代码复杂度: simple
        //编程复杂度: simple
        //管理上的复杂度: simple
        
        //根据设计原则:工厂方法模式!
        //根据实际业务:简单工厂模式!

3、抽象工厂模式

简介

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

  • 适用场景:

    • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
    • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
    • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
  • 优点:

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

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

图解

在这里插入图片描述
在这里插入图片描述

代码实现

factory/abstract1

两个产品接口

IphoneProduct

package factory.abstract1;

//手机产品接口
public interface IphoneProduct {
    
    
    void start();
    void shutdown();
    void callup();
    void sendSMS();
}

IRouterProduct

package factory.abstract1;

//路由器产品接口
public interface IRouterProduct {
    
    
    void start();
    void shutdown();
    void openWifi();
    void setting();
}


同一产品等级的两个工厂

XiaomiPhone

package factory.abstract1;

//小米手机
public class XiaomiPhone implements IPhoneProduct{
    
    
    @Override
    public void start() {
    
    
        System.out.println("小米开机");
    }

    @Override
    public void shutdown() {
    
    
        System.out.println("小米关机");
    }

    @Override
    public void callup() {
    
    
        System.out.println("小米打电话");
    }

    @Override
    public void sendSMS() {
    
    
        System.out.println("小米发信息");
    }

}

HuaweiPhone

package factory.abstract1;

//华为手机
public class HuaweiPhone implements IPhoneProduct{
    
    
    @Override
    public void start() {
    
    
        System.out.println("华为开机");
    }

    @Override
    public void shutdown() {
    
    
        System.out.println("华为关机");
    }

    @Override
    public void callup() {
    
    
        System.out.println("华为打电话");
    }

    @Override
    public void sendSMS() {
    
    
        System.out.println("华为发信息");
    }

}

小米产品族的路由器

XiaomiRouter

package factory.abstract1;

//小米路由器
public class XiaomiRouter implements IRouterProduct{
    
    
    @Override
    public void start() {
    
    
        System.out.println("小米开启路由器");
    }

    @Override
    public void shutdown() {
    
    
        System.out.println("小米关闭路由器");
    }

    @Override
    public void openWifi() {
    
    
        System.out.println("小米开启Wifi");
    }

    @Override
    public void setting() {
    
    
        System.out.println("小米设置路由器");
    }

}

华为产品族的路由器

package factory.abstract1;

//华为路由器
public class HuaweiRouter implements IRouterProduct{
    
    
    @Override
    public void start() {
    
    
        System.out.println("华为开启路由器");
    }

    @Override
    public void shutdown() {
    
    
        System.out.println("华为关闭路由器");
    }

    @Override
    public void openWifi() {
    
    
        System.out.println("华为开启Wifi");
    }

    @Override
    public void setting() {
    
    
        System.out.println("华为设置路由器");
    }

}

然后,我们需要工厂来建造产品

IProductFactory

抽象工厂:工厂的工厂
具体工厂需要实现它

package factory.abstract1;

//抽象产品工厂
public interface IProductFactory {
    
    
    //生产手机
    IPhoneProduct iPhoneProduct();
    
    //生产路由器
    IRouterProduct iRouterProduct();
}

XiaomiFactory

小米工厂

package factory.abstract1;

public class XiaomiFactory implements IProductFactory{
    
    

    @Override
    public IPhoneProduct iPhoneProduct() {
    
    
        return new XiaomiPhone();
    }

    @Override
    public IRouterProduct iRouterProduct() {
    
    
        return new XiaomiRouter();
    }
}

HuaweiFactory

华为工厂

package factory.abstract1;

public class HuaweiFactory implements IProductFactory{
    
    

    @Override
    public IPhoneProduct iPhoneProduct() {
    
    
        return new HuaweiPhone();
    }

    @Override
    public IRouterProduct iRouterProduct() {
    
    
        return new HuaweiRouter();
    }
}

Client

编写客户端测试

package factory.abstract1;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("=================小米系列产品==============");
        XiaomiFactory xiaomiFactory = new XiaomiFactory();
        IPhoneProduct iPhoneProduct = xiaomiFactory.iPhoneProduct();
        iPhoneProduct.start();

        IRouterProduct iRouterProduct = xiaomiFactory.iRouterProduct();
        iRouterProduct.start();

        System.out.println("=================华为系列产品==============");
        HuaweiFactory huaweiFactory = new HuaweiFactory();
        iPhoneProduct = huaweiFactory.iPhoneProduct();
        iPhoneProduct.start();

        iRouterProduct = huaweiFactory.iRouterProduct();
        iRouterProduct.start();
        
    	//=================小米系列产品==============
        //小米开机
        //小米开启路由器
        //=================华为系列产品==============
        //华为开机
        //华为开启路由器

	}
}

对应类图

在这里插入图片描述

如果现在加一个产品笔记本

需要改动抽象工厂及其所有子类

违反了开闭原则

如果没有改动,还是可以接受的

抽象工厂关注产品簇

小结

  • 简单工厂模式(静态工厂模式)

    • 虽然某种程度上不符合设计原则, 但实际使用最多
  • 工厂方法模式

    • 不修改已有类的前提下, 通过增加新的工厂类实现扩展
  • 抽象工厂模式

    • 不可以增加产品, 可以增加产品族
  • 应用场景

    • JDK中Calendar的getInstance方法
    • JDBC中的Connection对象的获取
    • Spring中IOC容器创建管理bean对象
    • 反射中Class对象的newstance方法

4、建造者模式

简介

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

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

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

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

举例说明: 设计房屋

  • 例子:

    • 工厂(建造者模式)︰负责制造汽车(组装过>程和细节在工厂内)
    • 汽车购买者(用户)∶你只需要说出你需要的>型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、车门、>发动机、方向盘等等)
  • 角色分析

在这里插入图片描述

  • 既然是建造者模式,那么我们还是继续造房吧,其实我也想不到更简单的例子。假设造房简化为如下步骤:(1)地基(2)钢筋工程(3)铺电线(4)粉刷;“如果”要盖一座房子,首先要找一个建筑公司或工程承包商(指挥者)。承包商指挥工人(具体建造者)过来造房子(产品),最后验收。

同中步骤可以造出来不同房子

代码实现

Product

产品:房子

package builder;

//产品:房子
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 + '\'' +
                '}';
    }
}

Builder

抽象的建造者

package builder;


//抽象的建造者:方法
public abstract class Builder {
    
    
    abstract void buildA();//地基
    abstract void buildB();//钢筋工程
    abstract void buildC();//铺电线
    abstract void buildD();//粉刷

    //完工:得到产品
    abstract Product getProduct();
}

Worker

具体的建造者:工人

注意:产品是工人创建的
在这里插入图片描述

package builder;


public class Worker extends Builder {
    
    

    private Product product;

    public Worker() {
    
    
        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;
    }



}

没有指挥工人

Director

指挥

package builder;

//指挥:核心,负责构建一个工程
//工程如何构建,有它绝决定
public class Director {
    
    
    //指挥工人按照顺序建房子
    public Product build(Builder builder){
    
    
        builder.buildA();
        builder.buildB();
        builder.buildC();
        builder.buildD();
        
        return builder.getProduct();
    }
}

Test

测试

package builder;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //指挥
        Director director=new Director();
        //指挥具体的工人完成产品
        Product build = director.build(new Worker());
        System.out.println(build.toString());
        
        //地基
        //钢筋工程
        //铺电线
        //粉刷
        //Product{buildA='地基', buildB='钢筋工程', buildC='铺电线', buildD='粉刷'}
    }
}

但是

  • 上面示例是 Builder模式的常规用法,导演类Director在 Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。
  • 通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
  • 比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。

代码再实现

builer/demo02

Product

package builder.demo02;

//产品:套餐
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 + '\'' +
                '}';
    }
}


Builder

不赋值,就是默认的4件套餐

package builder.demo02;

//建造者
public abstract class Builder {
    
    
    abstract Builder BuildA(String msg);//"汉堡";
    abstract Builder BuildB(String msg);//"可乐";
    abstract Builder BuildC(String msg);//"薯条";
    abstract Builder BuildD(String msg);//"甜点";

    abstract Product getProduct();

}

Worker

package builder.demo02;

//具体的建造者
public class Worker extends Builder{
    
    

    private Product product;

    public Worker() {
    
    
        product = new Product();
    }

    @Override
    Builder BuildA(String msg) {
    
    
        product.setBuildA(msg);
        return this;
    }

    @Override
    Builder BuildB(String msg) {
    
    
        product.setBuildB(msg);
        return this;
    }

    @Override
    Builder BuildC(String msg) {
    
    
        product.setBuildC(msg);
        return this;
    }

    @Override
    Builder BuildD(String msg) {
    
    
        product.setBuildD(msg);
        return this;
    }

    @Override
    Product getProduct() {
    
    
        return product;
    }

}

Test

package builder.demo02;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //服务员
        Worker worker=new Worker();
        //链式编程
//        Product product = worker.getProduct();
//        System.out.println(product.toString());
        //Product{BuildA='汉堡', BuildB='可乐', BuildC='薯条', BuildD='甜点'}

        //链式编程:在原来的基础上可以自由组合,如果不组合,也有默认的套餐
        Product product = worker.BuildA("全家桶").BuildB("雪碧").getProduct();
        System.out.println(product.toString());
        //Product{BuildA='全家桶', BuildB='雪碧', BuildC='薯条', BuildD='甜点'}
    }
}

优缺点

  • 优点:
    • 产品的建造和表示分离, 实现了解耦, 使用建造者模式可以使客户端不必知道产品内部组成的细节.
    • 将复杂产品的创建步骤分解在不同的方法中, 使得创建过程更加清晰
    • 具体的建造者类之间是相互独立的, 这有利于系统的扩展. 增加新的具体建造者无需修改原有的类库的代码, 符合"开闭原则"
  • 缺点:
    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似; 如果产品之间的差异性很大, 则不适合使用建造者模式, 因此其使用范围受到一定的限制
    • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类实现这种变化, 导致系统变得很庞大

应用场景以及与抽象工厂模式对比

  • 应用场景:
    • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
    • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
    • 适合于一个具有较多的零件〔属性)的产品(对象)的创建过程。
  • 建造者与抽象工厂模式的比较:
    • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
    • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
    • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!

建造者模式的意图和适用场景(拓展)

  • 模式意图
    将一个复杂的构件与其表示相分离,使得同样的构件过程可以创建不同的表示

  • 使用场景

    • 需要生成的产品对象有复杂的内部结构
    • 当一些基本部件不会变, 而其组合经常变化的时候
    • 需要生成的对象内部属性本身相互依赖

建造者的参与者(拓展)

  • Builder
    抽象建造者,给出一个抽象接口, 以规范产品对象的各个组成成分的建造. 这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建

  • ConcreteBuilder
    实现Builder接口, 针对不同的商业逻辑, 具体化复杂对象的各部分的创建, 即具体建造者

  • Director
    指挥者, 调用具体建造者来创建复杂对象的各个部分, 在指导者不涉及具体产品的信息, 只负责保证对象各部分完整创建或者按某种顺序创建

  • Product
    要创建的复杂对象, 即产品角色

总结 (拓展)

  • 建造者模式的使用使得产品的内部表象可独立的变化. 使用建造者模式可以使客户端不必知道产品内部组成的细节
  • 每一个Builder都相对独立, 而与其它的Builder无关
  • 可使得对构造过程更加精细控制
  • 将构建代码和表示代码分开
  • 建造者模式的缺点在于难于应付"分步骤构建算法"的需求变动

5、原型模式

  • 克隆

在这里插入图片描述

  • Prototype

  • Cloneable接口

  • clone()方法

代码实现

phototype/demo01

Video

package phototype.demo01;

import java.util.Date;


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

//Video:视频的类
public class Video implements Cloneable{
    
    //无良up主,克隆别人的视频
    private String name;
    private Date createTime;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    public Video() {
    
    

    }

    public Video(String name, Date createTime) {
    
    
        this.name = name;
        this.createTime = createTime;
    }

    public String getName() {
    
    
        return name;
    }

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

    public Date getCreateTime() {
    
    
        return createTime;
    }

    public void setCreateTime(Date createTime) {
    
    
        this.createTime = createTime;
    }

    @Override
    public String toString() {
    
    
        return "Video{" +
                "name='" + name + '\'' +
                ", createTime=" + createTime +
                '}';
    }

}

Bilibili

package phototype.demo01;

import java.util.Date;

/*
客户端:克隆
*/
public class Bilibili {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        //原型对象v1
        Date date = new Date();
        Video v1 = new Video("狂神说Java", date);
        System.out.println("v1=>" + v1);
        System.out.println("v1=>hash:" + v1.hashCode());

        //v1克隆v2
        //Video v2 = new Video( "狂神说Java", date);
        Video v2 = (Video) v1.clone();
        System.out.println("v2=>" + v2);
        System.out.println("v2=>hash:" + v2.hashCode());

        //v1=>Video{name='狂神说Java', createTime=Tue Jul 19 19:25:31 CST 2022}
        //v1=>hash:1735600054
        //v2=>Video{name='狂神说Java', createTime=Tue Jul 19 19:25:31 CST 2022}
        //v2=>hash:21685669
        
        v2.setName("Clone:狂神说Java");
        System.out.println("v2" + v2);
    }
}


v2=>v1
浅克隆

同时指向同一个date
在这里插入图片描述

测试

		System.out.println("==============");
        System.out.println("v1=>" + v1);
        System.out.println("v2=>" + v2);
        System.out.println("==============");
        date.setTime(22131231);
        System.out.println("v1=>" + v1);
        System.out.println("v2=>" + v2);

在这里插入图片描述

修改了v1的date,v2也改变其date了

我们想要v2也有自己的date属性
深克隆
一种实现方法时:改造克隆方法
另外还有:序列化、反序列化
在这里插入图片描述

代码再实现

复制代码到demo02

改造克隆方法

	@Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        Object obj = super.clone();
        //实现深克隆~     序列化, 反序列化
        Video v=(Video) obj;
         v.createTime = (Date) this.createTime.clone(); //将这个对象的属性也进行克隆
        
        return v;
    }

Video

package phototype.demo02;

import java.util.Date;


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

//Video:视频的类
public class Video implements Cloneable{
    
    //无良up主,克隆别人的视频
    private String name;
    private Date createTime;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        Object obj = super.clone();
        //实现深克隆~     序列化, 反序列化
        Video v=(Video) obj;
         v.createTime = (Date) this.createTime.clone(); //将这个对象的属性也进行克隆

        return v;
    }

    public Video() {
    
    

    }

    public Video(String name, Date createTime) {
    
    
        this.name = name;
        this.createTime = createTime;
    }

    public String getName() {
    
    
        return name;
    }

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

    public Date getCreateTime() {
    
    
        return createTime;
    }

    public void setCreateTime(Date createTime) {
    
    
        this.createTime = createTime;
    }

    @Override
    public String toString() {
    
    
        return "Video{" +
                "name='" + name + '\'' +
                ", createTime=" + createTime +
                '}';
    }

}

Bilibili

测试

package phototype.demo02;

import java.util.Date;


//Spring Bean :单例模式  原型模式
//原型模式+工厂模式==> new <=>原型模式

/*
客户端:克隆
*/
public class Bilibili {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        //原型对象v1
        Date date = new Date();
        Video v1 = new Video("狂神说Java", date);
        //v1克隆v2
        Video v2 = (Video) v1.clone();

        System.out.println("v1=>" + v1);
        System.out.println("v2=>" + v2);

        System.out.println("==============");
        date.setTime(22131231);



        System.out.println("v1=>" + v1);
        System.out.println("v2=>" + v2);

    }
}


在这里插入图片描述

2022/7/19 20:00:00
p1~p7


2022/7/20 09:00:00

6、适配者模式

结构性模式:

  • 作用

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

在这里插入图片描述

  • 分类

    • 适配器模式
    • 代理模式
    • 桥接模式
    • 装饰模式
    • 组合模式
    • 外观模式
    • 享元模式

简介

适配器模式就像USB网线转换器

在这里插入图片描述

代码实现

Adaptee

package adapter;

//要被适配的类:网线
public class Adaptee {
    
    
    public void request() {
    
    
        System.out.println("连接网线上网");
    }
}

面向接口编程
抽象的适配器

NetToUsb

package adapter;

//接口转换器的抽象实现~
public interface NetToUsb {
    
    
    //作用:处理请求,网线=>usb
    public void handleRequest();
}

具体的适配器

使用继承来实现的

Adapter

package adapter;

//1.继承(类适配器,单继承)
//真正的适配器,需要连接USB,连接网线~
public class Adapter extends Adaptee implements NetToUsb {
    
    
    @Override
    public void handleRequest() {
    
    
        //上网的具体实现,找一个转接头
        super.request();
    }
}

Computer

package adapter;


//客户端类: 想上网,插不网线~
public class Computer {
    
    
    //我们的电脑需要连接:找一个转换器才可以上网
    public void net(NetToUsb adapter) {
    
    
        //上网的具体实现, 找个转换器
        adapter.handleRequest();
    }

    public static void main(String[] args) {
    
    
        //Adapter继承关系实现
       //电脑,适配器,网线~
        Computer computer = new Computer(); //电脑
        Adaptee adaptee = new Adaptee(); //网线
        Adapter adapter = new Adapter(); //转按器
        computer.net(adapter);
    }
}

代码再实现

具体的适配器

使用组合来实现的

Adapter2

package adapter;

//2.组合(对象适配器,常用)

//真正的适配器,需要连接USB,连接网线~
public class Adapter2 implements NetToUsb{
    
    
    //组合模式
    private Adaptee adaptee;

    public Adapter2(Adaptee adaptee) {
    
    
        this.adaptee = adaptee;
    }
    @Override
    public void handleRequest() {
    
    
        //上网的具体实现,找一个转接头
        adaptee.request();//可以上网
    }

}

Computer.main()

 System.out.println("++++++++++++++++++++++++++++++");

        //电脑,适配器,网线~
        Computer computer = new Computer(); //电脑
        Adaptee adaptee = new Adaptee(); //网线
        Adapter2 adapter = new Adapter2(adaptee); //转换器

        computer.net(adapter);

再理解

再理解

  • 将一个类的接口转换成客户希望的另外一个接口. Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
  • 角色分析
    • 目标接口: 客户所期待的接口, 目标可以是具体的或抽象的类, 可以是接口
    • 需要适配的类: 需要适配的类或适配者类
    • 适配器: 通过包装一个需要适配的对象, 把原接口转换成目标对象

图解

在这里插入图片描述

又理解

  • 对象适配器优点
    • 一个对象适配器可以把多个不同的适配者适配到同一个目标
    • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据"里氏代换原则", 适配者的子类也可通过该适配器进行适配
  • 类适配器缺点
    • 对于Java, C#等不支持多重类继承的语言, 一次最多只能适配一个适配者类,不能同时适配多个适配者,
    • 在Java, C#等语言中, 类适配器模式中的目标抽象类只能为接口, 不能为类, 其使用有一定的局限性
  • 适用场景
    • 系统需要使用一些现有的类, 而这些类的接口(如方法名)不符合系统的需求,甚至没有这些类的源代码
    • 想创建一个可以重复使用的类, 用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作

7、桥接模式

简介

  • 桥接模式是将抽象部分与它的实现部分分离, 使他们都可以独立地变化, 它是一种对象结构型模式, 又称为柄体(Handle and Body)模式或接口(Interface)模式

单一职责原则

在这里插入图片描述

  • 分析:这个场景中有两个变化的维度: 品牌, 类型

在这里插入图片描述

  • 与抽象工厂模式对比

    • 可以处理多种维度扩展的情况

    • 结构型模式

代码实现

抽象的品牌

Brand

package bridge;

//品牌
public interface Brand {
    
    

    void info();
}

具体的品牌

Lenovo

package bridge;

//联想品牌
public class Lenovo implements Brand {
    
    
    @Override
    public void info() {
    
    
        System.out.print("联想");
    }
}

Apple

package bridge;

//苹果品牌
public class Apple implements Brand {
    
    
    @Override
    public void info() {
    
    
        System.out.print("苹果");
    }
}

Computer

package bridge;

//抽象的电脑类型类
public abstract class Computer {
    
    

    //组合,品牌~  桥
    protected Brand brand;

    public Computer(Brand brand) {
    
    
        this.brand = brand;
    }

    public void info() {
    
    
        brand.info();//自带品牌
    }


}

class Desktop extends Computer {
    
    
    public Desktop(Brand brand) {
    
    
        super(brand);
    }

    @Override
    public void info() {
    
    
        super.info();
        System.out.println("台式机");
    }
}

class Laptop extends Computer {
    
    
    public Laptop(Brand brand) {
    
    
        super(brand);
    }

    @Override
    public void info() {
    
    
        super.info();
        System.out.println("笔记本");
    }
}

Test

package bridge;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        //苹果管记本
        Computer computer = new Laptop(new Apple());
        computer.info();

        System.out.println("========");
        //联想台式机
        Computer computer2 = new Desktop(new Lenovo());
        computer2.info();

		//苹果笔记本
        //=======
        //联想台式机
    }
}


理解

图解

在这里插入图片描述

优劣势分析

  • 好处分析:
    • 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
    • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
  • 劣势分析:
    • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
    • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性.
  • 最佳实践:
    • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
    • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
    • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  • 场景:
    • Java语言通过Java虚拟机实现了平台的无关性。
    • AWT中的Peer架构
    • JDBC驱动程序也是桥接模式的应用之一。

8、代理模式

简介

是SpringAOP的底层 [SpringAOP和SpringMVC]

  • 代理模式的分类

    • 静态代理
    • 动态代理

中介
在这里插入图片描述

角色分析:

  • 抽象角色: 一般会使用接口或抽象类来解决
  • 真实角色: 被代理的角色
  • 代理角色: 代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户: 访问代理对象的人

静态代理

代码实现

proxy/demo01

抽象角色

Rent

package proxy.demo01;

//抽象角色:租房
public interface Rent {
    
    
    public void rent();
}

真实角色

Host

package proxy.demo01;

//房东
public class Host implements Rent{
    
    

    @Override
    public void rent() {
    
    
        System.out.println("房东要出租房子");
    }
}

代理角色

Proxy

package proxy.demo01;

//代理
public class Proxy implements Rent{
    
    

    private Host host;

    public Proxy(){
    
    

    }

    public Proxy(Host host) {
    
    
        this.host = host;
    }

    @Override
    public void rent() {
    
    
        host.rent();
        seeHouse();
        hetong();
        fare();
    }

    //看房
    public void seeHouse(){
    
    
        System.out.println("中介带你看房");
    }

    //收中介费
    public void fare(){
    
    
        System.out.println("收中介费");
    }

    //签合同
    public void hetong(){
    
    
        System.out.println("签合同");
    }
}


Client

package proxy.demo01;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //房东要租房子
        Host host = new Host();
//        host.rent();

        //代理,中介帮房东租房子,但是 代理一般会有一些附属操作
        Proxy proxy = new Proxy(host);

        //不用面对房东,直接找中介租房即可
        proxy.rent();
        
        //房东要出租房子
        //中介带你看房
        //签合同
        //收中介费
    }

}

理解

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共也就交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低

加深理解

理解

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

聊聊AOP:纵向开发,横向开发。

图解

在这里插入图片描述

代码实现

proxy/demo02

  1. 创建一个抽象角色

UserService

package proxy.demo02;

//抽象角色:增删改查业务
public interface UserService {
    
    
    void add();
    void delete();
    void update();
    void query();
}


  1. 我们需要一个真实对象来完成这些增删改查操作

UserServiceImpl

package proxy.demo02;

//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
    
    

    public void add() {
    
    
        System.out.println("增加了一个用户");
    }

    public void delete() {
    
    
        System.out.println("删除了一个用户");
    }

    public void update() {
    
    
        System.out.println("更新了一个用户");
    }

    public void query() {
    
    
        System.out.println("查询了一个用户");
    }
}


  1. 需求来了,现在我们需要增加一个日志功能,怎么实现!
  • 思路1 :在实现类上增加代码 【麻烦!】

  • 在这里插入图片描述

  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

  1. 设置一个代理类来处理日志, 代理角色

UserServiceProxy

package proxy.demo02;

//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
    
    
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
    
    
        this.userService = userService;
    }

    public void add() {
    
    
        log("add");
        userService.add();
    }

    public void delete() {
    
    
        log("delete");
        userService.delete();
    }

    public void update() {
    
    
        log("update");
        userService.update();
    }

    public void query() {
    
    
        log("query");
        userService.query();
    }

    public void log(String msg){
    
    
        System.out.println("执行了"+msg+"方法");
    }

}


  1. 测试访问类:

Client

package proxy.demo02;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //真实业务
        UserServiceImpl userService = new UserServiceImpl();
//        userService.add();

        //代理类
        UserServiceProxy proxy = new UserServiceProxy();
        //使用代理类实现日志功能!
        proxy.setUserService(userService);

        proxy.add();
    }

}

动态代理

简介

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类 : 基于接口的动态代理,基于类的动态代理
    • 基于接口-JDK动态代理
    • 基于类: cglib
    • java字节码实现 : javassist

需要了解的两个类: Proxy: 代理, InvocationHandler: 调用处理程序

Proxy

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

InvocationHandler(调用处理程序)

在这里插入图片描述

代码实现

Rent

package proxy.demo03;

//抽象角色:租房
public interface Rent {
    
    
    public void rent();
}

Host

package proxy.demo03;


//被代理角色:房东
public class Host implements Rent {
    
    

    @Override
    public void rent() {
    
    
        System.out.println("房东要出租房子");
    }
}

ProxyInvocationHandler

package proxy.demo03;

//ProxyInvocationHandler. java 即代理角色

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
    
    

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
    
    
        this.rent = rent;
    }

    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    public Object getProxy(){
    
    
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }

    // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
    // 处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
    
        seeHouse();
        //核心:本质利用反射实现!
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    //看房
    public void seeHouse(){
    
    
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
    
    
        System.out.println("收中介费");
    }

}


Client

package proxy.demo03;

//租客
public class Client {
    
    

    public static void main(String[] args) {
    
    
        //真实角色
        Host host = new Host();
        //代理实例的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //将真实角色放置进去!
        pih.setRent(host);
        //动态生成对应的代理类!
        Rent proxy = (Rent)pih.getProxy();
        //执行方法
        proxy.rent();
        
        //带房客看房
        //房东要出租房子
        //收中介费
        
        
    }

}


动态代理再理解

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

ProxyInvocationHandler

package proxy.demo04;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
    
    
    //被代理的接口
    private Object target;

    public void setTarget(Object target) {
    
    
        this.target = target;
    }

    //生成得到代理对象
    public Object getProxy(){
    
    
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // proxy : 代理类
    // method : 代理类的调用处理程序的方法对象.
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException  {
    
    
        log(method.getName());
        //动态代理的本质,就是使用反射机制实现
        Object result = method.invoke(target, args);
        return result;
    }

    public void log(String methodName){
    
    
        System.out.println("执行了"+methodName+"方法");
    }

}

Client

package proxy.demo04;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        //代理角色,不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        pih.setTarget(userService); //设置要代理的对象
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();

        proxy.add();

		//执行了add方法
        //增加了一个用户
    }
}

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

2022/7/22 11:41:01 星期三


最后

Markdown 40419 字数 2848 行数
HTML 35124 字数 1863 段落

猜你喜欢

转载自blog.csdn.net/qq_51625007/article/details/125872560