设计模式之结构型模式(5种)

目录

结构型模式(Structural Pattern):怎么构造一个对象(行为、属性)

一、适配器模式

          二、桥接模式(Bridge)

三、装饰者模式

        设计模式在JAVA I/O库中的应用

案例

        使用前

        使用后

总结: 

四、外观模式

案例

使用前 

使用后

总结:

五、代理模式

分类: 

静态代理

Cglib代理 ​

 总结

jdk代理与Cglib代理比较:

应用


结构型模式(Structural Pattern):怎么构造一个对象(行为、属性)

  1. 适配器模式   Adapter Pattern
  2. 桥接模式    Bridge  Pattern
  3. 组合模式   Composite  Pattern
  4. 装饰模式  Decorator  Pattern
  5. 外观模式   Façade  Pattern
  6. 享元模式   Flyweight  Pattern
  7. 代理模式    Proxy  Pattern

一、适配器模式

适配器介绍:
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
适配器模式的定义:
把一个类的接口变成客户端所期待的另一个接口,使本来不不匹配二无法再一起工作的类可以在一起工作。
使用场景

SpringMvc框架中HandlerAdapter类
1)系统需要使用现有的类,而类的接口不符合要求
2)需要建立一个可以重复使用的类,用于一些彼此之间没有太大关联的类
3)需要一个统一的接口,而输入类型不确定

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

案例:

需求:

电源适配的项目,要让手机能够使用220V的交流电;注意,手机只接受5.5V的电压;

  • 术语

    • source:待适配的类/对象/接口

    • Adapter:适配器

    • destination:适配后可用的类/对象/接口

  • 角色

    • sourcePower220V===》220V的电源

    • AdapterPowerAdapt===》电源适配器

    • DestinationPower5V===》5V的电源

  • 分类

    • 类适配器模式Adapter 类,通过继承 source 类,实现 Destination 类接口,完成 source->Destination 的适配。

    • 对象适配器模式将 Adapter 类作修改,不是继承 source 类,而是持有 source 类的实例,以解决兼容性的问题。 即:持有 source 类,实现 Destination 类接口,完成source->Destination 的适配

    • 接口适配器模式当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求 适用于一个接口不想使用其所有的方法的情况

适配器模式分类三种体现形式思想都大同小异,这里就只介绍最常用的 对象适配器模式;

举例二:假如我们要做一个下单操作,需要两个表 Order 订单 orderItem 订单项目=》需要写两个方法OrderDao.add(order)\OrderItemDao.add(oi);但是在某一些例子中,前台传递待后台的是order,而后台需要使用的却是OrderHis,所以这里要使用到我们的适配器。

设计模式之结构型模式(5种)_第1张图片

 示例代码:

package com.javaxl.design.adapter.before;

/**
 * @author 周大福ye
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 15:51
 *
 * 220V的电压
 */
public class Voltage220V {
    private double voltage;

    public Voltage220V() {
        this.voltage = 220;
    }

    public double getVoltage() {
        return voltage;
    }

    public void setVoltage(double voltage) {
        this.voltage = voltage;
    }
}

 方法中参数的Voltage220V voltage220V是不合理的,应该是适配器

例如:Person人:吃饭、睡觉、休息

       Dog 狗:吃饭、睡觉、休息

人和狗的习惯大致相似,那么Dog能不能继承Preson呢?答案:看似正确,但是不合理,上述方法中也是同理。

public class Phone {
//    充电
    public void charge(Voltage220V voltage220V){
        double voltage = voltage220V.getVoltage() / 40;
        System.out.println("最终手机充电所用电压:" + voltage + "V");
    }
}
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
//        已知有一个220V的电源,要用它给手机进行充电,我们只能将220V的电源进行处理后才能给手机充上电
//        还一种方案:新增5V的一个Voltage5V,Voltage的电压可以被手机使用
//        但是这违背现实生活现象,现实生活中只有220V的电源,其他的电源都是通过适配得来的
        phone.charge(new Voltage220V());
    }
}

使用适配器后(充电头相当于适配器)

package com.javaxl.design.adapter.after;

/**
 * @author 周大福ye
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 15:51
 *
 * 220V的电压
 */
public class Voltage220V {
    private double voltage;

    public Voltage220V() {
        this.voltage = 220;
    }

    public double getVoltage() {
        return voltage;
    }

    public void setVoltage(double voltage) {
        this.voltage = voltage;
    }
}
/**
 * 目标接口
 */
