java设计模式笔记-结构型模式

java设计模式笔记-结构型模式

适配器模式

结构型模式与适配器概述

适配器模式是一种使用频率非常高的结构型设计模式

结构型模式:

结构型模式重心在于如何将现有类或对象组织在一起形成更加强大的结构
不同的结构型模式从不同的角度来组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。
结构型模式可以描述两种不同的东西:类与类的实例(对象),根据这一点,结构模式可分为类结构模式和对象结构模式。
类结构模式在于类的组合,多个类组合成一个更大的系统,该模式一般只存在继承和实现关系;
对象结构模式在于类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法。
根据合成复用原则,尽量用关联关系替代继承关系。

结构型模式有

适配器*:将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
桥接:将抽象部分与他的实现部分解耦,使得两者能够独立变化。
组合*:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象
装饰:动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案
外观**:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
享元:运用共享技术有效地支持大量细粒度对象的复用
代理*:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问

概述:adapter适配器 别名 包装器Wrapper

适配器可以将一个类的接口和另一个类的接口匹配起来,而无需修改原来的适配者接口和抽象目标类接口

适配器模式定义中的接口是广义的接口,可表示为一个方法或者方法的集合。

结构与实现

适配器模式包括类适配器(继承关系)和对象适配器(关联关系)。

适配器结构:

  1. Target目标抽象类:目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于java不支持多继承,故而它只能是接口。
  2. Adapter适配器类:它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。适配器Adapter是适配器模式的核心,在类适配器中,它通过实现Target接口并继承Adaptee类来使二者产生联系,在对象适配器中,它通过继承Target并关联一个Adaptee对象来使二者产生联系
  3. Adaptee适配者类:适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。

类适配器的实现:

Adaptee没有request()方法,客户端期待这个方法,但adaptee类中实现了specificRequest()方法,此方法提供的实现时客户端所需的。为了使客户端能够使用Adaptee类,提供了一个中间类,即适配器类Adapter,适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,达到适配的目的;

Adaptee与Adapter是继承关系:类适配器模式

public class Adapter extends Adaptee implements Target {
    public void request() {
        super.specificRequest();
    }
}

对象适配器的实现:

Adaptee没有request()方法, 此时 Apapter继承Target重写request方法,关联Adaptee获取specificRequest方法

Adapter和Adaptee是关联关系:对象适配器模式

public class Adapter extends Target {
	// 维持一个对适配器对象的引用
	private Adaptee adaptee;
	
	public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
	}
	
	public void request() {
		// 转发调用
        adaptee.specificRequest();
	}

适配器模式可将一个类的接口和另一个类的接口匹配起来,使用的前提是不能或不想修改原来的适配者接口和抽象目标类接口。例如买了第三方类库或控件,没有源代码,此时使用适配器模式可以统一对象访问接口。

缺省适配器模式

缺省适配器模式是适配器模式的一种变体

定义:

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。

缺省适配器的结构:常用于接口与实现类的中间 作为抽象类 提供空方法

  1. ServiceInterface适配者接口:它是一个接口,通常在该接口中声明了大量的方法。
  2. AbstractServiceClass缺省适配器类:它是缺省适配器模式的核心类,**使用空方法的形式实现了在ServiceInterface接口中声明的方法。**通常将它定义为抽象类,因为对他进行实例化没有任何意义。
  3. ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法不得不提供空实现。在有了缺省适配器之后可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。
public abstract class AbstractServiceClass implements ServiceInterface {
    public void serviceMethod1() {} // 空方法
    public void serviceMethod2() {} // 空方法
    ...
}

java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter,KeyAdapter,MouseAdapter

双向适配器

在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器

public class Adapter implements Target, Adaptee {
    // 同时维持对抽象目标类和适配者的引用
    private Target target;
    private Adaptee adaptee;
    
    // 传入Target的实现类ConcreteTarget
    public Adapter(Target target) {
        this.target = target;
    }
    
    // 传入Adaptee的实现类ConcreteAdaptee
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    public void request() {
        adaptee.specificRequest();
    }
    
    public void specificRequest() {
        target.request();
    }
}

适配器模式优缺点与适用环境

适配器模式将现有接口转化为客户类所期望的接口,实现了对 现有类的复用 使用频率高

优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件可以方便地更换适配器,也可以不修改原有代码的基础上增加新的适配器,符合开闭原则

