结构型模式-装饰者&桥接&门面

一、装饰者模式

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

结构:

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

例如:快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

看看如何通过装饰着模式来设计,结构如下:

抽象构件

//快餐接口
public
abstract class FastFood { private float price;//价格 private String desc; //描述 public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public FastFood(float price, String desc) { this.price = price; this.desc = desc; } public FastFood() { } public abstract float cost(); }

具体构件

//炒饭
public class FriedRice extends FastFood {
​
    public FriedRice() {
        super(10, "炒饭");
    }
​
    public float cost() {
        return getPrice();
    }
}
​
//炒面
public class FriedNoodles extends FastFood {
​
    public FriedNoodles() {
        super(12, "炒面");
    }
​
    public float cost() {
        return getPrice();
    }
}

抽象装饰

//配料类
public abstract class Garnish extends FastFood {
​
    private FastFood fastFood;
​
    public FastFood getFastFood() {
        return fastFood;
    }
​
    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }
​
    public Garnish(FastFood fastFood, float price, String desc) {
        super(price,desc);
        this.fastFood = fastFood;
    }
}

具体装饰

//鸡蛋配料
public class Egg extends Garnish {
​
    public Egg(FastFood fastFood) {
        super(fastFood,1,"鸡蛋");
    }
​
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }
​
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}
​
//培根配料
public class Bacon extends Garnish {
​
    public Bacon(FastFood fastFood) {
​
        super(fastFood,2,"培根");
    }
​
    @Override
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }
​
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        //点一份炒饭
        FastFood food = new FriedRice();
        //花费的价格
        System.out.println(food.getDesc() + " " + food.cost() + "元");
​
        System.out.println("========");
        //点一份加鸡蛋的炒饭
        FastFood food1 = new FriedRice();
​
        food1 = new Egg(food1);
        //花费的价格
        System.out.println(food1.getDesc() + " " + food1.cost() + "元");
​
        System.out.println("========");
        //点一份加培根的炒面
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        //花费的价格
        System.out.println(food2.getDesc() + " " + food2.cost() + "元");
    }
}

饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。

装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

使用场景:

1、当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
  第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
  第二类是因为类定义不能继承(如final类)
2、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
3、当对象的功能要求可以动态地添加,也可以再动态地撤销时。

装饰者和代理同样都是为了增强对象的功能,两者的区别是什么呢?

相同点:
  都要实现与目标类相同的业务接口
  在两个类中都要声明目标对象
  都可以在不修改目标类的前提下增强目标方法
不同点:
  目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象
  获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象

二、桥接模式

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

假设现在有一个需求,需要在不同的操作系统windows、MAC播放不同格式RMVB、AVI的文件。我们可以利用继承的方式来设计,先设计一个接口,再定义不同的系统实现类,然后每个系统下面又是不同的视频格式实现。如果这样去实现,当我们增加一个操作系统跟文件格式时,就要增加很多种实现,最终造成类爆炸,这里可以采用桥接模式。

结构:

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

 

实现化角色 

//视频文件
public interface VideoFile {
    void decode(String fileName);
}

具体实现化角色

//avi文件
public class AVIFile implements VideoFile {
    public void decode(String fileName) {
        System.out.println("avi视频文件:"+ fileName);
    }
}
​
//rmvb文件
public class REVBBFile implements VideoFile {
​
    public void decode(String fileName) {
        System.out.println("rmvb文件:" + fileName);
    }
}

抽象化角色

//操作系统版本
public abstract class OperatingSystemVersion {
​
    protected VideoFile videoFile;
​
    public OperatingSystemVersion(VideoFile videoFile) {
        this.videoFile = videoFile;
    }
​
    public abstract void play(String fileName);
}

具体抽象化角色

//Windows版本
public class Windows extends OperatingSystem {
​
    public Windows(VideoFile videoFile) {
        super(videoFile);
    }
​
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}
​
//mac版本
public class Mac extends OperatingSystemVersion {
​
    public Mac(VideoFile videoFile) {
        super(videoFile);
    }
​
    public void play(String fileName) {
        videoFile.decode(fileName);
    }
}

测试

public class Client {
    public static void main(String[] args) {
        OperatingSystem os = new Windows(new AVIFile());
        os.play("战狼3");
    }
}

模式优点

1、桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。
2、实现细节对客户透明

使用场景

1、当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2、当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
3、当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

三、外观模式/门面模式

定义:通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

结构:

外观(Facade)角色:为多个子系统对外提供一个共同的接口。
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

案例:小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:

 

 

//灯类
public class Light {
    public void on() {
        System.out.println("打开了灯....");
    }
​
    public void off() {
        System.out.println("关闭了灯....");
    }
}
​
//电视类
public class TV {
    public void on() {
        System.out.println("打开了电视....");
    }
​
    public void off() {
        System.out.println("关闭了电视....");
    }
}
​
//控制类
public class AirCondition {
    public void on() {
        System.out.println("打开了空调....");
    }
​
    public void off() {
        System.out.println("关闭了空调....");
    }
}
​
//智能音箱
public class SmartAppliancesFacade {
​
    private Light light;
    private TV tv;
    private AirCondition airCondition;
​
    public SmartAppliancesFacade() {
        light = new Light();
        tv = new TV();
        airCondition = new AirCondition();
    }
​
    public void say(String message) {
        if(message.contains("打开")) {
            on();
        } else if(message.contains("关闭")) {
            off();
        } else {
            System.out.println("我还听不懂你说的!!!");
        }
    }
​
    //起床后一键开电器
    private void on() {
        System.out.println("起床了");
        light.on();
        tv.on();
        airCondition.on();
    }
​
    //睡觉一键关电器
    private void off() {
        System.out.println("睡觉了");
        light.off();
        tv.off();
        airCondition.off();
    }
}
​
//测试类
public class Client {
    public static void main(String[] args) {
        //创建外观对象
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        //客户端直接与外观对象进行交互
        facade.say("打开家电");
        facade.say("关闭家电");
    }
}

优点:

1、降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
2、对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

缺点:

不符合开闭原则,修改很麻烦

使用场景:

1、对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
2、当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
3、当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

其实在我们平时的编程当中,也会不经意间使用到这种模式,比如:我们想要获取一个用户的所有信息,我们不会关注调用了哪些系统(用户系统,会员系统,文件系统),我们只需要调用给我们提供的接口通过id获取就行,其实也算是一种门面模式 。

详细的教学请移步:https://www.bilibili.com/video/BV1Np4y1z7BU 

posted @ 2021-11-30 16:46  上官兰夏  阅读(94)  评论(0编辑  收藏  举报