设计模式

什么是设计模式?

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

学习设计模式的意义

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

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

  • 可以提高程序员的思维能力、编程能力和设计能力;

  • 使程序设计更加标准化、代码编制更加工程化、使软件开发效率大大提高,从而缩短软件的开发周期。

  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

设计模式的基本要素

  • 模式名称

  • 要解决的问题

  • 解决方案

  • 效果

GoF23

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

  • 创建型模式:(对象的创建与使用分离) 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式;

  • 结构型模式:(按照某种布局组成一种更大的结构) 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;

  • 行为型模式:(描述对象之间相互协作完成单个对象无法完成的任务,主要是分配一些职责) 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

面向对象(OOP)七大原则

设计模式必须遵守OOP原则

  • 开闭原则:对扩展开放,对修改关闭

  • 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立(尽量不重写父类的方法,不要破坏继承关系)

  • 依赖倒置原则:要面向接口编程,不要面向实现编程(降低耦合性)

  • 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性(类的职责要单一)

  • 接口隔离原则:要为个各类建立它们需要的专用接口

  • 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话(降低耦合性)

  • 合成复用原则:尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现

设计模式

创建型模式

作用: 整体而言创建者模式就是帮助我们创建(或克隆)对象

单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式;

单例模式

  • 核心作用: 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

  • 常见场景: 

    windows的任务管理器

    windows的回收站

    项目中,读取配置文件的类

    网站计数器一般也会采用单例模式,可以保证同步

    数据库连接池的设计一般也是单例模式

    在servlet编程中,每个servlet也是单例的

    在spring中每一个bean默认就是单例的

    。。。。。

     

饿汉式单例

/**
 * 饿汉式单例
 */
public class Hangry {
​
    private Hangry() {
​
    }
​
    private static final Hangry HANGRY = new Hangry();
​
    public static Hangry getInstance() {
        return HANGRY;
    }
}
View Code

懒汉式单例

/**
 * 懒汉式单例
 */
public class Lazy {

    // 确保 Lazy 对象只能创建一次
    private static boolean isCreated = false;

    private Lazy() {
        synchronized (Lazy.class) {
            if (!isCreated) {
                isCreated = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏单例对象!");
            }
        }
    }

    private volatile static Lazy LAZY;

    //双层检测锁模式 懒汉式单例 DCL
    public static Lazy getInstance() {
        if (LAZY == null) {
            synchronized (Lazy.class) {
                if (LAZY == null) {
                    /*
                    对象创建步骤:
                    1、分配内存空间
                    2、执行构造方法,初始化对象
                    3、把这个对象执行内存空间
                    new Lazy() 不是原子性操作,多线程先不安全
                    将对象加上 volatile 修饰,可避免指令重排,实现原子性操作
                     */
                    LAZY = new Lazy();
                }
            }
        }
        return LAZY;
    }

    public String getThreadName() {
        return Thread.currentThread().getName();
    }
}
View Code

 

工厂模式

作用

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

核心本质

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

  • 将创建对象统一管理和控制。从而将调用者跟实现类解耦

详细分类

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

    咱们用 Car 来表示某一类型的产品,name()方法表示这类产品的共性

    public interface Car {
        void name();
    }
    View Code
    public class Tesla implements Car {
        @Override
        public void name() {
            System.out.println("特斯拉");
        }
    }
    View Code
    public class WuLing implements Car {
        @Override
        public void name() {
            System.out.println("五菱宏光");
        }
    }
    View Code
    /**
     * 简单工厂模式(静态工厂模式)
     */
    public class CarFactory {
        public static Car getCar(String carName) {
            switch (carName) {
                case "五菱":
                    return new WuLing();
                case "特斯拉":
                    return new Tesla();
                default:
                    return null;
            }
        }
    }
    View Code
    public class Consumer {
        public static void main(String[] args) {
            Car car = CarFactory.getCar("五菱");
            Car car1 = CarFactory.getCar("特斯拉");
    
            car.name();
            car1.name();
        }
    }
    View Code

    可以生产同一等级结构中的任意产品,弊端就是增加新的产品需要扩展已有代码。

  • 工厂方法模式
    咱们还用上面的 Car 和它的两个实现类为例,不同的是每一个品牌的车都有自己的工厂,这样增加新的品牌时就不需要修改原来的代码,只需增加一个相对应的工厂就行了。

    /**
     * 工厂方法模式
     */
    public interface CarFactory {
        Car getCar();
    }
    View Code
    public class TeslaFactory implements CarFactory {
        @Override
        public Car getCar() {
            return new Tesla();
        }
    }
    View Code
    public class WuLingFactory implements CarFactory {
        @Override
        public Car getCar() {
            return new WuLing();
        }
    }
    View Code
    /**
     * 客户想要那种产品,去找相应的工厂就行了
     */
    public class Consumer {
        public static void main(String[] args) {
            Car car = new WuLingFactory().getCar();
            Car car1 = new TeslaFactory().getCar();
    
            car.name();
            car1.name();
        }
    }
    View Code

    咱们增加一个品牌的车

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

    新品牌车的工厂

    public class DaZhongFactory implements CarFactory {
        @Override
        public Car getCar() {
            return new DaZhong();
        }
    }
    View Code
    /**
     * 客户想要那种产品,去找相应的工厂就行了
     */
    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();
        }
    }
    View Code

     

    满足了开闭原则,但是每增加一个产品就需要增加这个产品的工厂,增加了代码量。

  • 抽象工厂模式 围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂(下方单独说明)

