设计模式第三弹 - 结构型模式 [一]

76 阅读12分钟

哈喽,大家好,我是janker。

叨叨

又来更新设计模式了,阿雄已经很久没有教育我了。没了他的鞭策我更新不出来,修正一下子,我重新来过。之前创建型的设计模式写的太单调了,看到课文类的文章就头疼,不好消化,没啥意思,搞得我自己都不想看,今天换个思路,用小故事聊聊结构型的设计模式。

正文

适配器模式

适配器的场景在生活中还是很常见的。

最近隔壁的小姐姐买了新款的苹果13 Pro,手机电用完了,官方又不送充电器(新买的充电器还没到),来找阿峰来借充电器,阿峰恰好用的是安卓手机,充电器接口自然不通用,如果满足不了小姐姐的需求小姐姐自然会不高兴,想起之前有个安卓转苹果接口的转接头,bingo! 安卓手机接上转接头就可以满足小姐姐的需求了。

定义与特点

将一个类的接口转换为另一个类的接口,使得原本使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

实现 - 对象适配器

基于以上的小故事我们来实现一下,体验一波。

对象适配器模式的结构图如图

1. IPhone充电器类(AndroidPower)

public class AndroidPower {
    public void androidPower(){
        System.out.println("Android 手机充电器正在充电");
    }
}

2. 充电目标接口(PowerApi)

public interface PowerApi {
    //充电
    void power();
}

3. 充电适配器(PowerAdapter)

public class PowerAdapter implements  PowerApi {

    private AndroidPower androidPower;
    public PowerAdapter(AndroidPower androidPower){
        this.androidPower = androidPower;
    }
    @Override
    public void power() {
        System.out.println("充电器适配器开始适配");
        iphonePower.iphonePower();
    }
}

4. 测试类(PowerAdapterTest)

public class PowerAdapterTest {
    public static void main(String[] args) {
        PowerApi powerApi = new PowerAdapter(new AndroidPower());
        powerApi.power();
    }
}

5. 测试结果

充电器适配器开始适配
Android 手机充电器正在充电

实现 - 类适配器

以上充电目标接口与IPhone充电器类都一样,只不过说适配器实现有所不同,上面的实现我们可以看出我们创建了新对象进行适配下面的一种方式直接不需要创建对象进行适配。

类适配器模式的结构图如图

代码如下。

1. 充电器类适配器(PowerClassAdapter)

public class PowerClassAdapter extends AndroidPower implements PowerApi {

    @Override
    public void power() {
        System.out.println("充电器类适配器开始适配");
        androidPower();
    }
}

2. 充电器类适配器测试类(PowerClassAdapterTest)

public class PowerClassAdapterTest {
    public static void main(String[] args) {
        PowerApi powerApi = new PowerClassAdapter();
        powerApi.power();
    }
}

3. 运行结果

充电器类适配器开始适配
Android 手机充电器正在充电

以上就能完美解决小姐姐借充电器的问题,虽然不是用原装IPhone充电器,但是充电器适配器也能暂时解决小姐姐的问题。

桥接模式

小姐姐又来了,不过这次不是来寻求帮助,而是来为难阿峰的。

问题:目前有几种图形,按形状分有两种,按颜色分有2种,请问最多有多少种组合?能不能用一种解决方案画出来?

阿峰:排列组合的问题其实挺简单,一种方式画出来,有点为难啊?我考虑一下。

如果使用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。

我们能不能把抽象的概念与实现分离呢,抽象出形状、颜色等概念,多种实现自由组合,利用组合方式解决这个问题。很容易我们就想到了桥接模式。

定义与特点

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

通过以上的例子,我们能很好的感觉到桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。桥接模式的优点有哪些呢?

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

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

实现

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

模式的结构

桥接(Bridge)模式包含以下主要角色。

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

拿图形的形状与颜色组合为例,其结构图如图。

1. 创建桥接实现接口。

