当前位置:   article > 正文

设计模式之适配器模式、桥接模式、代理模式、装饰者模式_设计模式 主元带适配

设计模式 主元带适配


一、适配器模式

在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。

在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式分为类结构型模式对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

1.1、实现方式

1.1.1、需求

通过内存卡案例实现,现在有两种内存卡,一种是SD卡,一种是FT卡,电脑只能读取SD卡,所以需要将FT卡适配为SD卡

1.1.2、适配器模式角色

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者

1.1.3、类适配

  • 适配器必须继承适配者类,因为要使用适配着中的方法进行转换
  • 适配器实现目标类接口
// SD卡接口
public interface ISDCard {
	// 读数据
    String readSD();
    // 写数据
    void writeSD(String msg);
}

// SD卡实现类
public class SDCard implements ISDCard{
    @Override
    public String readSD() {
        String msg = "read sd hello world";

        return msg;
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("write sd " + msg);
    }
}

// FT卡接口
public interface IFTCard {
    String readFT();
    void writeFT(String msg);
}

// FT卡实现类
public class FTCard implements IFTCard{
    @Override
    public String readFT() {
        String msg = "read ft hello world";
        return msg;
    }

    @Override
    public void writeFT(String msg) {
        System.out.println("write ft " + msg);
    }
}

// 电脑类,只支持读写SD卡中内容
public class Compter {

    public String readSD(ISDCard sdCard){
        if(sdCard == null) {
            throw new NullPointerException("sd card is not null");
        }
        return sdCard.readSD();
    }

    public void writeSD(ISDCard sdCard) {
        sdCard.writeSD("hello world");
    }
}

