设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式

设计模式之结构型模式

一、概述

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

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

  1. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  2. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
  3. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
  4. 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
  5. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
  6. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
  7. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

以上 7种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式,下面我们会分别、详细地介绍它们的特点、结构与应用。

二、代理模式

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

代理模式的主要优点有:

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

其主要缺点是:

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

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

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

其结构图如下图所示:
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第1张图片

在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态代理:在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。因此可以做到在不修改目标对象的功能前提下,对于目标对象扩展,缺点因为代理对象和目标对象实现一样的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象与代理对象都需要维护。
  • 动态:在程序运行时,运用反射机制动态创建而成。

动态代理有如下特点:代理对象,不需要实现接口,代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

代码实现如下:

package proxy;

public class ProxyTest {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

//抽象主题
interface Subject {
    void Request();
}

//真实主题
class RealSubject implements Subject {
    public void Request() {
        System.out.println("访问真实主题方法...");
    }
}

//代理
class Proxy implements Subject {
    private RealSubject realSubject;

    public void Request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }

    public void preRequest() {
        System.out.println("访问真实主题之前的预处理。");
    }

    public void postRequest() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

三、适配器模式

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

该模式的主要优点如下:

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

其缺点是:

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

适配器模式的角色和结构:

类适配器模式可采用多重继承方式实现,如C++可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构。

适配器模式(Adapter)包含以下主要角色。

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

类适配器模式的结构图如图所示:
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第2张图片

类适配器模式代码实现:

package adapter;
//目标接口
interface Target{
    public void request();
}
//适配者接口
class Adaptee{
    public void specificRequest(){       
        System.out.println("适配者中的业务代码被调用!");
    }
}
//类适配器类
class ClassAdapter extends Adaptee implements Target{
    public void request(){
        specificRequest();
    }
}
//客户端代码
public class ClassAdapterTest{
    public static void main(String[] args){
        System.out.println("类适配器模式测试:");
        Target target = new ClassAdapter();
        target.request();
    }
}

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

设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第3张图片

对象适配器模式代码实现:

package adapter;
//对象适配器类
class ObjectAdapter implements Target{
    private Adaptee adaptee;
    public ObjectAdapter(Adaptee adaptee){
        this.adaptee=adaptee;
    }
    public void request(){
        adaptee.specificRequest();
    }
}
//客户端代码
public class ObjectAdapterTest{
    public static void main(String[] args){
        System.out.println("对象适配器模式测试:");
        Adaptee adaptee = new Adaptee();
        Target target = new ObjectAdapter(adaptee);
        target.request();
    }
}

适配器模式(Adapter)通常适用于以下场景:

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

四、桥接模式

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

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

桥接(Bridge)模式的优点如下:

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

桥接模式的缺点如下:

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

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

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

其结构图如图所示:
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第4张图片

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。

桥接模式通常适用于以下场景:

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时;

  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时;

  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

桥接模式的代码实现:

package bridge;
public class BridgeTest{
    public static void main(String[] args){
        Implementor imple=new ConcreteImplementorA();
        Abstraction abs=new RefinedAbstraction(imple);
        abs.Operation();
    }
}
//实现化角色
interface Implementor{
    public void OperationImpl();
}
//具体实现化角色
class ConcreteImplementorA implements Implementor{
    public void OperationImpl(){
        System.out.println("具体实现化(Concrete Implementor)角色被访问" );
    }
}
//抽象化角色
abstract class Abstraction{
   protected Implementor imple;
   protected Abstraction(Implementor imple){
       this.imple=imple;
   }
   public abstract void Operation();   
}
//扩展抽象化角色
class RefinedAbstraction extends Abstraction{
   protected RefinedAbstraction(Implementor imple){
       super(imple);
   }
   public void Operation(){
       System.out.println("扩展抽象化(Refined Abstraction)角色被访问" );
       imple.OperationImpl();
   }
}

五、装饰模式

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

装饰(Decorator)模式的主要优点有:

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

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

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

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。

  • 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。

  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。