interface Voltage5V{
    double getVoltage();
}

/**
 * 适配器:里面封装了source源到destination目标的过程
 */
class VoltageAdapter implements  Voltage5V{
    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public double getVoltage() {
        return voltage220V.getVoltage() / 40;
    }
}
package com.javaxl.design.adapter.after;


public class Phone {
//    充电
    public void charge(Voltage5V voltage5V){
        double voltage = voltage5V.getVoltage();
        System.out.println("最终手机充电所用电压:" + voltage + "V");
    }
}
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
//        已知有一个220V的电源,要用它给手机进行充电,我们只能将220V的电源进行处理后才能给手机充上电
//        VoltageAdapter适配器对Voltage220V这个不能直接使用的电源适配后就可以使用了
        phone.charge(new VoltageAdapter(new Voltage220V()));
    }
}

通过适配器把220V电压进行适配。

适配器: VoltageAdapter

Voltage220V()相当于上面例子中的order

voltage相当于上面例子中的OrderHis

二、桥接模式(Bridge)

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

意图:将抽象部分与实现部分分离,使它们都可以独立的变化。

主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。

如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

应用场景:JDBC的Driver驱动类

关键代码:抽象类依赖实现类。

优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。

缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

  • 术语

    • Bridge:桥接

    • Abstraction:抽象类

    • Implementor:实现

    • concrete:具体的

  • 角色

    • Client 类:桥接模式的调用者

    • Abstraction:维护了 Implementor/ 即它的实现类 ConcreteImplementorA.., 二者是聚合关系,Abstraction充当桥接类

    • xxxAbstraction :抽象的具体子类

    • Implementor :行为实现类的接口

    • ConcreteImplementorA /B:具体行为的实现类A、B

  • 案例

    需求:手机(型号 + 品牌)操作问题;

    ​ 完成手机各品牌各型号的项目设计;

    ​ 列如:折叠式的华为、小米、Vivo,直立式的华为、小米、Vivo,旋转式的、滑盖的...

    ​ 要求该项目设计易于扩展

设计模式之结构型模式(5种)_第2张图片

设计模式之结构型模式(5种)_第3张图片

上图是常见的需求设计方案,非常不便于维护,手机的型号与品牌耦合度太高,当要新增滑盖(Slide)式的手机时,对应的品牌手机也要新增;

手机品牌有很多,就需要创建的子类就很多,品牌依托与规格,耦合性太高,很臃肿。

解决方案:

有多少个规格就设计多少个规格类,有多少个品牌就设计多少个品牌类?把它们相互组合(桥接)如下图

设计模式之结构型模式(5种)_第4张图片

 示例代码

抽象部分包含实现

package com.javaxl.design.bridge;

/**
 * @author 周大福ye
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 17:29
 * 

* 手机型号 */ public abstract class Abstraction { protected Implementor implementor; public abstract void call(); } class Folded extends Abstraction { private String name = "折叠式"; Folded(Implementor implementor) { this.implementor = implementor; } @Override public void call() { System.out.println(this.implementor.getName() + this.name + "正在通话中"); } } class Upright extends Abstraction { private String name = "直立式"; Upright(Implementor implementor) { this.implementor = implementor; } @Override public void call() { System.out.println(this.implementor.getName() + this.name + "正在通话中"); } } class Slide extends Abstraction { private String name = "滑盖式"; Slide(Implementor implementor) { this.implementor = implementor; } @Override public void call() { System.out.println(this.implementor.getName() + this.name + "正在通话中"); } }

package com.javaxl.design.bridge;

/**
 * @author 周大福ye
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 17:29
 * 手机品牌
 */
public interface Implementor {
    String getName();
}

class HW implements Implementor{
    private String name = "华为";

    @Override
    public String getName() {
        return name;
    }
}

class Mi implements Implementor{
    private String name = "小米";

    @Override
    public String getName() {
        return name;
    }
}

class Vivo implements Implementor{
    private String name = "Vivo";

    @Override
    public String getName() {
        return name;
    }
}
public class Client {
    public static void main(String[] args) {
        Folded folded = new Folded(new HW());
        folded.call();

        Upright upright = new Upright(new Mi());
        upright.call();

        Slide slide = new Slide(new Vivo());
        slide.call();
    }
}

后台输出结果:

设计模式之结构型模式(5种)_第5张图片

从结果可以看出来:

​ 使用桥接模式,对该项目进行设计,型号或品牌的扩展,都不会影响另一方;

即手机型号扩展,手机品牌不受影响;手机品牌的上市退市,不会影响到手机型号;

 注意事项及细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性

  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成

  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本

三、装饰者模式

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
 

术语:
Component:组件(主体)

concreteComponent:被装饰者

Decorator:装饰者

注意:concreteComponent、Decorator都会实现或继承Component

角色:

Client 类:装饰模式的调用者

Component:主体

concreteComponent:被装饰者

xxxConcreteComponent:具体的被装饰者

Decorator:装饰器

xxxDecorator:子装饰器


设计模式之结构型模式(5种)_第6张图片

1、Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
2、ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
3、Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
4、ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
 

设计模式在JAVA I/O库中的应用

装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。

  由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。

  Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。
设计模式之结构型模式(5种)_第7张图片

 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。
 

案例

单体咖啡与调味组合的饮料计价项目

使用前

这个项目最容易想到的设计方案就是采用继承的设计方案,设计思路如下:

Drink===》饮品

Juice===》果汁

.......

Coffee===》咖啡

ChinaCoffee===》中式咖啡

ASeasoningChinaCoffee===》被A调料修饰的中式咖啡

BSeasoningChinaCoffee===》被B调料修饰的中式咖啡

......

XxxCoffee===》其它咖啡

ASeasoningXxxCoffee===》被A调料修饰的其它咖啡

BSeasoningXxxCoffee===》被B调料修饰的其它咖啡

......

上面每个类中都有getPrice计价的功能,从结果上来看,确实可以完成项目需求,但是整个设计体系过于臃肿,不便于后期的扩展与维护;
 

 /**
 * 饮料包括单体咖啡+调料
 */
public abstract class Drink {
    protected double price;
    protected int n;
    protected DrinkSeasoning seasoning;
 
    public abstract double getPrice();
}
 
/**
 * 单体咖啡
 */
abstract class Coffee extends Drink {
}
 
/**
 * 单体果汁
 */
abstract class Juice extends Drink {
}
 
class ChinaCoffee extends Coffee{
    ChinaCoffee(double price,int n){
        this.price = price;
        this.n = n;
    }
 
    @Override
    public double getPrice() {
        return this.price*this.n+this.seasoning.getPrice();
    }
}
 
 
/**
 * @author 小李飞刀
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 18:32
 *
 * 调料的抽象接口
 */
public interface DrinkSeasoning {
    public abstract double getPrice();
}
 
/**
 * A类调料
 */
class ADrinkSeasoning implements  DrinkSeasoning{
    protected double price;
    protected int n;
    ADrinkSeasoning(double price,int n){
        this.price = price;
        this.n = n;
    }
    @Override
    public double getPrice() {
        return this.price*this.n;
    }
}
 
 
/**
 * B类调料
 */
class BDrinkSeasoning implements  DrinkSeasoning{
    private double price;
    protected int n;
    BDrinkSeasoning(double price,int n){
        this.price = price;
        this.n = n;
    }
    @Override
    public double getPrice() {
        return this.price*this.n;
    }
}
 
 
public class Client {
    public static void main(String[] args) {
        ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
        ADrinkSeasoning aDrinkSeasoning = new ADrinkSeasoning(2,2);
        chinaCoffee.seasoning = aDrinkSeasoning;
        System.out.println("中式咖啡1份+A调料2份,最终价格为:"+chinaCoffee.getPrice());
 
//        思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
//        思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
    }
}

使用后

设计模式之结构型模式(5种)_第8张图片

 * 饮料包括单体咖啡+调料
 */
public abstract class Drink {
    protected double price;
    protected int n;
 
    public abstract double getPrice();
}
 
/**
 * 单体咖啡
 */
abstract class Coffee extends Drink {
}
 
/**
 * 单体果汁
 */
abstract class Juice extends Drink {
}
 
class ChinaCoffee extends Coffee {
    ChinaCoffee(double price, int n) {
        this.price = price;
        this.n = n;
    }
 
    @Override
    public double getPrice() {
        return this.price * this.n;
    }
}
 
 
package com.javaxl.design.decorator.after;
 
/**
 * @author 小李飞刀
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 22:26
 */
public class DecoratorDrink extends Drink {
    private Drink drink;
 
    public DecoratorDrink(Drink drink, double price, int n) {
        this.drink = drink;
        this.price = price;
        this.n = n;
    }
 