小结

  • 简单工厂模式(又称静态工厂模式) 虽然某种程度上不符合设计原则,单实际使用最多

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

  • 抽象工厂模式 不可以增加产品,可以增加产品簇

应用场景

  • jdk中Calendar的getInstance方法

  • jdbc中Connection对象的获取

  • spring中ioc容器创建管理bean对象

  • 反射中Class对象的newInstance

抽象工厂模式

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

/**
 * 手机产品接口
 */
public interface PhoneProduct {
    void start();

    void shutdown();

    void callUp();

    void sendSMS();
}
View Code
/**
 * 路由器产品接口
 */
public interface RouterProduct {
    void start();

    void shutdown();

    void openWifi();

    void setting();
}
View Code
/**
 * 抽象产品工厂
 */
public interface ProductFactory {

    /**
     * 生产手机
     */
    PhoneProduct phoneProduct();

    /**
     * 生产路由器
     */
    RouterProduct routerProduct();
}
View Code
/**
 * 华为工厂
 */
public class HWFactory implements ProductFactory {
    @Override
    public PhoneProduct phoneProduct() {
        return new HWPhone();
    }

    @Override
    public RouterProduct routerProduct() {
        return new HWRouter();
    }
}
View Code
/**
 * 小米工厂
 */
public class XMFactory implements ProductFactory {
    @Override
    public PhoneProduct phoneProduct() {
        return new XMPhone();
    }

    @Override
    public RouterProduct routerProduct() {
        return new XMRouter();
    }
}
View Code
/**
 * 华为手机
 */
public class HWPhone implements PhoneProduct {
    @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("华为手机发短信");
    }
}
View Code
/**
 * 华为路由器
 */
public class HWRouter implements RouterProduct {
    @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("华为路由器设置参数");
    }
}
View Code
/**
 * 小米手机
 */
public class XMPhone implements PhoneProduct {
    @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("小米手机发短信");
    }
}
View Code
/**
 * 小米路由器
 */
public class XMRouter implements RouterProduct {
    @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("小米路由器设置参数");
    }
}
View Code
public class Consumer {
    public static void main(String[] args) {
        System.out.println("==================小米系列产品==================");
        //小米工厂
        XMFactory xmFactory = new XMFactory();

        PhoneProduct phoneProduct = xmFactory.phoneProduct();
        phoneProduct.callUp();
        phoneProduct.sendSMS();

        RouterProduct routerProduct = xmFactory.routerProduct();
        routerProduct.openWifi();
        routerProduct.setting();
        System.out.println();


        System.out.println("==================华为系列产品==================");
        //华为工厂
        HWFactory hwFactory = new HWFactory();

        phoneProduct = hwFactory.phoneProduct();
        phoneProduct.callUp();
        phoneProduct.sendSMS();

        routerProduct = hwFactory.routerProduct();
        routerProduct.openWifi();
        routerProduct.setting();
        System.out.println();
    }
}
View Code

 

 增加新产品困难,但是如果产品稳定,抽象工厂模式是非常强大的。

 

优点

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

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

缺点

  • 规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难

  • 增加了系统的抽象性和理解难度

适用场景

  • 客户端(应用层)不依赖于产品类实例如何创建、实现等细节

  • 强调一系列相关的产品对象(属于同一产品簇)一起使用创建对象需要大量的重复代码

  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现

建造者模式

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

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

主要作用:在用户不知道对象的创造过程和细节的情况下就可以直接创建复杂的对象。用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

例子

  • 工厂(建造者模式):负责制造汽车(组装过程和细节都在工厂内)

  • 汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的)

角色分析:

 

 

 

