设计模式(结构型)
设计模式 --结构型
范围\目的 |
创建型模式 |
结构型模式 |
行为型模式 |
类模式 |
工厂方法模式 |
(类)适配器模式 |
解释器模式 模板方法模式 |
对象模式 |
抽象工厂模式 建造者模式 原型模式 单例模式 |
(对象)适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 |
职责链模式 命令模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 访问者模式 |
根据目的、用途的不同,分为创建性模式、结构性模式、行为性模式。创建型模式主要用于创建对象,结构型模式主要用于处理类和对象的组合,行为性模式主要用于描述类或对象的交互以及职责分配。
根据处理范围不同,设计模式又可分为类模式和对象模式,类模式处理类与子类的关系,通过处理这些关系来建立继承,属于静态关系,在编译时候确定下来;对象模式处理对象之间的关系,运行时发生变化,属于动态关系。
2.结构型设计模式Structural(7种)
结构型模式所描述的是如何将类和对象结合在一起来形成一个更大的结构,它描述两种不同的事物:类和对象,根据这一点,可分为类结构型和对象结构型模式。类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式
一. 适配器模式(对象型和类型)
模式动机
a) 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。
b) 通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
c) 在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
d) 在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类。
e) 适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
定义:将一个接口转换成客户希望的另一个接口,从而使接口不兼容的那些类可以在一起工作。
说明:适配器模式既可作为类结构模式,也可作为对象结构模式,类适配模式是通过一个具体的类,将适配者适配到目标接口当中;对象适配模式是指一个适配器可以将多个不同的适配者适配到同一目标
类适配器模式:
public class Adapter extends Adaptee implements Target { public void request() { specificRequest(); } }
public class Adapter extends Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); } }
适配器模式包含如下角色:
Target:目标抽象类
Adapter:适配器类
Adaptee:适配者类
Client:客户类
适配器模式的优点
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
类适配器模式还具有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
类适配器模式的缺点如下:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
在以下情况下可以使用适配器模式:
系统需要使用现有的类,而这些类的接口不符合系统的需要。
想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
二. 桥接模式(对象型模式)
将抽象部分和实现部分分离,使它们都可以独立地发生变化,桥接模式是一种对象的结构模式,桥接模式类似于多重继承方案,但是多重继承方案往往违背了类的单一职责原则,并且复用性较差,桥接模式相对于多重继承来说,是更好的解决方案,桥接模式既能达到多重继承的用途,又能有利于复用。
模式动机
设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
第一种设计方案是为每一种形状都提供一套各种颜色的版本。
第二种设计方案是根据实际需要对形状和颜色进行组合。
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量
定义:将抽象部分和实现部分分离,使它们都可以独立地发生变化
桥接模式包含如下角色:
Abstraction:抽象类
RefinedAbstraction:扩充抽象类
Implementor:实现类接口
ConcreteImplementor:具体实现类
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
在以下情况下可以使用桥接模式:
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
三.组合模式(对象型模式),也称为整体-部分模式
是一种整体和部分的结构,通过组合多个对象形成树形结构,用这种方式表示整体和部分的结构层次。在组合模式中,对单个对象和组合对象的使用具有一致性。
定义:将对象组合成树形结构以表示整体和部分的层次结构,使得用户对单个对象和组合对象的使用具有一致性
组合模式包含如下角色:
- Component: 抽象构件
- Leaf: 叶子构件
- Composite: 容器构件
- Client: 客户类
在以下情况下可以使用组合模式:
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
- 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们。
四.装饰模式(对象模式)
一般有两种方式可以实现给一个类或对象增加行为:
a) 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
b) 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。
它可以动态地对一个对象增加一些额外的职责,就增加对象功能来说,比生成子类更加灵活一些,为类增加职责就是定义类的功能,为达到这个目的,可以增加子类的方法来实现,但是装饰模式也可以做到,而且更加灵活。
定义:动态地对一个对象增加额外的职责。它提供了用子类扩展功能的一个灵活的替代,比派生一个子类更加灵活。
当需要为对象动态的添加一些功能,并且可以动态的撤销或是不能使用子类展功能的时候,这两种情况时可以使用装饰模式
装饰模式包含如下角色:
- Component: 抽象构件
- ConcreteComponent: 具体构件
- Decorator: 抽象装饰类
- ConcreteDecorator: 具体装饰类
在以下情况下可以使用装饰模式:
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)。
典型的抽象装饰类代码:
public class Decorator extends Component { private Component component; public Decorator(Component component) { this.component=component; } public void operation() { component.operation(); } }
典型的具体装饰类代码:
public class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void operation() { super.operation(); addedBehavior(); } public void addedBehavior() { //新增方法 } }
五. 外观模式(对象结构模式)
定义:定义一个高层接口,为子系统中的一组接口提供一个一致的外观,从而简化了该子系统的使用
外观模式包含如下角色:
Facade: 外观角色
SubSystem:子系统角色
典型的外观角色代码:
public class Facade { private SubSystemA obj1 = new SubSystemA(); private SubSystemB obj2 = new SubSystemB(); private SubSystemC obj3 = new SubSystemC(); public void method() { obj1.method(); obj2.method(); obj3.method(); } }
在以下情况下可以使用外观模式:
当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。
客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。
在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
不要试图通过外观类为子系统增加新行为
不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。
外观模式与迪米特法则
外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。
六.享元模式(对象型模式)
定义:提供支持大量细粒度对象共享的有效方法
通过运用共享技术,有效地自制细粒度的对象,系统只使用少量的对象,而这些对象都很类似,状态变化很小,对象使用次数增多。享元对象可以做到共享的关键,是能区分出内部状态和外部状态。内部状态是存储在享元对象的内部,并且不会随时间和环境而发生变化,内部状态是可以共享的。外部状态是随着外部环境的改变而改变的,不可以共享,所以外部状态必须由客户端保存,并且在享元对象被创建之后,在需要使用的时候,传递到享元对象的内部,外部状态之间是相互独立的。
七. 代理模式(对象型模式)
模式动机
a)在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
定义:为其他对象提供一种代理,由代理对象控制这个对象的访问
协调调用者和被调用者,减低耦合度
缺点是请求速度变慢,而且增加了额外的工作量
代理模式包含如下角色:
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
根据代理模式的使用目的,常见的代理模式有以下几种类型:
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。