// 适配器类,继承FT卡实现类,实现SD卡接口,重写SD卡方法
public class FTAdapterSD extends FTCard implements ISDCard{
    @Override
    public String readSD() {
        System.out.println("适配FT卡读数据");
        return readFT();
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("适配FT卡写数据");
        writeFT(msg);
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Compter compter = new Compter();
        SDCard sdCard = new SDCard();
        System.out.println("插入SD卡");
        System.out.println(compter.readSD(sdCard));
        compter.writeSD(sdCard);

        System.out.println("插入FT卡转换器");
        FTAdapterSD ftAdapterSD = new FTAdapterSD();

        System.out.println(compter.readSD(ftAdapterSD));
        compter.writeSD(ftAdapterSD);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

违反了合成复用原则,如果目标对象不是接口,而是一个抽象类就无法实现,因为Java不支持多继承,所以多数情况下还是使用对象适配。

1.1.4、对象适配

// 适配器类,
// 将适配者当做对象引用进来即可。不再继承FT卡实现类,而是当做对象依赖进来
public class FTAdapterSD implements ISDCard {
    private FTCard ftCard;

    public FTAdapterSD(FTCard ftCard) {
        this.ftCard = ftCard;
    }

    @Override
    public String readSD() {
        System.out.println("适配FT卡读数据");
        return ftCard.readFT();
    }

    @Override
    public void writeSD(String msg) {
        System.out.println("适配FT卡写数据");
        ftCard.writeFT(msg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

还有一种适配器模式是接口适配。当不希望实现一个接口中所有方法时,可以创建一个抽象类Adapter,实现所有方法,而此时我们只需要继承该抽象类即可

1.2、适配器模式优缺点

优点

  • 客户端通过适配器可以透明的访问目标接口
  • 复用现存的类,程序员不需要修改原有代码而重用现有的适配者类
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致问题
  • 在很多业务场景中符合开闭原则

缺点

  • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱

1.3、适配器模式应用场景

  • 以前开发系统存在满足新系统功能需求的类,但其接口和新系统不一致
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不一样。

二、桥接模式

现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。

当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。

桥接模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

2.1、实现方式

2.1.1、案例需求

开发一个跨平台的播放器,可以在不同的操作系统(Windows,Mac,Linux)上播放不同格式的视频(RMVB、AVI、WMV等)该播放器包含两个维度,适合使用桥接模式

2.1.2、桥接模式角色

桥接模式将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。

  • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

2.1.3、代码实现

// 实现化角色,解码视频进行播放
public interface IVedioFile {
    // 解码功能
    void decode(String fileName);
}

// 具体实现化角色,可以对avi格式视频进行解码
public class AviVedioFile implements IVedioFile{
    @Override
    public void decode(String fileName) {
        System.out.println("avi视频文件:" + fileName);
    }
}
// 可以对Rmvb格式视频进行解码
public class RmvbVedioFile implements IVedioFile{
    @Override
    public void decode(String fileName) {
        System.out.println("Rmvb格式播放:" + fileName);
    }
}

// 抽象化角色
public abstract class AbstractOpratingSystem {

    protected IVedioFile vedioFile;

    public AbstractOpratingSystem(IVedioFile vedioFile) {
        this.vedioFile = vedioFile;
    }

    abstract void play(String fileName);
}

// 抽象化扩展类,window上的视频播放
public class WindowOpratingSystem extends AbstractOpratingSystem{

    public WindowOpratingSystem(IVedioFile vedioFile) {
        super(vedioFile);
    }

    @Override
    void play(String fileName) {
        vedioFile.decode(fileName);
    }
}
// Linux上的视频播放
public class LinuxOpratingSystem extends AbstractOpratingSystem{

    public LinuxOpratingSystem(IVedioFile vedioFile) {
        super(vedioFile);
    }

    @Override
    void play(String fileName) {
        vedioFile.decode(fileName);
    }
}


public class Client {
    public static void main(String[] args) {
        WindowOpratingSystem windowOpratingSystem = new WindowOpratingSystem(new AviVedioFile());
        windowOpratingSystem.play("流浪地球");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

2.2、桥接模式优缺点

通过上面的讲解,我们能很好的感觉到桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。

优点

  • 抽象与实现分离,扩展能力强
  • 符合开闭原则
  • 符合合成复用原则
  • 实现细节对客户透明

缺点

由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

2.3、桥接模式应用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

三、代理模式

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。代理模式分为静态代理动态代理

3.1、静态代理

我们以卖火车票案例进行介绍

// 抽象主题,也就是售卖火车票的方法
public interface SellTickets {
    void sell();
}

// 真实主题类,真正的火车票是火车站售出的
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

// 代理类(代售点)
public class ProxyPoint implements SellTickets{

    protected TrainStation trainStation = new TrainStation();
    @Override
    public void sell() {
        System.out.println("代理点收取一些费用");
        trainStation.sell();
    }
}

public class Client {
    public static void main(String[] args) {
    	// 创建代理对象 
        ProxyPoint proxyPoint = new ProxyPoint();
        // 调用代理对象售票方法
        proxyPoint.sell();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

3.2、JDK动态代理

JDK提供了一个动态代理类Proxy,Proxy并不是代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance)来获取代理对象

// 定义售票接口
public interface SellTickets {

    void sell();
}
// 火车站实现售票接口,获取售票方法
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
// 代理工厂类,生产代理对象
public class ProxyFactory {
    // 引用目标对象,也就是火车站对象
    private TrainStation trainStation = new TrainStation();
	// 创建代理对象
    public SellTickets getProxy() {
        // 返回代理对象

        /**
         * ClassLoader loader,
*          Class<?>[] interfaces,
*          InvocationHandler h
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy:代理对象,和 proxyObject 是同一个
                     * @param method:对接口中的方法封装的Method函数
                     * @param args:被调用的方法的实际参数
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点出售火车票,收取一些服务费用");
                        // 执行目标方法
                        Object obj = method.invoke(trainStation, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

public class Client {
    public static void main(String[] args) {
    	// 创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        // 获取代理对象
        SellTickets proxy = proxyFactory.getProxy();
        proxy.sell();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

3.2.1、JDK动态地理细节

ProxyFactory并不是代理对象,JDK动态代理的代理对象是在运行中创建在内存中,类名为$Proxy0

  • 代理类 $Proxy0实现了 SellTickets,真实类和代理类都要实现相同的接口
  • 代理类将我们提供了匿名内部类对象传递给了父类

执行流程:

  • 在测试类中通过代理对象调用sell方法
  • 根据多态特征,执行的是代理类中的sell方法
  • 代理类中sell方法中又调用了 InvocationHandler 接口的自实现类对象的invoke方法
  • invoke方法通过反射执行了真实对象所属类中的sell方法

3.3、CGLIB动态代理

如果没有定义SellTickets接口,之定义了火车站类,那么JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包,它为没有实现接口的类提供代理,为JDK的动态代理提供很好的补充

3.3.1、引入maven依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.9</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

3.3.2、代码实现

public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }

}

// 代理
public class ProxyFactory implements MethodInterceptor {

    private TrainStation trainStation = new TrainStation();

    public TrainStation getProxyObject() {
        // 创建Enhancer对象,类似于JDK代理中的Proxy
        Enhancer enhancer = new Enhancer();
        // 设置父类字节码对象
        enhancer.setSuperclass(TrainStation.class);

        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();

        return proxyObject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB代替售票");
        // 调用方法,并接收返回值
        Object obj = method.invoke(trainStation, objects);
        return obj;
    }
}

public class Client {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        TrainStation proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

3.4、三种代理方式对比

3.4.1、CGLIB和JDK代理区别

CGLIB底层使用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一要注意的是,CGLIB不能对声明为final的类或方法进行代理,因为CGLIB原理是动态生成被代理类的子类。JDK1.8之后JDK代理比CGLIB代理效率要高。所以如果有接口使用JDK代理,没有接口使用CGLIB代理

3.4.2、静态代理和动态代理区别

动态代理与静态代理相比较,最大的好处在于接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理一样每一个方法都要重写。

如果接口新增一个方法,如果是静态代理,代理类和实现类都要改动。而动态代理就不会有这种问题。

3.5、代理模式优缺点

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

优点

  • 代理模式在客户端和目标对象之间起到一定中介作用和保护目标的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低系统的耦合度,增强程序的可扩展性

缺点

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢
  • 增加系统复杂度

3.6、应用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
比如Spring中的AOP就是使用的动态代理,可以实现事务、日志、性能检测等功能

四、装饰者模式

上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题。有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,都是装饰器模式。

在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。

装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

4.1、实现方式

4.1.1、装饰者模式角色

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

4.1.2、代码实现

我们通过给饮料添加各种佐料,比如添加牛奶,加冰等来对元饮品进行装饰

// 抽象的饮料类
public abstract class Drick {

    // 描述
    public String desc;

    // 价格
    private float price;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
    
    // 抽象的价格计算方式
    abstract float cost();
}

// 咖啡类 抽象构件
public class Coffee extends Drick{

    // 单品咖啡成本计算
    @Override
    float cost() {
        return super.getPrice();
    }
}

// 装饰者类,聚合被装饰者,抽象装饰角色
public class Decorator extends Drick{

    // 组合饮品
    private Drick drick;

    public Decorator(Drick drick) {
        this.drick = drick;
    }

    public void setDrick(Drick drick) {
        this.drick = drick;
    }

    @Override
    float cost() {
        // 组合进行价格计算
        return super.getPrice() + drick.cost();
    }

    @Override
    public String getDesc() {
        return desc + "  " + getPrice() + "  " + drick.getDesc();
    }
}

// 具体饮品,具体构件角色
public class EspreeCoffee extends Coffee{

    public EspreeCoffee() {
        setDesc(" 意大利咖啡 ");
        setPrice(6.0f);
    }
}


public class LongBlack extends Coffee{
    public LongBlack() {
        setDesc(" longblack ");
        setPrice(5.0f);
    }
}

public class ShortBlack extends Coffee{

    public ShortBlack() {
        setDesc(" shortBlack ");
        setPrice(4.0f);
    }
}

// 具体装饰类 具体装饰角色
public class Chocolate extends Decorator{

    public Chocolate(Drick drick) {
        super(drick);
        setDesc(" 巧克力 ");
        setPrice(3.0f);
    }
}

public class Milk extends Decorator{

    public Milk(Drick drick) {
        super(drick);
        setDesc(" 牛奶 ");
        setPrice(2.0f);
    }

}

public class Soy extends Decorator{
    public Soy(Drick drick) {
        super(drick);
        setDesc(" 豆浆 ");
        setPrice(1.5f);
    }
}

// 使用
public class Client {
    public static void main(String[] args) {

        // 2分巧克力 + 1份牛奶 + 一个LongBlack咖啡
        Drick order = new LongBlack();
        System.out.println("一杯LongBlack咖啡费用是:" + order.cost());
        // 加一份牛奶
        order = new Milk(order);
        System.out.println("一杯LongBlack咖啡 + 一份牛奶的费用是:" + order.cost());

        // 加一分巧克力
        order = new Chocolate(order);
        System.out.println("一杯LongBlack咖啡 + 一份牛奶 + 1份巧克力的费用是:" + order.cost());

        // 在加一分巧克力
        order = new Chocolate(order);
        System.out.println("一杯LongBlack咖啡 + 一份牛奶 + 2份巧克力的费用是:" + order.cost());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138

4.2、优缺点

优点

  • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
  • 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
  • 装饰器模式完全遵守开闭原则

缺点

  • 装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

4.3、应用场景

  • 需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

总结

本章介绍了结构型设计模式中的适配器模式桥接模式代理模式装饰者模式,下一章介绍组合模式外观模式享元模式

码字不易,还请看过之后给个三连啦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/68953
推荐阅读
相关标签
  

闽ICP备14008679号