常用设计模式(Java)

设计模式引入

由于b站狂神设计模式是用ppt讲解的,没有视频笔记,因此自己总结了视频的笔记,
b站视频“通俗易懂的23种设计模式教学”,侵删

GitHub代码传送门

1. 什么是设计模式

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

image-20210121153232777

2. 学习设计模式的意义

  • 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的的充分理解。
  • 正确使用设计模式具有以下优点:
    • 可以提高程序员的思维能力、编程能力和设计能力。
    • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
    • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

3. 设计模式的基本要素

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

GoF23

  • GoF23

    • 是一种思维,一种态度,一种进步
  • 创建型模式:

    • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型模式:(从程序的结构上实现松耦合,从而扩大整体的类结构,用来解决更大的问题)

    • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  • 行为型模式:

    • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

4. OOP七大原则

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

1、单例模式

意图:保证一个类仅有一个实例,并提供一个访问他的全局访问点。

主要解决:一个全局使用的类频繁的创建与销毁。

何时使用:当您想控制实例数目节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

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 Hungry(){
        
    }
    
    private final static Hungry HUNGRY = new Hungry();
    
    public static Hungry getInstance(){
        return HUNGRY;
    }
    
}

饿汉式单例由于new一个对象时不是一个原子性操作,即 new一个对象时, java内部会进行更细致的线程操作,则导致在多线程的条件下会不安全

2. 懒汉式单例

//懒汉式单例
public class LazyMan{
    private LazyMan(){
        
    }
    
    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	此时lazyMan还没有完成构造
                    **/
            
        		}
            }
        }
        
        return lazyMan;
    }
    
    //多线程下不安全
    //多线程并发
    public static void main(String[] args){
        for(int i = 0; i < 10; i++){
            new Thread(()->{
                lazyMan.getInstance();
            }).start();
        }
    }
}

3. 内部类实现单例

//静态内部类实现
public class Holder{
    public static class InnerClass{
        
    }
    public static Holder getInstance(){
		return InnerClass.HOLDER;
    }
    public static class InnerClass{
        private static final Holder HOLDER=new Holder();
    }
}

4. 反射会破坏单例

import java.lang.reflect.Constructor;

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

    }

    private volatile 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();
    //     }
    // }

    //反射
    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-20210124102549325

5. 道高一尺魔高一丈

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

//懒汉式单例
public class LazyMan{
    private static boolean qinjiang=false;
    private LazyMan(){
        synchronized (LazyMan.class){
            if(qinjiang==false){
                qinjiang=true;
            }else{
                throw new RuntimeException("使用反射破坏单例异常");
            }
        }
    }

    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 static void main(String[] args) throws Exception {
        Field qinjiang_ = LazyMan.class.getDeclaredField("qinjiang");
        qinjiang_.setAccessible(true);

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

        qinjiang_.set(instance,false);

        LazyMan instance2=declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);

    }


}

运行结果:

image-20210124104243775

6. 枚举

枚举的单例不会被反射破坏

package 单例设计模式;

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

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;

    }



}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        EnumSingle instance1=EnumSingle.INSTANCE;
        // EnumSingle instance2=EnumSingle.INSTANCE;
        System.out.println(instance1);
        // System.out.println(instance2);
        
        //java欺骗我们EnumSingle的构造方法为无参构造
        //用jad反编译EnumSingle得到的构造方法为有参构造,参数为String与int
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle  instance2= declaredConstructor.newInstance();

        System.out.println(instance2);
    }
}

运行结果:

image-20210124210822264

2、工厂模式

  • 作用:

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

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

    • 简单工厂模式
      • 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
    • 工厂方法模式
      • 用来生产同一等级结构中的固定产品(支持增加任意产品)
    • 抽象工厂模式
      • 围绕一个超级工厂创建其他工厂。该超级工厂称为其他工厂的工厂。
  • OOP七大原则

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

1. 简单工厂模式(静态工厂)

某种程度上不符合设计原则,但实际使用最多。

原始方法

public interface Car {
    public void name();
}
public class Tesla implements Car{
    public void name(){
        System.out.println("特斯拉!");
    }
}
public class WuLing implements Car{
    public void name(){
        System.out.println("五菱宏光!");
    }
}
public class Consumer {
    public static void main(String[] args) {
        // 接口,所有实现类
        Car car=new WuLing();
        Car car2=new Tesla();
        car.name();
        car2.name();
        // Car car = CarFactory.getCar("五菱");
        // car.name();
    }
}