/**
 * 产品:房子
 */
public class Product {
    private String attributesA;
    private String attributesB;
    private String attributesC;
    private String attributesD;

    // getter、setter、toString 略···
}
View Code
/**
 * 抽象的建造者:定义方法
 */
public abstract class Builder {
    /**
     * 打地基
     */
    abstract void builderA();

    /**
     * 铺钢筋
     */
    abstract void builderB();

    /**
     * 铺电线
     */
    abstract void builderC();

    /**
     * 粉刷
     */
    abstract void builderD();

    /**
     * 完工:得到具体的产品
     */
    abstract Product getProduct();
}
View Code
/**
 * 指挥:建造者模式的核心,负责构建一个工程,工程如何构建由他决定
 */
public class Director {
    /**
     * 指挥生产产品(指挥工人按照循序建房子)
     *
     * @param builder 建造者
     * @return 得到产品
     */
    public Product build(Builder builder) {
        //生产过程
        builder.builderA();
        builder.builderB();
        builder.builderC();
        builder.builderD();
        //返回产品
        return builder.getProduct();
    }
}
View Code
/**
 * 具体的建造者:工人
 */
public class Worker extends Builder {

    private final Product product;

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

    @Override
    void builderA() {
        product.setAttributesA("打地基");
        System.out.println("打地基");
    }

    @Override
    void builderB() {
        product.setAttributesB("铺钢筋");
        System.out.println("铺钢筋");
    }

    @Override
    void builderC() {
        product.setAttributesC("铺电线");
        System.out.println("铺电线");
    }

    @Override
    void builderD() {
        product.setAttributesD("粉刷");
        System.out.println("粉刷");
    }

    @Override
    Product getProduct() {
        return this.product;
    }
}
View Code
public class Test {
    public static void main(String[] args) {
        //指挥
        Director director = new Director();
        //指挥建造者(工人)完成具体的产品(造房子)
        Product product = director.build(new Worker());
        System.out.println(product);
    }
}
View Code

上面示例是建造者模式的常规用法,Director在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。

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

比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交个用户来操作,使得产品的创建更加简单灵活。

/**
 * 产品:套餐
 */
public class Product {
    private String attributesA = "汉堡";
    private String attributesB = "可乐";
    private String attributesC = "薯条";
    private String attributesD = "甜点";

    // getter、setter、toString 略···
    
}
View Code
/**
 * 抽象的建造者:定义方法
 */
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();
}
View Code
/**
 * 具体的建造者:服务员
 */
public class Worker extends Builder {

    private final Product product;

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

    @Override
    Builder buildA(String msg) {
        product.setAttributesA(msg);
        return this;
    }

    @Override
    Builder buildB(String msg) {
        product.setAttributesB(msg);
        return this;
    }

    @Override
    Builder buildC(String msg) {
        product.setAttributesC(msg);
        return this;
    }

    @Override
    Builder buildD(String msg) {
        product.setAttributesD(msg);
        return this;
    }

    @Override
    Product getProduct() {
        return this.product;
    }
}
View Code
/**
 * 客户
 */
public class Customer {
    public static void main(String[] args) {
        //服务员
        Worker worker = new Worker();
        Product product = worker.buildA("鸡腿")
                .getProduct();
        System.out.println(product);
    }
}
View Code

 

应用场景

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;

  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品;

  • 适合于一个具有比较多的零件(属性)的产品(对象)的创建过程;

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

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品为与不同的产品等级结构,构成了一个产品簇;

  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象;

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

原型模式

原型模式实际上就是以一个对象为原型进行克隆

浅克隆

原型对象

import java.util.Date;

/**
 * 视频
 * 
 * 1、实现Cloneable接口
 * 2、重写clone()方法
 */
public class Video implements Cloneable{
    private String name;
    private Date createTime;

    public Video() {
    }

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

    // getter、setter、toString 略···


    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
View Code
测试
import java.util.Date;

/**
 * 客户端:克隆(浅克隆)
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象 v2
        Date createTime = new Date();
        Video v1 = new Video("告白气球", createTime);
        System.out.println("v1=>" + v1);
        System.out.println("v1hash=>" + v1.hashCode());

        //通过 v1 克隆出 v2
        Video v2 = (Video) v1.clone(); //克隆出来的对象和原来的对象一模一样,弊端是当修改了 他们的公共对象 createTime 时 v1 和 v2 的 createTime 都会随之改变
        System.out.println("v2=>" + v2);
        System.out.println("v2 hash=>" + v2.hashCode());

        System.out.println("==============================================");
        createTime.setTime(1111111);
        System.out.println(v1);
        System.out.println(v2);
    }
}
View Code

克隆出来的对象和原来的对象一模一样,弊端是当修改了 他们的公共对象 createTime 时 v1 和 v2 的 createTime 都会随之改变

 

深克隆

原型(在 clone() 方法中将属性也进行克隆)

import java.util.Date;

/**
 * 视频
 *
 * 1、实现Cloneable接口
 * 2、重写clone()方法
 */
public class Video implements Cloneable{
    private String name;
    private Date createTime;