    @Override
    public double getPrice() {
        return this.price * this.n + drink.getPrice();
    }
}
 
class ADecoratorDrink extends DecoratorDrink {
    public ADecoratorDrink(Drink drink, double price, int n) {
        super(drink, price, n);
    }
}
 
class BDecoratorDrink extends DecoratorDrink {
    public BDecoratorDrink(Drink drink, double price, int n) {
        super(drink, price, n);
    }
}
 
 
package com.javaxl.design.decorator.after;
 
 
/**
 * @author 小李飞刀
 * @site www.javaxl.com
 * @company
 * @create  2020-02-22 18:50
 */
public class Client {
    public static void main(String[] args) {
        ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
//        假定A类调料2元一份,B类调料3元一份
        Drink order = new ADecoratorDrink(chinaCoffee, 2, 2);
        System.out.println("中式咖啡1份+A调料2份,最终价格为:"+order.getPrice());
 
//        思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
        order = new ADecoratorDrink(order,2,1);
        System.out.println("式咖啡1份+A调料3份,最终价格为:"+order.getPrice());
        order = new BDecoratorDrink(order,3,2);
        System.out.println("式咖啡1份+A调料3份+B调料2份,最终价格为:"+order.getPrice());
 
//        思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
        order = new BDecoratorDrink(order,3,2);
        System.out.println("式咖啡1份+A调料3份+B调料4份,最终价格为:"+order.getPrice());
    }
}

设计模式之结构型模式(5种)_第9张图片

总结: 


        装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。在软件开发中,装饰模式应用较为广泛,例如在JavaIO中的输入流和输出流的设计、javax.swing包中一些图形界面构件功能的增强等地方都运用了装饰模式。

主要优点

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  2.  可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

主要缺点

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
  2. 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。


适用场景

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

注意事项及细节

  • 装饰者模式一般用于对原有功能进行增强/装饰

  • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性

应用

IO流体系:缓冲流

四、外观模式

外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用


术语:Facade:外观

角色:

  • Client 类 外观模式的调用者
  • ComponentA 子系统A 电脑
  • ComponentB 子系统B 投影仪
  • Facade HomeTheaterFacade 家庭影院
     

模式结构 

设计模式之结构型模式(5种)_第10张图片

Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。


案例

需求:组装一个家庭影院;

电脑打开、投影仪打开、音箱打开、灯光调暗、零食拿出来,电影开始;

零食收起来、灯光调亮、音箱关闭、投影仪关闭、电脑关闭,电影结束

使用前 

 /** 电脑(故意写两个用不上的功能,依次体现外观模式的优点)
 */
public class ComponentA {
    public void m1(){
        System.out.println("电脑功能一...");
    }
    public void m2(){
        System.out.println("电脑功能二...");
    }
 
    public void on(){
        System.out.println("电脑打开...");
    }
 
    public void off(){
        System.out.println("电脑关闭...");
    }
}
 
//投影仪
class ComponentB {
    public void on(){
        System.out.println("投影仪打开...");
    }
 
    public void off(){
        System.out.println("投影仪关闭...");
    }
}
 
//音箱
class ComponentC {
    public void on(){
        System.out.println("音箱打开...");
    }
 
    public void off(){
        System.out.println("音箱关闭...");
    }
}
 
//、灯光
class ComponentD {
    public void on(){
        System.out.println("灯光调亮...");
    }
 
    public void off(){
        System.out.println("灯光调暗...");
    }
}
 
//零食
class ComponenE {
    public void on(){
        System.out.println("零食拿出来...");
    }
 
    public void off(){
        System.out.println("零食收起来...");
    }
}
 
 
public class Client {
    public static void main(String[] args) {
        new ComponentA().on();
        new ComponentB().on();
        new ComponentC().on();
        new ComponentD().off();
        new ComponenE().on();
        System.out.println("电影开始了...");
 
        System.out.println();
        new ComponenE().off();
        new ComponentD().on();
        new ComponentC().off();
        new ComponentB().off();
        new ComponentA().off();
        System.out.println("电影结束了...");
    }
}

从上面代码可以看出:

客户端调用依赖了所有的子系统(ABCDE),如果该需求反复出现,对于客户端调用而言,就不是很方便了;

另一方面,此需求完成只需要依赖各个子系统的其中一部分功能,其它功能客户端用不上,依照迪米特法则我们也应该解耦客户端与各个子系统的关系;