运行截图

image-20210203114241055

这种方法需要知道所有接口和实现类才能调用

静态工厂方法

因为CarFactory类中的所有方法都是静态的,需要向其中添加参数来返回不同的对象实例。

增加一个新的产品,如果不修改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;
        }
    }
    
    
    // 方法二
    public static Car getWuLing(){
        return new WuLing();
    }
    public static Car getTesla(){
        return new Tesla();
    }

}
public class Consumer {
    public static void main(String[] args) {
        // 接口,所有实现类
        // Car car=new WuLing();
        // Car car2=new Tesla();
        // car.name();
        // car2.name();

        Car car = CarFactory.getCar("五菱");
        Car car2 = CarFactory.getCar("特斯拉");
        car.name();
        car2.name();
    }
}

运行截图

image-20210203114142962

图形描述:

image-20210303125913177

代码分析:

  • 在这种工厂实现的情况下增加新的产品(类)实现起来会比较复杂。需要修改CarFactory类,因此违反了开闭原则。

  • 虽然方法二比方法一添加新的类不需要修改内部逻辑、更加简单,但方法一与方法二本质上都是需要在原来代码的基础上增加新的代码才能添加新的产品(类),依旧不满足不修改原油代码的要求。

2. 工厂方法模式

不修改已有类的前提下,可以增加产品类。

public interface CarFactory {
    Car getCar();
}
public class TeslaFactory implements CarFactory {

    public Car getCar() {
        return new Tesla();
    }
}
public class WuLingFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new WuLing();
    }
}
public class Consumer {
    public static void main(String[] args) {
        Car car = new WuLingFactory().getCar();
        Car car1 = new TeslaFactory().getCar();
        car.name();
        car1.name();
    }
}

运行截图:

image-20210203122009442

新增一个产品

public class DaZhong implements Car{
    @Override
    public void name() {
        System.out.println("大众!");
    }
}
public class  DaZhongFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new DaZhong();
    }
}
public class Consumer {
    public static void main(String[] args) {
        Car car = new WuLingFactory().getCar();
        Car car1 = new TeslaFactory().getCar();
        car.name();
        car1.name();

        Car car2 = new DaZhongFactory().getCar();
        car2.name();
    }
}

运行截图:

image-20210203122232410

图形描述:

image-20210303130230077

3. 对比

  • 结构复杂度:simple更简单
  • 代码复杂度:simple更简单
  • 编程复杂度:simple更简单
  • 管理上的复杂度:simple更简单

根据设计原则:工厂方法模式

根据实际业务:简单工厂模式

3、抽象工厂模式

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

  • 适用场景:

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

    • 具体产品在应用层的代码隔离无需关心创建的细节

    • 将一个系列的产品统一到一起创建

  • 缺点:

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

1. 思维图

img

2. 代码实现

image-20210214225134410

手机和路由器产品接口

package factory.abstract_2;

public interface IPhoneProduct {
    public void open();
    public void callUp();
    public void sendSMS();
    public void shutDown();
}
package factory.abstract_2;

public interface IRouterProduct {
    public void open();
    public void setting();
    public void openWifi();
    public void shutDown();
}

华为和小米手机和路由器产品实现

package factory.abstract_2;

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

    @Override
    public void callUp() {
        System.out.println("华为手机打电话");

    }

    @Override
    public void sendSMS() {
        System.out.println("华为手机发短信");

    }

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

    }
}
package factory.abstract_2;

public class xiaomiIPhone implements IPhoneProduct {

    @Override
    public void open() {
        System.out.println("小米手机开机");
    }

    @Override
    public void callUp() {
        System.out.println("小米手机打电话");

    }

    @Override
    public void sendSMS() {
        System.out.println("小米手机发短信");

    }

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

    }
}
package factory.abstract_2;

public class huaweiIRouter implements IRouterProduct{
    @Override
    public void open() {
        System.out.println("华为路由器打开");
    }

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

    @Override
    public void openWifi() {
        System.out.println("华为路由器打开无线网");
    }

    @Override
    public void shutDown() {
        System.out.println("华为路由器关机");
    }
}
package factory.abstract_2;