具体来说,类适配器模式还有以下优点:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还有以下优点:一个对象适配器可以把多个不同的适配者适配到同一个目标。可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可通过该适配器进行适配。

类适配器缺点:

  • java,C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  • 适配者类不能是最终类
  • java,C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有局限。

对象适配器的缺点:

  • 该模式下要在适配器中置换适配者类的某些方法比较繁琐。若一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当成真正的适配者进行适配,实现过程繁琐。

适用环境:

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类(包括一些可能在将来引进的类)一起工作。

桥接模式

概述

若系统中的某个类存在两个独立变化的维度,通过桥接模式可将这两个维度分离出来,使得两者可以独立扩展。桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,同时有效地控制了系统中类的个数。

桥接模式中将两个独立变化的维度(如颜色和型号)设计为两个独立的继承等级结构,而不是将二者耦合在一起形成多层继承结构。桥接模式在抽象层建立起一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式

定义:将抽象部分与它的实现部分解耦,使得两者都能够独立变化。

桥接模式是一种对象结构型模式,他又被称为柄体(Handle and Body)模式或接口模式。

结构与实现

结构:

  • Abstraction抽象类:它是用于定义抽象类的接口,通常是抽象类而不是接口,其中定义了一个Implementor实现类接口类型的对象并可以维护该对象,他与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
  • RefinedAbstraction扩充抽象类:它扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
  • Implementor实现类接口:它是定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同。一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
  • ConcreteImplementor具体实现类:它具体实现了Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。

实现:

将具有两个独立变化维度的类的一些普通业务方法和与之关系最亲密的维度设计为抽象类层级结构(抽象部分),而将另一维度设计为实现类层次结构(实现部分)。例如毛笔和颜色,增加一种新型号的毛笔只需扩展抽象部分,新增扩充抽象类;新增颜色只需扩展实现部分,新增具体实现类

// 桥接模式中存在两个独立变化的维度,为了降低两者之间的耦合度,首先需要针对两个不同的维度提取抽象类和实现类接口,并建立一个抽象关联关系。以下是实现部分维度
public interface Implementor {
    public void operationImpl();
}

实现部分具体实现类中用于定义该维度相对应的一些具体方法

public class ConcreteImplementor implements Implementor {
    public void operationImpl() {
        // 具体业务方法的实现
    }
}

抽象部分维度

public abstract class Abstraction {
	// 定义实现类接口对象
    protected Implementor impl;
    
    public void setImpl(Implementor impl) {
        this.impl = impl;
    }
    
    // 声明抽象业务方法
    public abstract void operation();
}

抽象部分维度的子类

public class RefinedAbstraction extends Abstraction {
    public void operation() {
        // 业务代码  
        // 调用实现类的方法
        impl.operationImpl();
        // 业务代码
    }
}

对于客户端,可针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦。

桥接模式与适配器模式的联用

桥接模式通常与适配器模式联合使用。适配器模式解决两个已有接口间不兼容的问题,此时被适配的类往往是一个黑盒,无法控制扩展。适配器模式通常用于系统与第三方产品功能的集成。桥接模式不同,用户可由接口继承或类继承的方式对系统扩展。

桥接模式一般用于系统的初步设计,若之后系统与已有类无法协同时采用适配器模式。

优缺点与适用环境

桥接模式是设计java虚拟机和实现JDBC等驱动程序的核心模式之一

优点:

  1. 分离抽象接口及其实现。使用对象间的关联关系解耦了抽象和实现之间的固有绑定。任意组合子类获得多维度组合对象
  2. 多种情况下,桥接模式可以取代多层继承的方案
  3. 提供了系统的可扩展性

缺点:

  1. 关联关系建立在抽象层,提高系统的理解和设计难度,需开始时就对抽象层进行设计,编程
  2. 要求正确识别系统中两个独立变化的维度,使用范围有局限性

适用环境:

  1. 系统需要在抽象化和具体化之间增加灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
  2. 抽象部分和实现部分可用继承的方式独立扩展而不互相影响,运行时动态地将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色动态耦合。
  3. 一个类存在两个或多个独立变化的维度,且可独立进行扩展
  4. 替代多继承,继承

组合模式

概述

组合模式关注那些 包含叶子构件leaf和容器构件composite的结构以及它们的组织形式,在叶子构件中不包含成员对象,而容器构件中可以包含成员对象,这些对象通过递归组合可构成一个树形结构。组合模式使用面向对象的方式来处理树形结构,它为leaf和composite提供了一个公共的抽象构件类,客户端可以针对抽象构件进行处理,而无须关心所操作的是leaf还是composite

