Java后端架构师的成长之路(二)——Java设计模式(2)

23种设计模式

结构型模式

适配器模式

基本介绍

  • 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
  • 适配器模式属于结构型模式。
  • 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

适配器模式工作原理

  • 适配器模式将一个类的接口转换成另一种接口让原本接口不兼容的类可以兼容,从用户的角度看不到被适配者,是解耦的。
  • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。
  • 用户收到反馈结果,感觉只是和目标接口交互,如图:
    在这里插入图片描述

类适配器模式

  • 基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成 src 到 dst 的适配。
  • 应用实例:以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即 目标)是5V直流电。
  • UML类图:
    在这里插入图片描述
  • 代码实现:
/**
 * 适配器模式
 */
public class AdapterCase01 {
    public static void main(String[] args) {
        new Phone().charging(new ClassVoltageAdapter());
    }
}
/**
 * 被适配的类
 */
class Voltage220V {
    public int output220V() {
        int src = 220;
        System.out.println("电压" + src + "V");
        return src;
    }
}
/**
 * 适配接口
 */
interface IVoltage5V {
    int output5V();
}
/**
 * 手机
 */ 
class Phone {
    void charging(IVoltage5V voltage5V) {
        int output = voltage5V.output5V();
        if (output == 5) {
            System.out.println("电压为5V,可以充电");
        } else if (output > 5) {
            System.out.println("电压大于5V,不可以充电");
        }
    }
}
/**
 * 类适配器
 */
class ClassVoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
    	System.out.println("----类适配器----");
        int srcV = output220V();
        int dstV = srcV / 44;
        return dstV;
    }
}
  • Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性。
  • src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
  • 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器模式

  • 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即持有 src类,实现 dst 类接口,完成src到dst的适配。
  • 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
  • 对象适配器模式是适配器模式常用的一种
  • UML类图:
    在这里插入图片描述
  • 代码实现:
/**
 * 对象适配器
 */
class ObjectVoltageAdapter implements IVoltage5V {
    private Voltage220V voltage220V;
    ObjectVoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }
    @Override
    public int output5V() {
        System.out.println("----对象适配器----");
        int srcV = voltage220V.output220V();
        int dstV = srcV / 44;
        return dstV;
    }
}
  • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
  • 使用成本更低,更灵活。

接口适配器模式

  • 一些书籍称适配器模式为(Default Adapter Pattern)或缺省适配器模式。
  • 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
  • 适用于一个接口不想使用其所有的方法的情况。
  • UML类图:
    在这里插入图片描述
  • 代码实现:
public class AdapterCase02 {
    public static void main(String[] args) {
        AbstractAadpter adpter = new AbstractAadpter() {
            @Override
            public void m1() {
                System.out.println("m1");
            }
        };
        adpter.m1();
    }
}
interface InterfaceA {
    void m1();
    void m2();
    void m3();
    void m4();
}
abstract class AbstractAadpter implements InterfaceA {
    @Override public void m1() {}
    @Override public void m2() {}
    @Override public void m3() {}
    @Override public void m4() {}
}

适配器模式在SpringMVC框架应用的源码分析

  • SpringMvc中的HandlerAdapter,就使用了适配器模式。
  • 可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,得修改原来的代码,这样违背了 OCP 原则。
    在这里插入图片描述
  • Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类。
  • 适配器代替controller执行相应的方法。
  • 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,这就是设计模式的力量。
  • 动手写SpringMVC通过适配器设计模式获取到对应的Controller的源码:
  • UML类图:
    在这里插入图片描述
  • 代码实现:
// 处理器
public interface Handler {
    void doHandle();
}
class SimpleHandler implements Handler {
    @Override
    public void doHandle() {
        System.out.println("SimpleHandler.doHandle() ...");
    }
}
class HttpHandler implements Handler {
    @Override
    public void doHandle() {
        System.out.println("HttpHandler.doHandle() ...");
    }
}
class AnnotationHandler implements Handler {
    @Override
    public void doHandle() {
        System.out.println("AnnotationHandler.doHandle() ...");
    }
}
// 处理器适配器
public interface HandlerAdapter {
    boolean support(Handler handler);
    void handle(Handler handler);
}
class SimpleHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean support(Handler handler) {
        return handler instanceof SimpleHandler;
    }
    @Override
    public void handle(Handler handler) {
        handler.doHandle();
    }
}
class HttpHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean support(Handler handler) {
        return handler instanceof HttpHandler;
    }
    @Override
    public void handle(Handler handler) {
        handler.doHandle();
    }
}
class AnnotationHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean support(Handler handler) {
        return handler instanceof AnnotationHandler;
    }
    @Override
    public void handle(Handler handler) {
        handler.doHandle();
    }
}
// 分派
public class DispatchServlet {
    private static List<HandlerAdapter> handlerAdapters = new ArrayList<>();
    public DispatchServlet() {
        handlerAdapters.add(new SimpleHandlerAdapter());
        handlerAdapters.add(new HttpHandlerAdapter());
        handlerAdapters.add(new AnnotationHandlerAdapter());
    }
    public void doDispatch() {
        HttpHandler handler = new HttpHandler();
        HandlerAdapter adapter = getHandler(handler);
        if (adapter != null) {
            adapter.handle(handler);
        }
    }

    private HandlerAdapter getHandler(Handler handler) {
        for (HandlerAdapter adapter : handlerAdapters) {
            if (adapter.support(handler)) {
                return adapter;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        new DispatchServlet().doDispatch();
    }
}

适配器模式的注意事项和细节

  • 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
  • 类适配器:以类给到,在Adapter里,就是将src当做类,继承
  • 对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
  • 接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
  • Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。

桥接模式

手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
在这里插入图片描述

传统方案解决手机操作问题

在这里插入图片描述

  • 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  • 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
  • 解决方案-使用桥接模式

桥接模式(Bridge)-基本介绍

  • 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变,是一种结构型设计模式。
  • Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
  • UML原理类图:
    在这里插入图片描述
* 抽象类(Abstraction):维护了Implementor,及其实现子类,两者是聚合关系,Abstraction充当桥接类
* RefinedAbstraction:是Abstraction抽象类的实现子类
* Implementor:是行为实现类的接口
* ConcreteImplementorA、ConcreteImplementorB:是行为的具体实现类
  • 代码实现:
public class BridgeCase01 {
    public static void main(String[] args) {
        Phone foldedXiaoMi = new FoldedPhone(new XiaoMi());
        foldedXiaoMi.open();
        foldedXiaoMi.close();
        foldedXiaoMi.call();
        System.out.println("===============");
        Phone foldedViVo = new FoldedPhone(new ViVo());
        foldedViVo.open();
        foldedViVo.close();
        foldedViVo.call();
    }
}

/**
 * 手机品牌:Implementor行为实现类的接口
 */
interface Brand {
    void open();
    void close();
    void call();
}
/**
 * 小米手机:行为的具体实现类
 */
class XiaoMi implements Brand {
    @Override
    public void open() {
        System.out.println("小米手机开机");
    }
    @Override
    public void close() {
        System.out.println("小米手机关机");
    }
    @Override
    public void call() {
        System.out.println("小米手机打电话");
    }
}
/**
 * ViVo手机:行为的具体实现类
 */
class ViVo implements Brand {
    @Override
    public void open() {
        System.out.println("ViVo手机开机");
    }
    @Override
    public void close() {
        System.out.println("ViVo手机关机");
    }
    @Override
    public void call() {
        System.out.println("ViVo手机打电话");
    }
}
/**
 * 手机样式:抽象类(Abstraction)
 */
abstract class Phone {
    /**
     * 组合进 品牌
     */
    private Brand brand;
    Phone(Brand brand) {
        this.brand = brand;
    }
    protected void open() {
        this.brand.open();
    }
    protected void close() {
        this.brand.close();
    }
    protected void call() {
        this.brand.call();
    }
}
/**
 * 手机样式具体的实现子类
 */
class FoldedPhone extends Phone {
    private static final String STYLE = "折叠样式";
    FoldedPhone(Brand brand) {
        super(brand);
    }
    @Override
    public void open() {
        System.out.print(STYLE);
        super.open();
    }
    @Override
    public void close() {
        System.out.print(STYLE);
        super.close();
    }
    @Override
    public void call() {
        System.out.print(STYLE);
        super.call();
    }
}
//折叠样式小米手机开机
//折叠样式小米手机关机
//折叠样式小米手机打电话
//===============
//折叠样式ViVo手机开机
//折叠样式ViVo手机关机
//折叠样式ViVo手机打电话
  • 如果此时需要增加一种样式,比如直立式:
class UpRightPhone extends Phone {
    private static final String STYLE = "直立样式";
    UpRightPhone(Brand brand) {
        super(brand);
    }
    @Override
    public void open() {
        System.out.print(STYLE);
        super.open();
    }
    @Override
    public void close() {
        System.out.print(STYLE);
        super.close();
    }
    @Override
    public void call() {
        System.out.print(STYLE);
        super.call();
    }
}
public class BridgeCase01 {
    public static void main(String[] args) {
        Phone upRightXiaomi = new UpRightPhone(new XiaoMi());
        upRightXiaomi.open();
        upRightXiaomi.close();
        upRightXiaomi.call();
    }
}
//直立样式小米手机开机
//直立样式小米手机关机
//直立样式小米手机打电话

桥接模式在JDBC的源码剖析

  • Jdbc的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类。
    在这里插入图片描述

桥接模式的注意事项和细节

  • 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  • 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  • 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  • 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度【抽象与实现】,因此其使用范围有一定的局限性,即需要有这样的应用场景。

桥接模式其它应用场景

  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  • 常见的应用场景:JDBC驱动程序、银行转账系统【转账分类:网上转账、柜台转账、AMT转账…;转账用户类型:普通用户、银卡用户、金卡用户…】、消息管理【消息类型:即时消息、延时消息…;消息分类:手机短信、邮件消息、QQ消息…】。

装饰器模式

星巴克咖啡订单项目

  • 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)。
  • 调料:Milk、Soy(豆浆)、Chocolate。
  • 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。
  • 使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。

方案一

在这里插入图片描述