public class xiaomiIRouter implements IRouterProduct{
    @Override
    public void open() {
        System.out.println("小米路由器打开");
    }

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

    @Override
    public void openWifi() {
        System.out.println("小米路由器打开无线网");
    }

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

抽象工厂接口

  • 其本质就是工厂的工厂
package factory.abstract_2;

public interface IRouterProduct {
    public void open();
    public void setting();
    public void openWifi();
    public void shutDown();
}

抽象工厂的实现

package factory.abstract_2;

public class huaweiFactory implements ProductFactory {
    @Override
    public IPhoneProduct produceIPhone() {
        return new huaweiIPhone();
    }

    @Override
    public IRouterProduct produceIRouter() {
        return new huaweiIRouter();
    }
}
package factory.abstract_2;

public class xiaomiFactory implements ProductFactory{
    @Override
    public IPhoneProduct produceIPhone() {
        return new xiaomiIPhone();
    }

    @Override
    public IRouterProduct produceIRouter() {
        return new xiaomiIRouter();
    }
}

主程序

package factory.abstract_2;

public class Client {
    public static void main(String[] args) {
        
        System.out.println("===========小米系列产品===========");
        xiaomiFactory xf=new xiaomiFactory();
        IPhoneProduct xiaomiIPhoneProduct= xf.produceIPhone();
        // xiaomiIPhone xiaomiiphone2= (xiaomiIPhone) xf.produceIPhone();
        // xiaomiiphone2.open();
        xiaomiIPhoneProduct.open();
        xiaomiIPhoneProduct.callUp();

        IRouterProduct xiaomiIRouterProduct = xf.produceIRouter();
        xiaomiIRouterProduct.open();
        xiaomiIRouterProduct.openWifi();

        System.out.println();
        System.out.println("===========华为系列产品===========");
        huaweiFactory hf=new huaweiFactory();
        IPhoneProduct huaweiIPhoneProduct = hf.produceIPhone();
        huaweiIPhoneProduct.open();
        huaweiIPhoneProduct.callUp();

        IRouterProduct huaweiIRouterProduct = hf.produceIRouter();
        huaweiIRouterProduct.open();
        huaweiIRouterProduct.openWifi();
    }



}

运行截图:

image-20210214225746701

3. 实现类图

精彩类图~

image-20210214200256178

4. 小结

  • 简单工厂模式

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

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

    • 不可以增加产品,可以增加产族(也就是增加一个三星)!
  • 应用场景:

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

4、建造者模式

  • 建造者模式也属于创建型模式, 它提供了一种创建对象的最佳方式。
  • 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
  • 主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象
  • 用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
  • 例子:
    • 工厂(建造者模式) :负责制造汽车(组装过程和细节在工厂内)
    • 汽车购买者(用户) :你只需要说出你需要的型号(对象的类型和内容) ,然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、车门、>发动机、方向盘等等) )

image-20210216070849987

既然是建造者模式,那么我们还是继续造房吧,其实我也想不到更简单的例子。

假设造房简化为如下步骤:(1)地基 (2)钢筋工程 (3) 铺电线 (4)粉刷;

"如果”要盖- -座房子,

  • 首先要找一个建筑公司或工程承包商(指挥者)。
  • 承包商指挥工人(具体建造者)过来造房子(产品) ,
  • 最后验收。

1. 代码实现

方法一

package builder.builder1;

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

    public abstract Product getProduct();

}
package builder.builder1;

//产品
public class Product {
    private String buildA;
    private String buildB;
    private String buildC;
    private String buildD;

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "buildA='" + buildA + '\'' +
                ", buildB='" + buildB + '\'' +
                ", buildC='" + buildC + '\'' +
                ", buildD='" + buildD + '\'' +
                '}';
    }
}
package builder.builder1;

// 工人
public class Worker extends Builder {
    
    // 工人创建一个空的产品
    private Product product;
    public Worker() {
        product=new Product();
    }
	
    // 开始建造
    @Override
    public void buildA() {
        product.setBuildA("地基");
        System.out.println("地基");
    }

    @Override
    public void buildB() {
        product.setBuildB("钢筋工程");
        System.out.println("钢筋工程");
    }

    @Override
    public void buildC() {
        product.setBuildC("铺电线");
        System.out.println("铺电线");
    }