    public Video() {
    }

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

    // getter、setter、toString 略···


    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();

        //实现深克隆
        Video v = (Video) obj;
        //将这个对象的属性也进行克隆
        v.createTime = (Date) this.createTime.clone();
        return v;
    }
}
View Code
​测试
import java.util.Date;

/**
 * 客户端:克隆(深克隆,改造 Video 中的 clone 方法)
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date createTime = new Date();
        //原型对象 v1
        Video v1 = new Video("告白气球", createTime);
        System.out.println("v1=>" + v1);
        System.out.println("v1hash=>" + v1.hashCode());

        //通过 v1 克隆出 v2
        Video v2 = (Video) v1.clone(); //
        System.out.println("v2=>" + v2);
        System.out.println("v2 hash=>" + v2.hashCode());

        System.out.println("==============================================");
        createTime.setTime(1111111);
        System.out.println(v1);
        System.out.println(v2);
    }
}
View Code

 

通过测试可以看出深克隆可以保证对象的安全性。

结构型模式

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

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

适配器模式

作用:将一个类的接口转换成客户希望的另一个接口,Adapter模式使得原本由于接口不兼容二不能一起工作的类可以在一起工作

角色分析

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

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

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

适配器模式分为对象适配器和类适配器

例:我新买了台笔记本电脑,我想连接网线上网,但是电脑没有网线接口,这个时候就需要一个分线器,电脑连接分线器,分线器连接网线,这时就达到连接网线上网的目的了。

首先我们有网线

/**
 * 要被适配的类:网线
 */
public class Cable {
    public void request() {
        System.out.println("连接网络");
    }
}
View Code

然后是分线器

/**
 * 分线器的抽象实现
 */
public interface Net2USB {

    //作用:处理请求,将网线接到电脑上
    void handleRequest();
}
View Code
/**
 * 真正的适配器,需要连接电脑和网线
 */
public class Adapter extends Cable implements Net2USB {
    @Override
    public void handleRequest() {
        super.request();//可以上网了
    }
}
View Code

电脑

/**
 * 客户端类:电脑,想上网,但是插不上网线
 */
public class Computer {
    // 电脑需要连接分线器才可以连接网线上网
    public void net(Net2USB adapter) {
        //上网具体实现,找一个带网线接口的分线器
        adapter.handleRequest();
    }
}
View Code

测试

public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();// 电脑
        Adapter adapter = new Adapter();//分线器(适配器)
        computer.net(adapter);
    }
}
View Code

但是类适配器跟适配对象是继承关系,java中的继承是单继承,有局限性。我们改造下适配器

/**
 * 真正的适配器,需要连接电脑和网线
 *
 * 组合(对象适配器,常用)
 */
public class Adapter2 implements Net2USB {

    private final Cable cable;

    public Adapter2(Cable cable) {
        this.cable = cable;
    }

    @Override
    public void handleRequest() {
        cable.request();//可以上网了
    }
}
View Code

测试

public class Test {

    public static void main(String[] args) {

        /*
        //继承(类适配器,单继承有局限性)
        Computer computer = new Computer();// 电脑
        Adapter adapter = new Adapter();//分线器(适配器)
        Cable cable = new Cable();//网线

        computer.net(adapter);
        */

        //组合(对象适配器,常用)
        Computer computer = new Computer();// 电脑
        Cable cable = new Cable();//网线
        Adapter2 adapter2 = new Adapter2(cable);//分线器(适配器)

        computer.net(adapter2);
    }
}
View Code

 

对象适配器(组合的方式)优点

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标

  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏替换原则”,适配者的子类也可通过该适配器进行适配

类适配器缺点

  • 对于Java、C#等不支持多继承的语言,一次最多只能适配一个适配者,不能同时适配多个适配者

  • 在Java、C#等语言中,类适配模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性

适配场景

  • 系统需要使用一些现有的类,而这些接口(如方法名)不符合系统的需要,甚至没有这些类的源代码

  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作

桥接模式bridge

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

 

/**
 * 品牌
 */