  • Drink 是一个抽象类,表示饮料;description 就是对咖啡的描述,比如咖啡的名字;cost()方法就是计算费用,Drink类中做成一个抽象方法。
  • Decaf 就是单品咖啡, 继承Drink,并实现cost;Espress && Milk 就是单品咖啡+调料, 这个组合很多。
  • 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸。

方案二

  • 前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图):
    在这里插入图片描述
  • 方案2可以控制类的数量,不至于造成很多的类,但是在增加或者删除调料种类时,代码的维护量很大。
  • 考虑到用户可以添加多份调料时,可以将 hasMilk 返回一个对应int。
  • 方案三:考虑使用装饰者模式

装饰者模式定义

  • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。
  • 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代码的形式体现。

装饰者模式(Decorator)原理

  • UML类图:
    在这里插入图片描述
  • 装饰者模式就像打包一个快递,主体比如:陶瓷、衣服 (Component) 【被装饰者】;包装比如:报纸填充、塑料泡沫、纸板、木板(Decorator)。
  • Component:主体,比如类似前面的Drink。
  • ConcreteComponent:具体的主体,比如前面的各个单品咖啡。
  • Decorator:装饰者,比如各调料。
  • 在如图的Component与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。

装饰者模式解决星巴克咖啡订单

  • 设计方案:
    在这里插入图片描述
  • 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
    在这里插入图片描述
  • 代码实现:
public class DecoratorCase {
    public static void main(String[] args) {
        // 2份巧克力+一份牛奶的 LongBlack
        // 1、点一份 LongBlack
        Drink order = new LongBlack();
        System.out.println("费用1 = " + order.getCost() + ",描述 = " + order.getDesc());
        // 2、order加入一份牛奶
        order = new Milk(order);
        System.out.println("费用2 = " + order.getCost() + ",描述 = " + order.getDesc());
        // 3、order加入一份巧克力
        order = new Chocolate(order);
        System.out.println("费用3 = " + order.getCost() + ",描述 = " + order.getDesc());
        // 4、order再加入一份巧克力
        order = new Chocolate(order);
        System.out.println("费用4 = " + order.getCost() + ",描述 = " + order.getDesc());
    }
}
/**
 * 被装饰者:Component
 */
@Data
abstract class Drink {
    private String desc;
    private float price;
    abstract float getCost();
}

class Coffee extends Drink {
    @Override
    float getCost() {
        return super.getPrice();
    }
}
/**
 * 具体的被装饰者
 */
class Espresso extends Coffee {
    Espresso() {
        setDesc("意大利咖啡");
        setPrice(6.0f);
    }
}
class LongBlack extends Coffee {
    LongBlack() {
        setDesc("LongBlack");
        setPrice(5.0f);
    }
}
class ShortBlack extends Coffee {
    ShortBlack() {
        setDesc("ShortBlack");
        setPrice(4.0f);
    }
}
/**
 * 装饰者
 */
class Decorator extends Drink {
    private Drink drink;
    Decorator(Drink drink) {
        this.drink = drink;
    }
    @Override
    float getCost() {
        return super.getPrice() + drink.getCost();
    }
    @Override
    public String getDesc() {
        // drink.getDesc() 是被装饰者的信息
        return super.getDesc() + " " + super.getPrice() + " && " + drink.getDesc();
    }
}
/**
 * 具体的装饰者:调味品
 */
class Chocolate extends Decorator {
    Chocolate(Drink drink) {
        super(drink);
        setDesc(" 巧克力 ");
        // 调味品的价格
        setPrice(3.0f);
    }
}
class Milk extends Decorator {
    Milk(Drink drink) {
        super(drink);
        setDesc(" 牛奶 ");
        setPrice(2.0f);
    }
}
class Soy extends Decorator {
    Soy(Drink drink) {
        super(drink);
        setDesc(" 豆浆 ");
        setPrice(1.5f);
    }
}

装饰者模式在JDK应用的源码分析

  • Java的IO结构,FilterInputStream就是一个装饰者
    在这里插入图片描述
public abstract class InputStream implements Closeable{} // 是一个抽象类,即Component
public class FilterInputStream extends InputStream {// 是一个装饰者类Decorator
	protected volatile InputStream in; // 被装饰的对象
}
public class DataInputStream extends FilterInputStream implements DataInput {} // 是 FilterInputStream 子类,也继承了被装饰的对象 InputStream

组合模式

学校院系展示需求

  • 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
    在这里插入图片描述
  • 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的。
  • 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等。
  • 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式

组合模式基本介绍

  • 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
  • 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
  • 这种类型的设计模式属于结构型模式。
  • 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
  • UML类图:
    在这里插入图片描述
* Component : 这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口
* Leaf : 在组合中表示叶子节点,叶子节点没有子节点
* Composite : 非叶子节点, 用于存储子部件, 在 Component 接口中实现 子部件的相关操作,比如增加(add),删除
  • 组合模式解决这样的问题:当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子。
    在这里插入图片描述

组合模式解决学校院系展示

  • 思路分析:
    在这里插入图片描述
  • 代码实现:
public class CompositeCase {
    public static void main(String[] args) {
        // 从大到小创建对象
        University university = new University("清华大学");
        College college1 = new College("计算机学院");
        College college2 = new College("信息工程学院");

        college1.add(new Department("软件工程"));
        college1.add(new Department("网络安全"));
        college1.add(new Department("计算机科学与技术"));
        college2.add(new Department("通信工程"));
        college2.add(new Department("信息工程"));

//        college1.print();

        university.add(college1);
        university.add(college2);

        university.print();
    }
}
@Data
abstract class OrgComponent {
    private String name;
    OrgComponent(String name){
        this.name = name;
    }
    protected void add(OrgComponent component) {
        // 默认实现
        throw new UnsupportedOperationException();
    }
    protected void remove(OrgComponent component) {
        // 默认实现
        throw new UnsupportedOperationException();
    }
    protected void get(int index) {
        // 默认实现
        throw new UnsupportedOperationException();
    }
    abstract void print();
}
class University extends OrgComponent {
    List<OrgComponent> components = new ArrayList<>();
    University(String name) {
        super(name);
    }
    @Override
    public void add(OrgComponent component) {
        components.add(component);
    }
    @Override
    public void remove(OrgComponent component) {
        components.remove(component);
    }
    @Override
    public void get(int index) {
        components.get(index);
    }
    @Override
    public void print() {
        System.out.println("--------" + getName() +"--------");
        if (components.size() > 0) {
            for (OrgComponent component : components) {
                component.print();
            }
        }
    }
}
class College extends OrgComponent {
    List<OrgComponent> components = new ArrayList<>();
    College(String name) {
        super(name);
    }
    @Override
    public void add(OrgComponent component) {
        // 实际业务中,add方法不一定完全一样
        components.add(component);
    }
    @Override
    public void remove(OrgComponent component) {
        components.remove(component);
    }
    @Override
    public void get(int index) {
        components.get(index);
    }
    @Override
    public void print() {
        System.out.println("--------" + getName() +"--------");
        if (components.size() > 0) {
            for (OrgComponent component : components) {
                component.print();
            }
        }
    }
}
/**
 * 叶子结点
 */
class Department extends OrgComponent {
    Department(String name) {
        super(name);
    }
    @Override
    public void print() {
        System.out.println(getName());
    }
}

组合模式在JDK集合的源码分析

  • Java的集合类-HashMap就使用了组合模式,类图:
    在这里插入图片描述
public class JDKComposite {
    public static void main(String[] args) {
        /*
        Map 就是一个抽象的构建(类似组合模式里面的Component)
        HashMap是一个中间的构建(Composite),实现/继承了相关方法:put、putAll
        Node 是 HasMap 的静态内部类,类似于 Leaf 叶子结点,没有 put、putAll 方法
        */
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(0, "西游记");

        Map<Integer, String> map = new HashMap<>();
        map.put(1, "水浒传");
        map.put(2, "红楼梦");
        hashMap.putAll(map);
        System.out.println(hashMap);
    }
}

组合模式的注意事项和细节

  • 组合模式简化了客户端操作,客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  • 具有较强的扩展性,当我们要更改组合对象时,我们只需要调整内部的层次关系客户端不用做出任何改动。
  • 方便创建出复杂的层次结构,客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
  • 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式。
  • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

外观模式

影院管理项目

  • 组建一个家庭影院:DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:
• 直接用遥控器:统筹各设备开关 • 开爆米花机
• 放下屏幕
• 开投影仪
• 开音响
• 开DVD,选dvd
• 去拿爆米花
• 调暗灯光
• 播放
• 观影结束后,关闭各种设备

传统方式解决影院管理

在这里插入图片描述

  • 在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程。
  • 不利于在ClientTest 中,去维护对子系统的操作。
  • 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的一群接口。
  • 也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式

外观模式基本介绍

  • 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  • 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。

外观模式原理类图

在这里插入图片描述

  • 外观类(Facade):为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象。
  • 调用者(Client):外观接口的调用者。
  • 子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者。

外观模式解决影院管理

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

  • 代码实现:
public class FacadeCase {
    public static void main(String[] args) {
        // 如果直接调用,很不方便
        HomeTheaterFacade facade = new HomeTheaterFacade();
        facade.ready();
        facade.play();
        facade.end();
    }
}
class DVDPlayer {
    private DVDPlayer() {}
    private static DVDPlayer INSTANCE = new DVDPlayer();
    public static DVDPlayer getInstance() {
        return INSTANCE;
    }
    public void on() {
        System.out.println("DVD on");
    }
    public void off() {
        System.out.println("DVD off");
    }
    public void play() {
        System.out.println("DVD playing");
    }
    public void pause() {
        System.out.println("DVD paused");
    }
}
class Popcorn {
    private Popcorn() {}
    private static Popcorn INSTANCE = new Popcorn();
    public static Popcorn getInstance() {
        return INSTANCE;
    }
    public void on() {
        System.out.println("Popcorn on");
    }
    public void off() {
        System.out.println("Popcorn off");
    }
    public void pop() {
        System.out.println("Popcorn popping");
    }
}
class Projector {
    private Projector() {}
    private static Projector INSTANCE = new Projector();
    public static Projector getInstance() {
        return INSTANCE;
    }
    public void on() {
        System.out.println("Projector on");
    }
    public void off() {
        System.out.println("Projector off");
    }
    public void focus() {
        System.out.println("Projector focus");
    }
}
class Screen {
    private Screen() {}
    private static Screen INSTANCE = new Screen();
    public static Screen getInstance() {
        return INSTANCE;
    }
    public void up() {
        System.out.println("Screen up");
    }
    public void down() {
        System.out.println("Screen down");
    }
}
class Stereo {
    private Stereo() {}
    private static Stereo INSTANCE = new Stereo();
    public static Stereo getInstance() {
        return INSTANCE;
    }
    public void on() {
        System.out.println("Stereo on");
    }
    public void off() {
        System.out.println("Stereo off");
    }
    public void up() {
        System.out.println("Stereo up");
    }
    public void down() {
        System.out.println("Stereo down");
    }
}
class TheaterLight {
    private TheaterLight() {}
    private static TheaterLight INSTANCE = new TheaterLight();
    public static TheaterLight getInstance() {
        return INSTANCE;
    }
    public void on() {
        System.out.println("TheaterLight on");
    }
    public void off() {
        System.out.println("TheaterLight off");
    }
    public void dim() {
        System.out.println("TheaterLight dim");
    }
    public void bright() {
        System.out.println("TheaterLight bright");
    }
}

class HomeTheaterFacade {
    /**
     * 定义各个子系统的对象
     */
    private DVDPlayer dvdPlayer;
    private Popcorn popcorn;
    private Projector projector;
    private Screen screen;
    private Stereo stereo;
    private TheaterLight theaterLight;

    public HomeTheaterFacade() {
        this.dvdPlayer = DVDPlayer.getInstance();
        this.popcorn = Popcorn.getInstance();
        this.projector = Projector.getInstance();
        this.screen = Screen.getInstance();
        this.stereo = Stereo.getInstance();
        this.theaterLight = TheaterLight.getInstance();
    }

    public void ready(){
        popcorn.on();
        popcorn.pop();
        screen.down();
        projector.on();
        stereo.on();
        dvdPlayer.on();
        theaterLight.dim();
    }

    public void play() {
        dvdPlayer.play();
    }

    public void pause() {
        dvdPlayer.pause();
    }

    public void end() {
        popcorn.off();
        screen.up();
        projector.off();
        stereo.off();
        dvdPlayer.off();
        theaterLight.off();
    }
}

外观模式在MyBatis框架应用的源码分析

  • MyBatis 中的Configuration 去创建MetaObject 对象使用到外观模式
    在这里插入图片描述
    在这里插入图片描述

外观模式的注意事项和细节

  • 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
  • 外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展。
  • 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  • 当系统需要进行分层设计时,可以考虑使用Facade模式。
  • 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。
  • 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。

享元模式