    @Override
    public void buildD() {
        product.setBuildD("粉刷");
        System.out.println("粉刷");
    }

    @Override
    public Product getProduct() {
        return product;
    }
}
package builder.builder1;

// 指挥者
public class Director {


    public Product getProduct(Builder builder){
        builder.buildA();
        builder.buildB();
        builder.buildC();
        builder.buildD();
        return builder.getProduct();

    }
}
package builder.builder1;

//建造
public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker();
        Director director = new Director();
        Product product = director.getProduct(worker);
        System.out.println(product);

    }
}

运行截图:

image-20210220154102322

方法二

方法一由于显示情况是一般是客户就是指挥者,由客户自定义需求,因此需要改进代码

package builder.builder2;


public abstract class Builder {

    // 注意这里的返回值是Builder,而是void,方便链式编程
    public abstract Builder buildA(String msg);  //汉堡
    public abstract Builder buildB(String msg);  //薯条
    public abstract Builder buildC(String msg);  //可乐
    public abstract Builder buildD(String msg);  //鸡肉卷

    public abstract Product getProduct();

}
package builder.builder2;

public class Product {
    private String buildA="汉堡";
    private String buildB="薯条";
    private String buildC="可乐";
    private String buildD="鸡肉卷";

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "buildA='" + buildA + '\'' +
                ", buildB='" + buildB + '\'' +
                ", buildC='" + buildC + '\'' +
                ", buildD='" + buildD + '\'' +
                '}';
    }
}
package builder.builder2;



public class Worker extends Builder {
    private Product product;
    public Worker() {
        product=new Product();
    }


    @Override
    public Builder buildA(String msg) {
        product.setBuildA(msg);
        return this;    // 注意这里返回this
    }

    @Override
    public Builder buildB(String msg) {
        product.setBuildB(msg);
        return this;

    }

    @Override
    public Builder buildC(String msg) {
        product.setBuildC(msg);
        return this;

    }

    @Override
    public Builder buildD(String msg) {
        product.setBuildD(msg);
        return this;

    }

    @Override
    public Product getProduct() {
        return product;
    }
}
package builder.builder2;


public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker();
        // 链式编程,这样就能实现自定义建造
        Product product = worker.buildA("全家桶").buildB("雪碧")
                .getProduct();
        System.out.println(product.toString());

    }
}

2. 优点

  • 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。

  • 将复杂产品的创建步骤分解在不同的方法中, 使得创建过程更加清晰

  • 具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则

3. 缺点

  • 建造者模式所创建的产品- -般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

4. 应用场景

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

5. 建造者与抽象工厂模式的比较

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回-系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一 个复杂对象, 返回一个完整的对象。
  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!

5、原型模式

  • 克隆
  • Prototype
  • Cloneable接口
  • clone方法

image-20210220164950652

  • 深克隆的一个简单方式就是改变clone方法,即实现深克隆的一个方式就是将方法中的属性一起克隆
  • 第二个方法就是序列化与反序列化

1. 浅克隆

package prototype.demo01;


// 实现原型模式的两个步骤
// 1.继承一个借口(Cloneable)
// 2.重写一个方法(clone())

import java.util.Date;

// 对视频进行盗版克隆
public class Video implements Cloneable{

    private String videoName;
    private Date creatDateTime;

    public Video() {

    }

    public Video(String videoName, Date creatDateTime) {
        this.videoName = videoName;
        this.creatDateTime = creatDateTime;
    }

    public void setVideoName(String videoName) {
        this.videoName = videoName;
    }

    public void setCreatDateTime(Date creatDateTime) {
        this.creatDateTime = creatDateTime;
    }

    public String getVideoName() {
        return videoName;
    }

    public Date getCreatDateTime() {
        return creatDateTime;
    }

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

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

import java.util.Date;

public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        // 浅克隆
        Video v1 = new Video("狂神说Java", date);
        System.out.println("v1=>"+v1.toString());
        System.out.println("v1 hashcode=>"+v1.hashCode());
        Video v2 = (Video) v1.clone();
        System.out.println("================");
        System.out.println("v2=>"+v2.toString());
        System.out.println("v2 hashcode=>"+v2.hashCode());
    }
}

image-20210220171503070

2. 深克隆

package prototype.demo02;



import java.util.Date;