使用后


使用后类图: 
设计模式之结构型模式(5种)_第11张图片

 /** 电脑(故意写两个用不上的功能,依次体现外观模式的优点)
 */
public class ComponentA {
    public void m1(){
        System.out.println("电脑功能一...");
    }
    public void m2(){
        System.out.println("电脑功能二...");
    }
 
    public void on(){
        System.out.println("电脑打开...");
    }
 
    public void off(){
        System.out.println("电脑关闭...");
    }
}
 
//投影仪
class ComponentB {
    public void on(){
        System.out.println("投影仪打开...");
    }
 
    public void off(){
        System.out.println("投影仪关闭...");
    }
}
 
//音箱
class ComponentC {
    public void on(){
        System.out.println("音箱打开...");
    }
 
    public void off(){
        System.out.println("音箱关闭...");
    }
}
 
//、灯光
class ComponentD {
    public void on(){
        System.out.println("灯光调亮...");
    }
 
    public void off(){
        System.out.println("灯光调暗...");
    }
}
 
//零食
class ComponentE {
    public void on(){
        System.out.println("零食拿出来...");
    }
 
    public void off(){
        System.out.println("零食收起来...");
    }
}
 
 
public class ComponentFacade {
    ComponentA componentA =new ComponentA();
    ComponentB componentB = new ComponentB();
    ComponentC componentC = new ComponentC();
    ComponentD componentD = new ComponentD();
    ComponentE componenE = new ComponentE();
    public void on(){
        componentA.on();
        componentB.on();
        componentC.on();
        componentD.off();
        componenE.on();
        System.out.println("电影开始了...");
    }
 
    public void off(){
        componenE.off();
        componentD.on();
        componentC.off();
        componentB.off();
        componentA.off();
        System.out.println("电影结束了...");
    }
}
 
 
public class Client {
    public static void main(String[] args) {
        ComponentFacade componentFacade = new ComponentFacade();
        componentFacade.on();
        System.out.println();
        componentFacade.off();
    }
}

从上面可以看出,客户端只依赖了外观类,彻底解耦了与各个子系统之间的关系;

注意事项及细节

  • 屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  • 对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
  • 当系统需要进行分层设计时,可以考虑使用 Facade 模式
  • 把子系统中常用的方法封装起来便于调用

总结:


        外观模式是一种使用频率非常高的设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,使子系统与客户端的耦合度降低,且客户端调用非常方便。

主要优点
       外观模式的主要优点如下:

  • 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  • 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  • 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

主要缺点

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活 性。
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

适用场景

  1. 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  2. 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  3. 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

五、代理模式


概述:

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问

术语

Proxy:代理

设计模式之结构型模式(5种)_第12张图片

代理模式包含如下三个角色:

  •  Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
  • Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
  • RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
     

分类: 

  • 静态代理
  • 动态代理(jdk代理 )
  • Cglib代理

静态代理

角色

  • 接口 ITeacherDao
  • 目标对象 TeacherDAO
  • 代理类 TeacherDAOProxy

细节

代理对象与目标对象要实现相同的接口 调用的时候通过调用代理对象的方法来调用目标对象

 /** 目标类
 */
public class TeacherDAO implements ITeacherDao {
    public void teach() {
        System.out.println("老师传授知识");
    }
}
 
//目标接口
interface ITeacherDao {
    void teach();
}
 
//代理类
class TeacherDAOProxy implements ITeacherDao {
    private ITeacherDao teacherDAO;
 
    public TeacherDAOProxy(ITeacherDao teacherDAO) {
        this.teacherDAO = teacherDAO;
    }
 
    @Override
    public void teach() {
        System.out.println("老师正式授课前的准备工作,如学生全部签到...");
        teacherDAO.teach();
        System.out.println("老师结束授课,如下课铃声响起...");
    }
}
 
 
public class Client {
    public static void main(String[] args) {
        TeacherDAOProxy proxy = new TeacherDAOProxy(new TeacherDAO());
        proxy.teach();
    }
}

动态代理jdk代理 ​

角色 ​

  • 接口 ​ ITeacherDao ​
  • 目标对象 ​ TeacherDAO ​
  • 代理类 ​ TeacherDAOProxy ​


细节 ​

不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 ​ 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象 ​ 代理类所在包:java.lang.reflect.Proxy
 

/**
 * 目标接口
 */
interface ITeacherDao {
    String teach();
 