展示网站项目需求

  • 小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:有客户要求以新闻的形式发布、有客户人要求以博客的形式发布、有客户希望以微信公众号的形式发布。
  • 传统方案解决网站展现项目:直接复制粘贴一份,然后根据客户不同要求,进行定制修改,给每个网站租用一个空间。
    在这里插入图片描述
  • 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费。
  • 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。
  • 对于代码来说,由于是一份实例,维护和扩展都更加容易。==>> 使用 享元模式 来解决

享元模式基本介绍

  • 享元模式(Flyweight Pattern) 也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象。
  • 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
  • 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
  • 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
    在这里插入图片描述

享元模式的原理类图

在这里插入图片描述

  • FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现。
  • ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务。
  • UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。
  • FlyWeightFactory 享元工厂类,用于构建一个池容器(集合), 同时提供从池中获取对象方法。

内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子座标就是棋子的外部状态。

  • 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态.
  • 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
  • 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
  • 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。

享元模式解决网站展现项目

在这里插入图片描述

public class FlyweightCase {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();
        WebSite webSite1 = factory.getWebSite("新闻");
        webSite1.use(new User("张三"));

        WebSite webSite2 = factory.getWebSite("博客");
        webSite2.use(new User("李四"));
        WebSite webSite3 = factory.getWebSite("博客");
        webSite3.use(new User("王五"));

        System.out.println("网站的分类共 " + factory.getWebSiteCount() + " 个");
    }
}
abstract class WebSite {
    abstract void use(User user);
}
class ConcreteWebSite extends WebSite {
    /**
     * 网站类型:内部状态
     */
    private String type;
    ConcreteWebSite(String type) {
        this.type = type;
    }
    @Override
    public void use(User user) {
        System.out.println("网站的发布形式:" + type + "," + user.getName() + "在使用中...");
    }
}
class WebSiteFactory {
    private Map<String, ConcreteWebSite> pool = new HashMap<>();
    public WebSite getWebSite(String type) {
        if (!pool.containsKey(type)) {
            pool.put(type, new ConcreteWebSite(type));
        }
        return pool.get(type);
    }
    public int getWebSiteCount() {
        return pool.size();
    }
}
/**
 * 外部状态
 */
@Data
@AllArgsConstructor
class User {
    private String name;
}
//网站的发布形式:新闻,张三在使用中...
//网站的发布形式:博客,李四在使用中...
//网站的发布形式:博客,王五在使用中...
//网站的分类共 2 个

享元模式在JDK-Interger的应用源码分析

  • 在JDK Integer中使用到了享元模式
public class JDKFlyweight {
    public static void main(String[] args) {
        /*
         如果 Integer.valueOf(x) x 在 -127~128 之间,就是使用的享元模式返回
         public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
         }
         */
        Integer x = Integer.valueOf(127);
        Integer y = new Integer(127);
        Integer z = Integer.valueOf(127);
        Integer w = new Integer(127);


        System.out.println(x.equals(y));    // true
        System.out.println(x == y);         // false
        System.out.println(x == z);         // true
        System.out.println(w == x);         // false
        System.out.println(w == y);         // fasle

        Integer x1 = Integer.valueOf(200);
        Integer x2 = Integer.valueOf(200);
        System.out.println(x1 == x2);       // false
    }
}
  • 在 valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的 Integer(new), 否则,就直接从 缓存池返回。
  • valueOf 方法,就使用到享元模式。
  • 如果使用 valueOf 方法得到一个 Integer 实例,范围在 -128 - 127 ,执行速度比 new 快。

享元模式的注意事项和细节

  • 在享元模式这样理解,“享”就表示共享,“元”表示对象。
  • 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
  • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。
  • 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
  • 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
  • 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
  • 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。

代理模式

代理模式的基本介绍

  • 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。
  • 好处:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  • 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  • 代理模式有不同的形式,主要有三种 静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
  • 代理模式UML图:
    在这里插入图片描述

静态代理

  • 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。
  • 应用实例:
* 定义一个接口:ITeacherDao
* 目标对象TeacherDAO实现接口ITeacherDAO
* 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
* 调用的时候通过调用代理对象的方法来调用目标对象
* 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
  • 思路分析:
    在这里插入图片描述
  • 代码实现:
public class StaticProxyCase {
    public static void main(String[] args) {
        // 创建被代理对象
        TeacherDao teacherDao = new TeacherDao();
        // 创建代理对象,同时将被代理对象传递给代理对象
        TeacherDaoProxy proxy = new TeacherDaoProxy(teacherDao);
        // 通过代理对象,调用到被代理对象的方法
        proxy.teach();
    }
}
interface ITeacherDao {
    void teach();
}
class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师在上课...");
    }
}
/**
 * 代理对象
 */
class TeacherDaoProxy implements ITeacherDao {
    private ITeacherDao target;
    TeacherDaoProxy(ITeacherDao target) {
        this.target = target;
    }
    @Override
    public void teach() {
        System.out.println("开始代理...");
        target.teach();
        System.out.println("结束代理...");
    }
}
  • 静态代理优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
  • 静态代理缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类;一旦接口增加方法,目标对象与代理对象都要维护。

动态代理

  • 代理对象不需要实现接口了,但是目标对象要实现接口,否则不能用动态代理。
  • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。
  • 动态代理也叫做:JDK代理、接口代理。
JDK中生成代理对象的API
  • 代理类所在包:java.lang.reflect.Proxy
  • JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • 将前面的静态代理改进成JDK动态代理模式:
    在这里插入图片描述
  • 代码实现:
public class JDKDynamicProxyCase {
    public static void main(String[] args) {
        JDKProxyFactory proxy = new JDKProxyFactory(new TeacherDao());
        ITeacherDao teacherDao = (ITeacherDao) proxy.getProxyInstance();
        System.out.println("teacherDao = " + teacherDao + ", teacherDao.getClass = " + teacherDao.getClass());
        // class com.atguigu.dp.L11Proxy.$Proxy0 内存中动态生成了代理对象
        teacherDao.teach();
    }
}
class JDKProxyFactory {
    private Object target;

    JDKProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * public static Object newProxyInstance(ClassLoader loader,
     * Class<?>[] interfaces,
     * InvocationHandler h)
     * 1、loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
     * 2、interfaces:目标对象实现的接口类型,使用泛型确定类型
     * 3、h:事件处理,执行目标对象方法时,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK动态代理 Before...");
                        Object result = method.invoke(target, args);
                        System.out.println("JDK动态代理 After...");
                        return result;
                    }
                });
    }
}
Cglib代理模式
  • 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理。
  • Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
  • 在AOP编程中如何选择代理模式:标对象需要实现接口,用JDK代理;目标对象不需要实现接口,用Cglib代理
  • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
  • UML类图:
    在这里插入图片描述
  • 代码实现:
public class CglibDynamicProxyCase {
    public static void main(String[] args) {
        // 创建目标对象
        StudentDao target = new StudentDao();
        // 获取代理对象,并将目标对象传递给代理对象
        StudentDao studentDao = (StudentDao) new CglibProxyFactory(target).getProxyInstance();
        // 执行代理对象的方法
        studentDao.study();
    }
}
class StudentDao {
    public void study() {
        System.out.println("学生学习中...");
    }
}
class CglibProxyFactory implements MethodInterceptor {
    private Object target;
    CglibProxyFactory(Object target) {
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLib动态代理 Before...");
        Object result = method.invoke(target, args);
        System.out.println("CGLib动态代理 After...");
        return result;
    }
    public Object getProxyInstance() {
        // 1、创建一个工具类
        Enhancer enhancer = new Enhancer();
        // 2、设置父类
        enhancer.setSuperclass(target.getClass());
        // 3、设置回调函数
        enhancer.setCallback(this);
        // 4、创建子类对象,即代理对象
        return enhancer.create();
    }
}
  • 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

代理模式(Proxy)的变体

  • 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
  • 缓存代理:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
  • 远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
  • 同步代理:主要使用在多线程编程中,完成多线程间同步工作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章