如Windows中目录是容器Container,文件是叶子Leaf,容器对象的某个方法被执行将遍历整个树形结构,寻找也包含这个方法的成员对象并执行,其中使用了递归调用的机制。

组合模式使得用户可以一致性地处理整个树形结构或者树形结构的一部分,描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无需对他们进行区分,可以一致地对待容器和叶子对象。组合模式又称为部分-整体模式(Part-Whole),将对象组织到树形结构中,可用来描述整体与部分的关系。

定义:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。

结构与实现

结构:

  • Component抽象构件:他可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件,删除子构件,获取子构件等。
  • Leaf叶子构件:它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过抛出异常,提示错误等方式进行处理。
  • Composite容器构件:它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供了一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以通过递归调用其子节点的业务方法。

实现:

组合模式的关键在于定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,客户端针对抽象构件编程。同时容器对象与抽象构件类之间还建立了一个聚合关联关系,以此实现递归组合,形成一个树形结构。

抽象构件角色

public abstract class Component {
	// 增加成员
    public abstract void add(Component c);
    // 删除成员
    public abstract void remove(Component c);
	// 获取成员
	public abstract Component getChild(int i);
	// 业务方法
	public abstract void operation();
}

一般将抽象构件类设计为接口或抽象类,将所有子类共有方法的声明和实现放在抽象构件类中。

继承抽象构件的叶子节点

public class Leaf extends Component {
    public void add(Component c) {
        // 异常处理或错误提示
    }
        
    public void remove(Component c) {
        // 异常处理或错误提示
    }
    
    public Component getChild(int i) {
        // 异常处理或错误提示
        return null;
    }
    
    public void operation() {
        // 叶子构件具体业务方法的实现
    }
}

继承抽象构件的容器构件

public class Composite extends Component {
	private ArrayList<Component> list = new ArrayList<Component>();
	
    public void add(Component c) {
        list.add(c);
    }
        
    public void remove(Component c) {
        list.remove(c);
    }
    
    public Component getChild(int i) {
        return (Component) list.get(i);
    }
    
    public void operation() {
        // 容器构件具体业务方法的实现,将递归调用成员构件的业务方法
        for (Object obj : list) {
            ((Component) obj).operation();
        }
    }
}

透明组合模式与安全组合模式

1.透明组合模式:

此模式抽象构件Component声明了所有用于管理成员对象的方法,add,remove,getChild等,确保所有的构件类有相同的接口,缺点是不够安全,叶子节点没有add,remove,getChild的异常处理

2.安全组合模式:

抽象构件Component中没有声明任何用于管理成员对象的方法(只有operation的抽象方法),而是在Composite类中声明并实现这些方法,如此根本不向叶子节点提供管理成员对象的方法。 此模式缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件

优缺点与适用环境

优点:

  1. 清除地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
  2. 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
  3. 在组合模式中增加新的容器构件和叶子构件比较方便,无需修改源代码,符合开闭原则
  4. 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形结构,但对树形结构的控制却非常简单。

缺点:

  1. 增加新构件时很难对容器中的构件类型进行限制。

适用环境:

  1. 具有整体和部分的层次结构中希望通过一种方式忽略整体与部分的差异,客户端可一致地对待它们
  2. 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  3. 在一个系统中能够分离出叶子节点和容器对象,且它们的类型不固定,需要增加一些新的类型

装饰模式

概述

装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。无需修改原有对象代码,通过装饰者动态增删原有代码功能,符合开闭原则

定义:动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。

结构与实现

结构:

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

实现:

抽象构件一般是抽象类或接口

public abstract class Component {
    public abstarct void operation();
}

具体构件类作为抽象构件类的子类实现了在抽象构件类中声明的业务方法

public class ConcreteComponent extends Component {
	public void operation() {
        // 业务代码
	}

装饰模式的核心在于抽象装饰类的设计

public class Decorator extends Component {
	// 维持一个队抽象构件对象的引用
    private Component component;
    
    // 注入一个抽象构件类型的对象
    public Decorator(Component component) {
        this.component = component;
    }
    
    public void operation(){
    	// 调用原有业务方法
        component.operation();
    }
}

在Decorator的子类(即具体装饰类)中将继承operation()方法并根据需要进行扩展

public class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component component) {
        super(component);
    }
    
    public void operation() {
        // 调用原有业务方法
        super.operation();
        // 调用新增业务方法
        addedBehavior();
    }
    
    // 新增业务方法
    public void addedBehavior() {
        ...
    }
}