public interface DrawAPI {
   public void drawCircle(int radius, int x, int y);
}

2. 创建实现了 DrawAPI 接口的实体桥接实现类。

public class RedCircle implements DrawAPI {
   @Override
   public void drawCircle(int radius, int x, int y) {
      System.out.println("Drawing Circle[ color: red, radius: "
         + radius +", x: " +x+", "+ y +"]");
   }
}
public class GreenCircle implements DrawAPI {
   @Override
   public void drawCircle(int radius, int x, int y) {
      System.out.println("Drawing Circle[ color: green, radius: "
         + radius +", x: " +x+", "+ y +"]");
   }
}

3. 使用 DrawAPI 接口创建抽象类 Shape。

public abstract class Shape {
   protected DrawAPI drawAPI;
   protected Shape(DrawAPI drawAPI){
      this.drawAPI = drawAPI;
   }
   public abstract void draw();  
}

4. 创建实现了 Shape 抽象类的实体类。

public class Circle extends Shape {
   private int x, y, radius;
 
   public Circle(int x, int y, int radius, DrawAPI drawAPI) {
      super(drawAPI);
      this.x = x;  
      this.y = y;  
      this.radius = radius;
   }
 
   public void draw() {
      drawAPI.drawCircle(radius,x,y);
   }
}

5. 使用 Shape 和 DrawAPI 类画出不同颜色的圆。

public class BridgePatternDemo {
   public static void main(String[] args) {
      Shape redCircle = new Circle(100,100, 10, new RedCircle());
      Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
 
      redCircle.draw();
      greenCircle.draw();
   }
}

6. 执行程序,输出结果

Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[  color: green, radius: 10, x: 100, 100]

代理模式

小姐姐又又来了!

小姐姐大姨妈来了,但是手头姨妈巾不多了,小姐姐又来找阿峰帮忙了。

小姐姐:阿峰小哥哥,有个事儿能不能帮忙?

阿峰:有啥事尽管说。

小姐姐:能不能委托你帮我买个姨妈巾?

阿峰:也不是不行。(这不是表现的好时候吗),顺便再给小姐姐带点红糖。【舔得好,舔得妙,舔得呱呱叫,阿雄见了都得流泪。】

由于一些特殊原因,小姐姐不能去买姨妈巾,委托阿峰去买,阿峰不仅买了姨妈巾,还带了点红糖,这种模式就是代理模式。

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

定义与特点

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

优点

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

缺点

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

那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式

结构与实现

代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。

模式的结构

代理模式的主要角色如下。

  1. 抽象主题(Subject)类 BuyApi:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类 Person:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类 ProxyPerson:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

其结构图如图

模式的实现

1. 抽象类 BuyApi

public interface BuyApi {
    void buy();
}

2. 实际实现类(Person)

public class Person implements BuyApi {
    @Override
    public void buy() {
        System.out.println("去超市买点姨妈巾");
    }
}

3. 代理类(ProxyPerson)

public class ProxyPerson implements BuyApi {
    private Person person;
    @Override
    public void buy() {
        if (null == person){
            person = new Person();
        }
        person.buy();
        buyBrownSugar();
    }

    private void buyBrownSugar() {
        System.out.println("阿峰又买了点红糖");
    }
}

4. 测试类(ProxyPersonTest)

public class ProxyPersonTest {
    public static void main(String[] args) {
        ProxyPerson person = new ProxyPerson();
        person.buy();
    }
}

5. 运行结果

去超市买点姨妈巾
阿峰又买了点红糖

阿峰帮小姐姐买完姨妈巾,小姐姐非常感谢他,阿峰是个好人。(好人卡一张,嘎嘎)

###装饰器模式

经过阿峰这么多次帮忙,小姐姐与阿峰的关系越来越密切了。阿峰与小姐姐聊的越来越多,越来越投机。聊天中发现,小姐姐有睡懒觉的习惯,并且经常不吃早饭,而阿峰经常早起。

