软件设计与体系结构——结构型模式(代码片段)

_瞳孔 _瞳孔     2022-12-19     200

关键词:

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔空间

结构型模式(Structural Pattern)描述如何将类或对象按某种布局组成更大的结构。就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构

结构型模式可以分为类结构型模式和对象结构型模式

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式

结构型模式分为以下7种:

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

一:代理模式

有时候客户端不能直接操作到某个对象,但又必须和那个对象有所互动,比如以下两种情况:

  • 如果对象是一个大图片,需要花费很长时间才能显示出来,此时需要做个图片Proxy来代替真正的图片
  • 如果对象在某远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象

如何应对这种变化?如何提供一种机制让原本交互起来比较困难的两个对象实现畅通无阻地交流呢?如何保持系统的结构不随着需求改变而轻易改变?这就是代理模式。

代理模式:为其他对象提供一种代理以控制对这个对象的访问。

代理模式分为三种角色:

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

Java中的代理按照代理类生成时机不同又分为:

  • 静态代理:静态代理代理类在编译期生成
  • 动态代理:而动态代理代理类在Java运行时动态生成。动态代理又有两种:
    • JDK代理
    • CGLib代理

1.1:静态代理

以下是火车站卖票的案例:

public class ProxyDemo1 
    public static void main(String[] args) 
        ProxyPoint1 proxyPoint = new ProxyPoint1();
        proxyPoint.sell();
    


// 售票接口
interface SellTickets1 
    void sell();


// 火车站
class TrainStation1 implements SellTickets1 
    @Override
    public void sell() 
        System.out.println("火车站卖票");
    


// 代售点卖票
class ProxyPoint1 implements SellTickets1 
    // 声明火车站类对象
    private TrainStation1 trainStation = new TrainStation1();

    @Override
    public void sell() 
        System.out.println("收取服务费");
        trainStation.sell();
    

从上面代码中可以看出主类(充当测试类)直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取了服务费用)。

1.2:动态代理

1.2.1:JDK代理

Java中提供了一个动态代理类Proxy,该类提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

改进火车站售票代码如下:

public class ProxyDemo2 
    public static void main(String[] args) 
        ProxyFactory2 proxyFactory = new ProxyFactory2();
        SellTickets2 proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    


// 售票接口
interface SellTickets2 
    void sell();


// 火车站
class TrainStation2 implements SellTickets2 
    @Override
    public void sell() 
        System.out.println("火车站卖票");
    


// 代理工厂,用于创建代理对象
class ProxyFactory2 
    private TrainStation2 station = new TrainStation2();

    public SellTickets2 getProxyObject() 
        return (SellTickets2)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                (proxy, method, args) -> 
                    /*
                     * proxy:与Proxy.newProxyInstance返回值为同一对象
                     * method:对接口中的方法进行封装的method对象
                     * args:调用方法的实际参数
                     */
                    System.out.println("收取服务费");
                    return method.invoke(station, args);
                
        );
    

1.2.2:CGLIB代理

如果没有定义SellTickets接口,只定义了TrainStation (火车站类)。很显然JDK代理是无法使用了,因为JDk动态代理要求必须定义接口,对接口进行代理。

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

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

改进火车站售票代码如下:

public class ProxyDemo3 
    public static void main(String[] args) 
        ProxyFactory3 proxyFactory3 = new ProxyFactory3();
        TrainStation3 proxyObject = proxyFactory3.getProxyObject();
        proxyObject.sell();
    


// 火车站
class TrainStation3 
    public void sell() 
        System.out.println("火车站卖票");
    


// 代理对象工厂
class ProxyFactory3 implements MethodInterceptor 
    private TrainStation3 station = new TrainStation3();

    public TrainStation3 getProxyObject() 
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperClass(TrainStation3.class);
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        return (proxyObject)enhancer.create();
    

    @Override
    public Object intercept(Object o, Method method, Object[] objects. MethodProxy methodProxy) 
        System.out.println("收取服务费");
        return method.invoke(station, objects);
    

1.3:总结

三种代理的对比:

  • JDK代理和CGLIB代理:使用CGLIB实现动态代理,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLIB不能对声明为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理类的子类。在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
  • 动态代理和静态代理:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

代理模式的优点:

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

代理模式的缺点:

  • 增加了系统的复杂度