    ITeacherDao sleep(int minutes);
}
 
class TeacherDao implements ITeacherDao{
    @Override
    public String teach() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date())+":老师传授知识";
    }
 
    @Override
    public ITeacherDao sleep(int minutes) {
        System.out.println("老师睡了" + minutes + "分钟");
        return this;
    }
 
}
 
//真实代理类的外衣
class TeacherDaoProxy{
    private ITeacherDao target;
 
    public TeacherDaoProxy(ITeacherDao target) {
        this.target = target;
    }
 
    public Object xxx(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object obj = null;
                        String methodName = method.getName();
                        System.out.println("目标方法" + methodName + ":jdk代理开始...");
                        System.out.println("真实代理对象:"+proxy.getClass());
                        System.out.println("目标对象:"+target.getClass());
                        if("sleep".equals(methodName)){
//                            method.invoke(target, args);
//                            obj = proxy;
                            obj = method.invoke(target, args);
                        }else {
//                        proxy是真实代理类,method是目标方法,args是目标方法携带的参数
                            obj = method.invoke(target, args);
                        }
                        System.out.println("目标方法" + methodName + ":jdk代理结束...");
                        return obj;
                    }
                });
    }
}
 
 
public class Client {
    public static void main(String[] args) {
        TeacherDaoProxy proxy = new TeacherDaoProxy(new TeacherDao());
        ITeacherDao ins = (ITeacherDao) proxy.xxx();
        System.out.println("===========代理类实例被使用   begin=============");
        System.out.println(ins);
        System.out.println("===========代理类实例被使用   end=============");
        System.out.println(ins.teach());
//        System.out.println(proxy.execute());
        System.out.println("===========代理类实例被使用   begin=============");
        ins.sleep(10);
        System.out.println("===========代理类实例被使用   end=============");
        ins.sleep(20).sleep(60);
    }
}

设计模式之结构型模式(5种)_第13张图片

注意:java.lang.reflect.InvocationHandler.invoke第一个参数proxy的使用场景,链式编程中会用到; 

Cglib代理 ​

角色 ​

  • 接口 ​ ITeacherDao ​
  • 目标对象 ​ TeacherDAO ​
  • 代理类 ​ TeacherDAOProxy

细节

  • 目标对象与代理对象都不需要实现接口 ​
  • Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展 ​ Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用 ​
  • Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类 ​

注意:

  • 需要引入 cglib 的 jar 文件
  • 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:
  • 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
 
class TeacherDao {
    public String teach() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date()) + ":老师传授知识";
    }
 
    public TeacherDao sleep(int minutes) {
        System.out.println("老师睡了" + minutes + "分钟");
        return this;
    }
 
}
 
//真实代理类的外衣
class TeacherDaoProxy implements MethodInterceptor {
    private Object target;
 
    public TeacherDaoProxy(Object target) {
        this.target = target;
    }
 
    //返回一个代理对象:	是 target  对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }
 
    /**
     * @param proxyIns  由CGLib动态生成的代理类实例
     * @param method    上文中实体类所调用的被代理的方法引用
     * @param args      参数值列表
     * @param methodProxy   生成的代理类对方法的代理引用
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxyIns, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        String methodName = method.getName();
        Object res;
        System.out.println("目标方法" + methodName + ":cglib代理开始...");
        System.out.println("真实代理对象:" + proxyIns.getClass());
        System.out.println("目标对象:" + target.getClass());
        if ("sleep".equals(methodName)) {
//                            method.invoke(target, args);
//                            obj = proxy;
            res = method.invoke(target, args);
//            res = methodProxy.invokeSuper(proxyIns,args);
            res = proxyIns;
        } else {
//                        proxy是真实代理类,method是目标方法,args是目标方法携带的参数
            res = method.invoke(target, args);
        }
        System.out.println("目标方法" + methodName + ":cglib代理结束...");
        return res;
    }
}
 
public class Client {
    public static void main(String[] args) {
        TeacherDao proxy = (TeacherDao) new TeacherDaoProxy(new TeacherDao()).getProxyInstance();
        proxy.sleep(111).sleep(222);
    }
}

 总结

     代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。

模式优点

       代理模式的共同优点如下:

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

模式缺点
       代理模式的主要缺点如下:

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂。
     

jdk代理与Cglib代理比较:

JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高

​ 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

应用

Spring框架的AOP:Cglib代理的体现

你可能感兴趣的:(设计模式,结构型,java)