由于抽象装饰类Decorator中注入的是Component类型的对象,故而可将一个具体构件对象注入其中,再通过具体装饰类进行装饰;此外还可将一个已经装饰过的Decorator子类的对象再注入其中进行多次装饰,从而实现对原有功能的多次扩展。

透明装饰模式与半透明装饰模式

具体装饰类通过新增成员变量或者成员方法来扩充具体构件类的功能。在标准的装饰模式中,新增行为需要在原有业务方法中调用operation(),无论是具体构件对象还是装饰过的构件对象,对于客户端是透明的,这种装饰模式被称为透明(Transparent)装饰模式

有些情况下,新增的行为需要被单独调用,此时客户端不能再一致性地处理装饰之前的对象和装饰之后的对象,这种装饰模式称为半透明(Semi-transparent)装饰模式

1.透明装饰模式

透明装饰模式要求客户端针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应全部声明为抽象构件类型。对于客户端而言,具体构件对象和具体装饰对象没区别。

Component com1,com2;  // 而不是ComcreteComponent com1;ComcreteDecotor com2
com1 = new ConcreteComponent();
com2 = new ConcreteDecorator(com1);
使用抽象构件类型定义对象而不是具体构件类型和具体装饰类型定义对象

多次装饰

Component com1,com2,com3;
com1 = new ConcreteComponent();
com2 = new ConcreteDecorator(com1);
com3 = new ConcreteDecorator(com2);
com3.operation();
// 无法单独调用com2的addedBehavior方法  由于抽象构件中没有声明该方法

2.半透明装饰模式

为了能够调用到新增方法,不得不用具体装饰类型来定义装饰之后的对象,而具体构件类型仍然可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式。
对于客户端,具体构件类型无需关心,是透明的;但是具体装饰类型必须指定,是不透明的

Component com1 = new ConcreteComponent(); // 抽象构件类型定义
ConcreteDecorator com2 = new ConcreteDecorator(com1); // 具体装饰类型定义
com2.operation();
com2.addedBehavior(); // 单独调用新增业务方法

半透明装饰模式更灵活,设计简单,使用方便;缺点在于不能实现对同一个对象的多次装饰,且客户端需要有区别地对待装饰之前的对象和装饰之后的对象。

优缺点与适用环境

取代继承复用的有效方式之一。如在Java I/O中的输入流和输出流的设计用到了装饰模式

优点:

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

缺点:

  • 调试排错繁琐
  • 占用系统资源

适用环境:

  • 不影响其他对象的情况下以动态,透明的方式给单个对象添加职责
  • 当不能采用继承的方式(final)对系统进行扩展或者采用继承不利于系统扩展和维护时可使用装饰模式。

外观模式

概念

通过引入一个外观角色(Faccade)来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,使子系统与客户端的耦合度降低

在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)

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

外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度

结构与实现

结构:

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

实现:

降低类与类之间的耦合关系。引入外观模式之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改,只需要在外观类中增加或移除对子系统的引用即可。

外观模式中所指的子系统是一个广义的概念,它可以是一个类,一个功能模块,系统的一个组成部分或者一个完整的 系统。

public class SubSystemA {
    public void methodA() {
        // 业务实现代码
    }
}

public class SubSystemB {
    public void methodB() {
        // 业务实现代码
    }
}

public class SubSystemC {
    public void methodC() {
        // 业务实现代码
    }
}

在引入外观类之后,与子系统业务类之间的交互统一由外观类来完

public class Facade {
    private SubSystemA obj1 = new SubSystemA();
    private SubSystemB obj2 = new SubSystemB();
    private SubSystemC obj3 = new SubSystemC();
    
    public void method() {
        obj1.methodA();
        obj2.methodB();
        obj3.methodC();
    }
}

client客户端代码

...
Facade facade = new Facade();
facade.method();
...

抽象外观类

标准的外观模式结构,若需要增加,删除或更换与外观类交互的子系统类,必须修改外观类或客户端代码。违背开闭原则,因此可以通过引入抽象外观类对系统进行改进。引入抽象外观类后,客户端可以针对抽象外观类进行编程,对于新的业务需要,无需修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象。

抽象外观类
public abstract class AbstractEncryptFacade {
    public abstract void fileEncrypt(String fileNameSrc, String fileNameDes);
}