// 实现深克隆,在重写的clone()方法中对该类中的属性也进行克隆
public class Video implements Cloneable{

    private String videoName;
    private Date creatDateTime;

    public Video() {

    }

    public Video(String videoName, Date creatDateTime) {
        this.videoName = videoName;
        this.creatDateTime = creatDateTime;
    }

    public void setVideoName(String videoName) {
        this.videoName = videoName;
    }

    public void setCreatDateTime(Date creatDateTime) {
        this.creatDateTime = creatDateTime;
    }

    public String getVideoName() {
        return videoName;
    }

    public Date getCreatDateTime() {
        return creatDateTime;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 关键步骤,在此处修改clone()方法
        // 令该方法的属性也克隆一份
        Object obj=super.clone();
        Video v= (Video) obj;
        Video ob= (Video) super.clone();
        
        // 克隆被克隆对象的时间
        Date date = (Date) this.creatDateTime.clone();
        
        //克隆对象的时间设置为上方的date
        ob.setCreatDateTime(date);
        return ob;
    }
}
package prototype.demo02;



import java.util.Date;

public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        // 深克隆
        Video v1 = new Video("狂神说Java", date);
        System.out.println("v1=>"+v1.toString());
        System.out.println("v1 hashcode=>"+v1.hashCode());
        Video v2 = (Video) v1.clone();
        System.out.println("v2=>"+v2.toString());
        System.out.println("v2 hashcode=>"+v2.hashCode());
        
        // 修改createDateTime,
        // 浅克隆会导致克隆对象和被克隆对象的createDataTime都发生改变,
        // 而深克隆发生改变的只是被克隆对象。
        System.out.println("================");
        System.out.println("改变data");
        System.out.println("================");
        date.setTime(214123412);
        System.out.println("v1=>"+v1.toString());
        System.out.println("v1 hashcode=>"+v1.hashCode());
        System.out.println("v2=>"+v2.toString());
        System.out.println("v2 hashcode=>"+v2.hashCode());


    }
}

image-20210220191051361

6、适配器模式

  • 将一个类的接口转换成客户希望的另外-个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!

  • 角色分析

    • 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。

    • 需要适配的类: 需要适配的类或适配者类。

    • 适配器: 通过包装一个需要适配的对象,把原接口转换成目标对象!

1. 对象适配器优点

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标。
  • 可以适配-个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

2. 类适配器缺点

  • 对于Java、 C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  • 在Java、 C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

3. 适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类, 包括-些可能在将来引进的类一起工作。

image-20210220221125692

4. 代码实现

package adapter;
// 网线
public class Adaptee {
    public void request(){
        System.out.println("网线连接成功了!");
    }
}
package adapter;


// 客户端类:向上网,插不上网线~
public class Computer {

    // 我们的电脑需要转接器才能连接电脑
    public void net(NetToUSB netToUSB){
        // 上网的具体实现~,找一个转接头
        netToUSB.handleRequest();
    }


    public static void main(String[] args) {
        // 电脑,适配器,网线

        // 方法一:继承(类适配器)
        /*Computer computer = new Computer();
        Adapter adapter = new Adapter();
        Adaptee adaptee = new Adaptee();

        computer.net(adapter);*/

        // 方法二:组合(对象适配器)
        Computer computer = new Computer();
        Adaptee adaptee = new Adaptee();
        Adapter adapter = new Adapter(adaptee);

        computer.net(adapter);

    }
}
package adapter;

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




// 真正的适配器~,需要连接USB,连接网线

// 方法一:继承(类适配器)
/*public class Adapter extends Adaptee implements NetToUSB  {

    @Override
    public void handleRequest() {
        super.request();
    }
}*/

// 方法二:组合(对象适配器)
public class Adapter implements NetToUSB  {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void handleRequest() {
        adaptee.request();
    }
}

image-20210221092524515

7、桥接模式

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

image-20210221092713391

1. 思维图

image-20210221101513129

image-20210221102909479

2. 代码实现

// 商标接口
public interface Brand {
    void info();
}

public class Apple implements Brand {

    @Override
    public void info() {
        System.out.print("苹果");
    }
}
public class Dell implements Brand {
    @Override
    public void info() {
        System.out.print("戴尔");
    }
}