使用场景:

  • 远程代理(Remote Proxy):本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
  • 虚拟代理(Virtual Proxy):在需要创建开销很大对象时缓存对象信息
  • 保护代理(Protection Proxy):控制对原始对象的访问。如果需要,可以给不同的用户提供不同级别的使用权限。
  • 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如记录访问的流量和次数等
  • 防火墙代理(Firewall Proxy):当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

二:适配器模式

如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220x转换为5x的电压),读卡器等,其实就是使用到了适配器模式。

适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用场合:

  • 使用一个已存在的类,而它的接口不符合要求
  • 创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
  • 使用一些已经存在的子类,但不可能通过子类化以匹配各自接口。对象适配器可以适配它的父类接口

适配器模式包含以下主要角色:

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

适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

2.1:类适配器

实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

例如,现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。代码如下:

public class AdapterDemo1 
    public static void main(String[] args) 
        Computer computer = new Computer();
        String msg = computer.readSD(new SDAdapterTF());
        System.out.println(msg);
    


interface TFCard 
    String readTF();

    void writeTF(String msg);


// 具体TF卡
class TFCardImpl implements TFCard 
    @Override
    public String readTF() 
        return "hello TFCard";
    

    @Override
    public void writeTF(String msg) 
        System.out.println("TFCard write msg:" + msg);
    


// 目标接口
interface SDCard 
    String readSD();

    void writeSD(String msg);


// 具体SD卡
class SDCardImpl implements SDCard 

    @Override
    public String readSD() 
        return "hello SDCard";
    

    @Override
    public void writeSD(String msg) 
        System.out.println("SDCard write msg:" + msg);
    


// 计算机类
class Computer 
    // 从SD卡中读取数据
    public String readSD(SDCard sdCard) 
        return sdCard.readSD();
    


// 适配器类
class SDAdapterTF extends TFCardImpl implements SDCard 
    @Override
    public String readSD() 
        return readTF();
    

    @Override
    public void writeSD(String msg) 
        writeTF(msg);
    

类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。

2.2:对象适配器

实现方式:对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

改进上述读卡器案例,代码如下:

public class AdapterDemo2 
    public static void main(String[] args) 
        Computer2 computer = new Computer2();
        String msg = computer.readSD(new SDAdapterTF2(new TFCardImpl2()));
        System.out.println(msg);
    


interface TFCard2 
    String readTF();

    void writeTF(String msg);


// 具体TF卡
class TFCardImpl2 implements TFCard2 
    @Override
    public String readTF() 
        return "hello TFCard";
    

    @Override
    public void writeTF(String msg) 
        System.out.println("TFCard write msg:" + msg);
    


// 目标接口
interface SDCard2 
    String readSD();

    void writeSD(String msg);


// 具体SD卡
class SDCardImpl2 implements SDCard2 

    @Override
    public String readSD() 
        return "hello SDCard";
    

    @Override
    public void writeSD(String msg) 
        System.out.println("SDCard write msg:" + msg);
    


// 计算机类
class Computer2 
    // 从SD卡中读取数据
    public String readSD(SDCard2 sdCard) 
        return sdCard.readSD();
    


// 适配器类
class SDAdapterTF2 implements SDCard2 
    // 声明适配者类
    private final TFCard2 tfCard;

    public SDAdapterTF2(TFCard2 tfCard) 
        this.tfCard = tfCard;
    

    @Override
    public String readSD() 
        return tfCard.readTF();
    

    @Override
    public void writeSD(String msg) 
        tfCard.writeTF(msg);
    

三:装饰者模式

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

而装饰者模式(Decorator Pattern),就是在不改变对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

装饰者模式中的角色

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

示例代码如下:

public class DecoratorDemo1 
    public static void main(String[] args) 
        // 点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());
    


abstract class FastFood 
    private float price;
    private String desc;

    public FastFood() 

    public FastFood(float price, String desc) 
        this.price = price;
        this.desc = 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 abstract float cost();


// 炒饭
class FriedRice extends FastFood 
    public FriedRice() 
        super(10, "炒饭");
    

    @Override
    public float cost() 
        return getPrice();
    


// 炒面
class FriedNoodles extends FastFood 
    public FriedNoodles() 
        super(12, "炒面");
    

    @Override
    public float cost() 
        return getPrice();
    


// 装饰类
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;
    


// 配料类(鸡蛋)
class Egg extends Garnish 

    public Egg(FastFood fastFood) 
        super(fastFood, 1, "鸡蛋");
    

    @Override
    public float cost() 
        // 计算价格
        return getPrice() + getFastFood().cost();
    

    @Override
    public String getDesc() 
        return super.getDesc() + getFastFood查看详情  