  • 具体装饰(Concrete Decorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

具体的结构图如下:
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第5张图片

实现代码如下:

package decorator;
public class DecoratorPattern{
    public static void main(String[] args){
        Component p=new ConcreteComponent();
        p.operation();
        System.out.println("---------------------------------");
        Component d=new ConcreteDecorator(p);
        d.operation();
    }
}
//抽象构件角色
interface  Component{
    public void operation();
}
//具体构件角色
class ConcreteComponent implements Component{
    public ConcreteComponent(){
        System.out.println("创建具体构件角色");       
    }   
    public void operation(){
        System.out.println("调用具体构件角色的方法operation()");           
    }
}
//抽象装饰角色
class Decorator implements Component
{
    private Component component;   
    public Decorator(Component component){
        this.component=component;
    }   
    public void operation(){
        component.operation();
    }
}
//具体装饰角色
class ConcreteDecorator extends Decorator
{
    public ConcreteDecorator(Component component){
        super(component);
    }   
    public void operation(){
        super.operation();
        addedFunction();
    }
    public void addedFunction(){
        System.out.println("为具体构件角色增加额外的功能addedFunction()");           
    }
}

类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。

六、外观模式

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

在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类;

  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易;

  • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观(Facade)模式的主要缺点如下:

  • 不能很好地限制客户使用子系统类,很容易带来未知风险;

  • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

外观(Facade)模式包含以下主要角色。

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口;

  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它;

  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

其结构图如下:
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第6张图片

代码实现外观模式:

package facade;
public class FacadePattern{
    public static void main(String[] args){
        Facade f=new Facade();
        f.method();
    }
}
//外观角色
class Facade{
    private SubSystem01 obj1=new SubSystem01();
    private SubSystem02 obj2=new SubSystem02();
    private SubSystem03 obj3=new SubSystem03();
    public void method(){
        obj1.method1();
        obj2.method2();
        obj3.method3();
    }
}
//子系统角色
class SubSystem01{
    public  void method1(){
        System.out.println("子系统01的method1()被调用!");
    }   
}
//子系统角色
class SubSystem02{
    public  void method2(){
        System.out.println("子系统02的method2()被调用!");
    }   
}
//子系统角色
class SubSystem03{
    public  void method3(){
        System.out.println("子系统03的method3()被调用!");
    }   
}

外观模式的应用场景:

  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。

七、享元模式

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:

  • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

  • 读取享元模式的外部状态会使得运行时间稍微变长。

享元模式的主要角色有:

  • Flyweight:享元对象;
  • IntrinsicState:内部状态,享元对象共享内部状态;
  • ExtrinsicState:外部状态,每个享元对象的外部状态不同。

其结构图如下:

设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第7张图片

实现代码:

//享元接口:
public interface Flyweight {
	void doOperation(String extrinsicState);
}

//具体享元角色
public class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;
    public ConcreteFlyweight(String intrinsicState) {
    	this.intrinsicState = intrinsicState;
 	}
    @Override
    public void doOperation(String extrinsicState) {
        System.out.println("Object address: " + System.identityHashCode(this));
        System.out.println("IntrinsicState: " + intrinsicState);
        System.out.println("ExtrinsicState: " + extrinsicState);
     }
}

//享元工厂角色:
public class FlyweightFactory {
	private HashMap flyweights = new HashMap<>();
	Flyweight getFlyweight(String intrinsicState) {
		if (!flyweights.containsKey(intrinsicState)) {
			Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
			flyweights.put(intrinsicState, flyweight);
		}
		return flyweights.get(intrinsicState);
	}
}

public class Client {
	public static void main(String[] args) {
		FlyweightFactory factory = new FlyweightFactory();
		Flyweight flyweight1 = factory.getFlyweight("aa");
		Flyweight flyweight2 = factory.getFlyweight("aa");
		flyweight1.doOperation("x");
		flyweight2.doOperation("y");
	}
}

享元模式的应用场景:

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  • 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。

  • 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。

  • 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

八、组合模式

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

组合模式的主要优点有:

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

  • 不容易限制容器中的构件;

  • 不容易用继承的方法来增加构件的新功能;

组合模式分为透明模式安全模式

(1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()Remove()GetChild()方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第8张图片

(2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
设计模式(三):结构型模式之代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式和组合模式_第9张图片

透明模式的结构和代码实现如下:

package composite;
import java.util.ArrayList;
public class CompositePattern{
    public static void main(String[] args){
        Component c0=new Composite(); 
        Component c1=new Composite(); 
        Component leaf1=new Leaf("1"); 
        Component leaf2=new Leaf("2"); 
        Component leaf3=new Leaf("3");          
        c0.add(leaf1); 
        c0.add(c1);
        c1.add(leaf2); 
        c1.add(leaf3);          
        c0.operation(); 
    }
}
//抽象构件
interface Component{
    public void add(Component c);
    public void remove(Component c);
    public Component getChild(int i);
    public void operation();
}
//树叶构件
class Leaf implements Component{
    private String name;
    public Leaf(String name){
        this.name=name;
    }
    public void add(Component c){ }           
    public void remove(Component c){ }   
    public Component getChild(int i){
        return null;
    }   
    public void operation(){
        System.out.println("树叶"+name+":被访问!"); 
    }
}
//树枝构件
class Composite implements Component{
    private ArrayList<Component> children=new ArrayList<Component>();   
    public void add(Component c){
        children.add(c);
    }   
    public void remove(Component c){
        children.remove(c);
    }   
    public Component getChild(int i){
        return children.get(i);
    }   
    public void operation(){
        for(Object obj:children){
            ((Component)obj).operation();
        }
    }    
}

组合模式的应用场景:

  • 在需要表示一个对象整体与部分的层次结构的场合;
  • 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

你可能感兴趣的:(设计模式,设计模式)