// 不使用接口,是因为在Computer类中,需要引入商标,使用组合,进行桥接
public abstract class Computer {
    // 这里使用protected类型,确保其继承类能使用brand
    protected Brand brand;

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

public class Desktop extends Computer{
    public Desktop(Brand brand) {
        super(brand);
    }
    public void info(){
        super.info();
        System.out.println("台式机");
    }
}
public class Laptop extends Computer{
    public Laptop(Brand brand) {
        super(brand);
    }
    public void info(){
        super.info();
        System.out.println("笔记本");
    }
}

public class Test {
    public static void main(String[] args) {
        Desktop desktop1 = new Desktop(new Apple());
        desktop1.info();

        Laptop laptop = new Laptop(new Dell());
        laptop.info();


    }
}

image-20210221110730842

3. 好处分析

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

4. 劣势分析

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

5. 最佳实践

  • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一 个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

6. 场景

  • Java语言通过Java虚拟机实现了平台的无关性。
  • AWT中的Peer架构。
  • JDBC驱动程序也是桥接模式的应用之一。

思考?桥接模式+适配器模式

8、代理模式

为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP和SpringMVC】面试必问

代理就是中介角色

代理模式分类:

  • 静态代理
  • 动态代理

image-20210302123455889

8.1 静态代理

角色分析:

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

代理可以增加一些功能而且不用改变其他模块的代码

实现步骤

1.接口

package staticproxy.demo01;

// 租赁的抽象
public interface Rent {
    public void rent();
}


2.真实角色

package staticproxy.demo01;

// 房东类
public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}

3.代理

package staticproxy.demo01;

public class Proxy implements Rent{
    private Host host;

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

    @Override
    public void rent() {
        host.rent();
        contract();
        seeHouse();
        fee();
    }

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

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

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

4.客户端访问代理角色

package staticproxy.demo01;

public class Client {
    public static void main(String[] args) {
        // 房东要租房子
        Host host = new Host();
        // 代理,中介帮房东租房子,带是呢?代理角色一般会有一些附属操作!
        Proxy proxy = new Proxy(host);

        // 你不用面对房东,直接找中介租房即可
        proxy.rent();
    }
}

优点:

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

缺点:

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

8.2 静态代理加深理解

  1. 改动原有的业务代码,在公司中是大忌,有可能导致原有的代码崩了。

image-20210302154403847

package staticproxy.demo02;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}



package staticproxy.demo02;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加操作");
    }

    @Override
    public void delete() {
        System.out.println("删除操作");

    }

    @Override
    public void update() {
        System.out.println("更新操作");

    }

    @Override
    public void query() {
        System.out.println("查找操作");

    }
}


package staticproxy.demo02;

public class Proxy implements UserService{
    private UserServiceImpl userService;

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

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

    @Override
    public void delete() {
        log("delete");
        userService.delete();

    }

    @Override
    public void update() {
        log("update");
        userService.update();

    }

    @Override
    public void query() {
        log("query");
        userService.query();

    }

    public void log(String msg){
        System.out.println("[Debug] 使用了"+msg+"方法");
    }

}



package staticproxy.demo02;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        //
        // userService.add();
        Proxy proxy=new Proxy();
        proxy.setUserService(userService);
        proxy.add();
        proxy.query();
    }

}

8.3 动态代理

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

需要了解两个类:

Proxy,

InvocationHandler:调用处理程序

代码实现

package dynamicproxy;

// 租赁的抽象
public interface Rent {
    public void rent();
}


package dynamicproxy;

// 房东类
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}



package dynamicproxy;

import java.lang.reflect.InvocationHandler;
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;
    }

    /*Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
            new Class<?>[] { Foo.class },
            handler);*/
	// 获取代理类
    public Object getProxy(){
      return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                    target.getClass().getInterfaces(),
                                    this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理的本质就是使用反射机制
        Object invoke = method.invoke(target, args);
        seeHouse();
        contract();
        fee();
        return invoke;
    }

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

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

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


package dynamicproxy;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setTarget(host);
        Rent proxy = (Rent) proxyInvocationHandler.getProxy();
        proxy.rent();
        
    }
}

运行结果:

image-20210303103819515

动态代理的好处

  • 静态代理的全部优点
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务。
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口。
posted @ 2021-05-13 15:54    阅读(106)  评论(0编辑  收藏  举报