案例分析:设计模式与代码的结构特性(代码片段)

...iOS版本中找到适用于Cocoa的设计模式。基于模式的机制和体系结构在Cocoa框架和Objective-C的runtime和语言中很常见。Cocoa经常把自己独特的旋律放在一个模式上,因为它的设计受语言能力或现有体系结构等因素的影响。  本节包含... 查看详情

c++设计模式(代码片段)

...positeIteratorChainofResposibilityChainofResposibility动机(Motivation)在软件构建过程中 查看详情

c++设计模式(代码片段)

...模式CompositeIteratorChainofResposibilityComposite动机(Motivation)在软件在某些情况下,客户代码过多地依赖 查看详情

软件设计模式及体系结构之桥接模式(代码片段)

桥接模式前言分析:蜡笔:颜色和型号两个不同的变化维度(即两个化原因)耦合在一起,无论是对颜色进行扩展还对型号进行扩展都势必会影响另一个维度毛笔:颜色和型号实现了分离,增加新的颜色或者型号对另一方没有任何影... 查看详情

结构型模式-组合模式(树形结构的处理)(代码片段)

目录1.定义2.结构3.代码实现4.透明组合模式与安全组合模式4.1透明组合模式4.1安全组合模式5.优缺点6.适用场景7.个人理解参考树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织... 查看详情

1设计模式组合模式(代码片段)

...:★★★☆☆,使用频率:★★★★☆】 树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的... 查看详情

医学信息与软工(代码片段)

...数命名准则等),以便协调组内各成员的工作.(2)软件体系结构的总体设计 查看详情

设计模式(34)-----结构型模式-----桥接设计模式(代码片段)

...解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的... 查看详情

结构型设计模式之代理模式(代码片段)

@TOC代理模式分类1.静态代理:静态定义代理类2.动态代理:动态生成代理类主要角色代理模式一般包含三种角色:1.抽象主题角色(Subject)2.真实主题角色(RealSubject)3.代理主题角色(Proxy)作用1.保护目标对象,将代理对象与真实被调用... 查看详情

设计模式-结构型模式_桥接模式(代码片段)

文章目录结构型模式概述CaseBadImplBetterImpl小结结构型模式结构型模式主要是解决如何将对象和类组装成较大的结构,并同时保持结构的灵活和⾼效。结构型模式包括:适配器、桥接、组合、装饰器、外观、享元、代理ÿ... 查看详情

设计模式-结构型模式_桥接模式(代码片段)

文章目录结构型模式概述CaseBadImplBetterImpl小结结构型模式结构型模式主要是解决如何将对象和类组装成较大的结构,并同时保持结构的灵活和⾼效。结构型模式包括:适配器、桥接、组合、装饰器、外观、享元、代理ÿ... 查看详情

设计模式-结构型(代码片段)

设计模式-结构型结构型设计模式包含:代理模式、适配器模式、桥接模式、装饰模式、外观设计模式、享元模式、组合模式代理模式核心是在具体的功能类与使用者之间建立一个中介类作为代理,使用者通过代理对象对... 查看详情

9bridge桥梁模式将类的功能层次结构与实现层结构分离结构型设计模式(代码片段)

1、何为桥接模式桥接模式是一种将类的功能层次和实现层次分离的技术,所谓类的功能层次指的是类要实现什么功能,要定义多少个函数还进行处理,在功能之中我们会用到继承来定义新的方法同时也能使用父类的方法,这样... 查看详情

63.display:none与visibility:hidden的区别?(代码片段)

浅谈设计模式之结构型模式前言通过学习设计模式,我们知道根据目的、用途的不同,把设计模式分为创建型模式、结构型模式、行为型模式。创建型模式主要用于创建对象;结构型模式主要用于处理类和对象的组合;行为型模... 查看详情

go中设计模式之结构型模式(代码片段)

外观模式1.定义:外部与一个子系统通信必须通过一个统一的对象进行,为子系统中的一组接口提供一致界面。2.代码示例://定义对外APItypeAPIinterfaceTest()funcNewAPI()APIreturnapiImplnewMod()typeapiImplstructmmodfunc(aapiImpl)Test()a.m.mod()//需要交... 查看详情

java设计模式之适配器学习与掌握(代码片段)

...而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高优点在很多业务场景中符合开闭原则。客户端通过适配器可以透明地调用目标接口。复用了 查看详情

结构型设计模式之桥接模式(代码片段)

Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。 ... 查看详情