具体外观类
public class newEncryptFacade extends AbstractEncryptFacade {
    private FileReader reader;
    private NewCipherMachine cipher;
    private FileWriter writer;
    
    public newEncryptFacade() {
        reader = new FileReader();
        cipher = new NewCipherMachine();
        writer = new FileWriter();
    }
    
    public void fileEncrypt(String fileNameSrc, String fileNameDes) {
        String plainStr = reader.read(fileNameSrc);
        String encryptStr = cipher.encrypt(plainStr);
        writer.write(encryptStr, fileNameDes);
    }
}

优缺点与适用环境

优点:

  • 减少客户端所需处理的对象数目,通过引入外观模式,客户端代码简单,与之关联的对象少
  • 实现了子系统与客户端之间的松耦合关系,子系统的变化不会影响到调用它的客户端,只需调整外观类即可
  • 一个子系统的修改对其他子系统没有任务影响,且子系统内部变化也不会影响到外观对象

缺点:

  • 限制减少了可变性和灵活性

适用环境:

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

享元模式

概述

当系统中存在大量相同或相似的对象时,享元模式是一种值得考虑的解决方案。在享元模式中提供了一个享元池用于存储已经创建好的享元对象,并通过享元工厂类将享元对象提供给客户端使用。

享元模式中存储共享实例对象的地方称为享元池(Flyweight Pool)。

享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分内部状态(Intrinsic State)和外部状态(Extrinsic State)

1.内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。例如字符的内容不会随外部环境的改变而变化,无论在任何环境下字符‘a’始终不会变成‘b’
2.外部状态是随环境改变而变化的,不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可在不同的地方有不同的颜色,如a有红色,蓝色等,字符的大小也是如此,a有5号,4号等。字符的颜色和大小是两个独立的外部状态,它们可以独立变化,互相之间没有影响,客户端可以在使用时将外部状态注入到享元对象中。

因为区分了内部和外部状态,故而可将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,当需要时取出对象,实现对象复用。通过向取出的对象注入不同的外部状态可得到一系列相似的对象,而这些对象在内存只存储一份。

定义:运用共享技术有效地支持大量细粒度对象的复用(要求被共享的对象必须是细粒度对象)

结构与实现

结构复杂,通常结合工厂模式一起使用

结构:

  • Flyweight抽象享元类:抽象享元类通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可向外界提供享元对象的内部数据(内部状态),同时也可通过这些方法来设置外部数据(外部状态)
  • ConcreteFlyweight具体享元类:具体享元类实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常可结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight非共享具体享元类:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象可直接通过实例化创建
  • FlyweightFactory享元工厂类:享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储kv"键值对"的集合(也可是其他类型的集合),可结合工厂模式设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已经创建的实例或者创建一个新的实例(若不存在),返回新创建的实例并将其存储在享元池中。

实现:

抽象享元类

public abstract class Flyweight {
    public abstract void operation(String extrinsicState);
}

具体享元类ConcreteFlyweight中要将内部状态和外部状态分开处理,通常将内部状态作为具体享元类的成员变量,而外部状态通过注入的方式添加到具体享元类中。

public class ConcreteFlyweight extends Flyweight {
    // 内部状态intrinsicState作为成员变量,同一个享元对象的内部状态是一致的
    private String intrinsicState;
    
    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    
    // 外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每次调用是可传入不同的外部状态
    public void operation(String extrinsicState) {
        // 业务方法
    }
}
public class UnsharedConcreteFlyweight extends Flyweight {
    public void operation(String extrinsicState) {
        // 业务方法
    }
}

享元工厂类FlyweightFactory,作用是提供一个用于存储享元对象的享元池

public class FlyweightFactory {
    // 定义一个HashMap用于存储享元对象,实现享元池
    private HashMap<String, Object> flyweights = new HashMap<String, Object>();
    
    public Flyweight getFlyweight(String key) {
        // 若对象存在,则直接从享元池获取
        if (flyweights.containsKey(key)) {
            return (Flyweight) flyweights.get(key);
        }
        // 若对象不存在,先创建一个新的对象添加到享元池中,然后返回
        else {
            Flyweight fw = new ConcreteFlyweight();
            flyweights.put(key, fw);
            return fw;
        }
    }
}

单纯享元模式与复合享元模式:

1.单纯享元模式

单纯享元模式中所有的具体享元类都是可以共享的,不存在非共享具体享元类

2.复合享元模式