阿峰:小姐姐,你虽然睡懒觉,但是不吃早饭可不行呀。

小姐姐:我这没人疼没人爱的,不吃也没事儿。

阿峰:哪儿能呢,我早上来的早一些,我帮你带吧。

小姐姐:那多不好意思。

阿峰:没事儿,你要帮你带啥,跟我说就行。(就是舔,就是上赶子)

小姐姐:我喜欢吃鸡蛋灌饼(标配)。

阿峰:我也喜欢吃鸡蛋灌饼,加香肠的,明天帮你带哈。(标配加香肠)

本质上都是鸡蛋灌饼,只不过说阿峰的吃的鸡蛋灌饼是在原有产品基础上选配了一些料。这种模式就是装饰器模式。

定义与特点

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

装饰器模式的主要优点有:

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

其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

结构与实现

模式的结构

装饰器模式主要包含以下角色。

  1. 抽象构件(Component)角色 FillingCakeApi :定义一个抽象接口以规范准备接收附加责任的对象。(标准制作鸡蛋灌饼的接口)
  2. 具体构件(ConcreteComponent)角色 EggFillingCake:实现抽象构件,通过装饰角色为其添加一些职责。(具体实现制作鸡蛋灌饼的实现类)
  3. 抽象装饰(Decorator)角色 Decorator:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。(为灌饼增加新功能的抽象装饰类)
  4. 具体装饰(ConcreteDecorator)角色 SausageFillingCakeDecorator:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。(具体实现加香肠的装饰实现类)

装饰器模式的结构图如图

模式的实现

1. 抽象类(FillingCakeApi)

制作灌饼的接口

public interface FillingCakeApi {
    //做饼
    void generateCake();
}

2. 具体实现(EggFillingCake)

鸡蛋灌饼(标配)

public class EggFillingCake implements FillingCakeApi{
    public EggFillingCake(){
        System.out.println("鸡蛋灌饼初始化工作");
    }
    @Override
    public void generateCake() {
        System.out.println("制作鸡蛋灌饼");
    }
}

3. 抽象装饰类(Decorator)

定义需要装饰的接口

public class Decorator implements FillingCakeApi{
    private FillingCakeApi fillingCakeApi;
    public Decorator(FillingCakeApi fillingCakeApi){
        this.fillingCakeApi = fillingCakeApi;
    }
    @Override
    public void generateCake() {
        fillingCakeApi.generateCake();
    }
}

4. 具体装饰类(SausageFillingCakeDecorator)

加香肠鸡蛋灌饼装饰类

public class SausageFillingCakeDecorator extends Decorator {
    public SausageFillingCakeDecorator(FillingCakeApi fillingCakeApi) {
        super(fillingCakeApi);
    }

    @Override
    public void generateCake() {
        super.generateCake();
        //加根香肠
        addSausage();
    }

    private void addSausage() {
        System.out.println("鸡蛋灌饼中加一根香肠");
    }
}

5. 测试类(DecoratorPatternTest)

public class DecoratorPatternTest {
    public static void main(String[] args) {
        FillingCakeApi fillingCakeApi = new EggFillingCake();
        FillingCakeApi cakeApi = new SausageFillingCakeDecorator(fillingCakeApi);
        cakeApi.generateCake();
    }
}

6. 运行结果

鸡蛋灌饼初始化工作
制作鸡蛋灌饼
鸡蛋灌饼中加一根香肠

全文小结

由于篇幅原因,阿峰与小姐姐的故事就先告一段落,小故事都是其次,最主要是想让大家通过故事对设计模式相关的内容理解更加深刻,如果有举例不定的地方请留言,及时改进补充。点赞👍🏻,收藏☆,关注就是我最大的动力。

忙时做业绩,闲时修内功。我是janker,咱们下期见。

阿峰友情提示下期内容: 结构型模式 [二]