public interface Brand {
    void info();
}
View Code
/**
 * 联想品牌
 */
public class Lenovo implements Brand{
    @Override
    public void info() {
        System.out.print("联想");
    }
}
View Code
/**
 * 苹果品牌
 */
public class Apple implements Brand{
    @Override
    public void info() {
        System.out.print("苹果");
    }
}
View Code
/**
 * 抽象的电脑类型
 */
public abstract class Computer {
    /**
     * 组合品牌(桥)
     */
    protected Brand brand;

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

    public void info() {
        brand.info();
    }
}
View Code
/**
 * 台式机
 */
public class Desktop extends Computer {
    public Desktop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("台式机");
    }
}
View Code
/**
 * 笔记本
 */
public class Laptop extends Computer {
    public Laptop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("笔记本");
    }
}
View Code
public class Test {
    public static void main(String[] args) {
        //苹果笔记本
        Computer laptop = new Laptop(new Apple());
        laptop.info();

        //联想台式机
        Computer desktop = new Desktop(new Lenovo());
        desktop.info();
    }
}
View Code

 

好处分析

  • 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法,大大的减少了子类的个数,从而降低管理和维护的成本

  • 桥接模式提高了系统的可扩充性,在两个变化纬度中任意扩展一个纬度,都不需要修改原有的系统,符合开闭原则,就像一座桥,可以把两个变化的纬度连接起来

劣势分析

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程

  • 桥接模式要求正确识别出系统中两个独立变化的纬度,因此其适用范围具有一定的局限性

代理模式

为什么要学习代理模式?因为这就是SpringAOP的底层

代理模式分类:

  • 静态代理

  • 动态代理

静态代理

代码步骤

  1. 接口

    /**
     * 租房
     */
    public interface Rent {
        void rent();
    }
    View Code
  2. 真实角色

    /**
     * 房东
     */
    public class Landlord implements Rent{
        @Override
        public void rent() {
            System.out.println("房东出租房子");
        }
    }
    View Code
  3. 代理角色

    /**
     * 代理角色:中介
     */
    public class Proxy implements Rent{
    ​
        private Landlord landlord;
            
        public Proxy(Landlord landlord) {
            this.landlord = landlord;
        }
    ​
        @Override
        public void rent() {
            System.out.print("代理帮");
            landlord.rent();
            seeHouse();
            contract();
            fare();
        }
    ​
        public void seeHouse() {
            System.out.println("中介带你看房");
        }
    ​
        public void contract() {
            System.out.println("签租赁合同");
        }
    ​
        public void fare() {
            System.out.println("中介收中介费");
        }
    }
    View Code
  4. 客户端访问代理角色

    /**
     * 客户要租房子
     */
    public class Client {
        public static void main(String[] args) {
            Proxy proxy = new Proxy(new Landlord());
            proxy.rent();
        }
    }
    View Code

     

角色分析

  • 抽象角色:一般使用接口或抽象类解决

  • 真是角色:被代理的角色

  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作

  • 客户:访问代理对象的人

静态代理的好处

  • 可以使真实角色的操作更加纯粹,不用去专注一些公共的业务

  • 公共的业务就交给了代理角色,实现了分工

  • 公共的业务发生扩展的时候方便集中管理

缺点

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

动态代理

动态代理和静态代理角色一样,动态代理的代理类时动态生成的,不是我们直接写好的

动态代理分为:

  • 基于接口的动态代理(JKD动态代理)

  • 基于类的动态代理(cglib)

  • 基于Java字节码(JAVAssist)

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

还以租房为例,咱们写一个生成代理类的工具

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

    /**
     * 处理代理实例并返回结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        contract();

        //动态代理的本质就是使用反射实现
        Object invoke = method.invoke(rent, args);

        fare();
        return invoke;
    }

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

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

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

测试

public class Client {
    public static void main(String[] args) {
        //真实角色:房东
        Landlord landlord = new Landlord();
        //代理角色
        ProxyInvocationHandler handler = new ProxyInvocationHandler();
        //通过代用程序处理角色来调用我们要调用的接口对象
        handler.setRent(landlord);
        //动态生成代理类
        Rent proxy = (Rent) handler.getProxy();
        proxy.rent();
    }
}
View Code

 

动态代理的好处

  • 可以使真实角色的操作更加纯粹,不用去专注一些公共的业务

  • 公共的业务就交给了代理角色,实现了分工

  • 公共的业务发生扩展的时候方便集中管理

  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务

  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

posted @ 2021-06-23 06:01  kaisheng_reflect  阅读(47)  评论(0编辑  收藏  举报