将一些单纯享元对象使用组合模式加以组合还可以形成复合享元对象,这样的复合享元对象本身不能共享,但它们可以分解成单纯享元对象,而后者可共享。
通过使用复合享元模式可让复合享元类中所包含的每个单纯享元类都具有相同的外部状态,而这些单纯享元类的内部状态往往可以不同。

注意:String类使用了享元模式 Copy On Write机制

优缺点与适用环境

优点:

  • 节约内存(减少内存中对象的数量),提高性能
  • 享元模式的外部状态相对独立,且不会影响内部状态,从而享元对象可在不同环境共享

缺点:

  • 逻辑复杂,需要分离内部状态和外部状态
  • 读取外部状态使运行时间变长

适用环境:

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费
  • 对象的大部分状态都可以外部化,可将这些外部状态传入对象中
  • 适用享元模式时需要维护一个存储享元对象的享元池,如此耗费系统资源,故而应该在重复使用享元对象时才使用享元模式。

代理模式

概念

当无法直接访问某个对象时,可通过代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。

根据代理模式的使用目的不同,代理模式分为保护代理,远程代理,虚拟代理,缓冲代理,智能引用代理等

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

结构与实现

代理模式的核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层

结构:

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

实现:
抽象主题类声明了真实主题类和代理类的公共方法,可以是接口,抽象类或具体类

public abstract class Subject {
    public abstract void request();
}

真实主题类继承了抽象主题类,提供了业务方法的具体实现

public class RealSubject extends Subject {
    public void request() {
        // 业务代码
    }
}

代理类是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,调用时可在原有业务方法的基础上附加一些新的方法对功能进行扩充或约束

public class Proxy extends Subject {
	// 维持一个对真实主题对象的引用 :自己创建
    private RealSubject realSubject = new RealSubject();
    // 可以增加关联类的成员变量,用于扩展主题的功能
    public void preRequest() {
        ...
    }
    public void request() {
        preRequest();
        // 调用真实主题对象的方法
        realSubject.request();
        postRequest();
    }
    public void postRequest(){
        ...
    }
}

代理模式与装饰模式的区别

装饰模式是装饰类带有被装饰类的引用,需要客户端传入被装饰类的引用。而代理模式是自己创建被代理对象,对客户端透明

常用代理模式

远程代理(Remote Proxy)

远程代理模式使得客户端可访问在远程主机上的对象,远程主机具有更高的计算资源。远程代理可将网络的细节隐藏起来,使得客户端不必考虑网络的存在。(远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用)

Java语言中可通过名为RMI(Remote Method Invocation,远程方法调用)的机制来实现远程代理,它能够实现一个Java虚拟机中的对象调用另一个Java虚拟机中对象的方法。

在RMI中,客户端可通过一个桩(Stub)对象与远程主机上的业务对象进行通信,桩对象与远程业务对象接口一致,即桩对象就是远程业务对象在本地主机的代理对象。

在RMI实现过程中,远程主机有一个Skeleton(骨架)对象来负责与Stub对象通信,RMI的基本实现步骤:

客户端发起请求,将请求转交至RMI客户端的Stub类。
Stub类将请求的接口,方法,参数等信息进行序列化
将序列化后的流使用Socket传输至服务器端
服务器端接收到流后将其转发至相应的Skeleton类
Skeleton类将请求信息反序列化后调用实际的业务处理类
业务处理类处理完后将结果返回给Skeleton类
Skeleton类将结果序列化,再次通过Socket将流传送给客户端的Stub
Stub在接收到流后进行反序列化,将反序列化得到的Java Object对象返回给客户端调用者。

除了RMI,java语言还通过其他方式实现远程通信和远程方法调用,如Hessian。

虚拟代理(Virtual Proxy)

对于一些占用系统资源较多或者加载时间较长的对象,可给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后虚拟代理将用户的请求转发给真实对象。

Java动态代理

java的动态代理是基于接口的,CGlib是基于类的。

动态代理用于事务管理,日志管理,AOP等

Proxy类:用于创建动态代理类和实例对象的方法

public static Class<?> getProxyClass(ClassLoader loader, Class<?>...interfaces):该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h):该方法用于返回一个动态创建的代理类的实例,方法中的第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表,第三个参数h表示所指派的调用处理程序类

InvocationHandler接口:代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类。

public Object invoke(Object proxy, Method method, Object[] args);

动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时调用请求会将请求自动转发给InvocationHandler对象的invoke方法

相关推荐
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页