1 结构型模式
结构型模式主要涉及到如何组合类和对象以获得更大的结构。结构型模式采用继承机制来组合接口或者实现。
1.1 适配器模式
1.1.1 意图
Adapter: 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能工作的类可以一起工作。
1.1.2实用性
以下情况使用A d a p t e r模式
• 你想使用一个已经存在的类,而它的接口不符合你的需求。
• 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
• (仅适用于对象A d a p t e r)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
1.1.3结构
1.1.4参与者
• Ta r g e t ( S h a p e )
— 定义C l i e n t使用的与特定领域相关的接口。
• C l i e n t ( D r a w i n g E d i t o r )
— 与符合Ta rg e t接口的对象协同。
• A d a p t e e ( Te x t Vi e w )
— 定义一个已经存在的接口,这个接口需要适配。
• A d a p t e r ( Te x t S h a p e )
— 对A d a p t e e的接口与Ta rg e t接口进行适配
1. Target
/**
*
目标角色
*/
public interface Target {
//
目标角色有自己的方法
public void request();
}
2.
Adaptee
/**
*
源角色
*/
public class Adaptee {
//
原有的业务逻辑
public void doSomething(){
System.out.println("I'm kind of busy,leave me alone,pls!");
}
}
3.
Adapter
/**
*
适配器
*/
public class Adapter extends Adaptee implements Target {
public void request() {
super.doSomething();
}
}
1.2 桥接模式
1.2.1 意图
Bridge: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
1.2.2实用性
以下一些情况使用B r i d g e模式:
• 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
• 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时B r i d g e模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
• 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
• (C + +)你想对客户完全隐藏抽象的实现部分。在C + +中,类的表示在类接口中是可见的。
• 正如在意图一节的第一个类图中所示的那样,有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。R u m b a u g h称这种类层次结构为“嵌套的普化”(nested generalizations)。
• 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。
1.2.3结构
1.2.4参与者
• Abstraction
— 定义抽象类的接口。
— 维护一个指向I m p l e m e n t o r类型对象的指针。
• RefinedAbstraction
— 扩充由A b s t r a c t i o n定义的接口。
• Implementor
— 定义实现类的接口,该接口不一定要与A b s t r a c t i o n的接口完全一致;事实上这两个接口可以完全不同。一般来讲, I m p l e m e n t o r接口仅提供基本操作,而A b s t r a c t i o n则定义了基于这些基本操作的较高层次的操作。
• ConcreteImplementor
— 实现I m p l e m e n t o r接口并定义它的具体实现。
1. Abstraction
public abstract class Abstraction {
//
定义对实现化角色的引用
private Implementor imp;
//
约束子类必须实现该构造函数
public Abstraction(Implementor _imp){
this.imp = _imp;
}
//
自身的行为和属性
public void request(){
this.imp.doSomething();
}
//
获得实现化角色
public Implementor getImp(){
return imp;
}
}
2.
RefinedAbstraction
public class RefinedAbstraction extends Abstraction {
//
覆写构造函数
public RefinedAbstraction(Implementor _imp){
super(_imp);
}
//
修正父类的行文
@Override
public void request(){
/*
*
业务处理....
*/
super.request();
super.getImp().doAnything();
}
}
3.
Implementor
public interface Implementor {
//
基本方法
public void doSomething();
public void doAnything();
}
4.
ConcreteImplementor1
public class ConcreteImplementor1 implements Implementor{
public void doSomething(){
//
业务逻辑处理
}
public void doAnything(){
//
业务逻辑处理
}
}
5.
Client
public class Client {
public static void main(String[] args) {
//
定义一个实现化角色
Implementor imp = new ConcreteImplementor1();
//
定义一个抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
//
执行行文
abs.request();
}
}
1.3 组合模式
1.3.1 意图
Composite: 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
1.3.2实用性
以下情况使用C o m p o s i t e模式:
• 你想表示对象的部分-整体层次结构。
• 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
1.3.3结构
1.3.4优缺点
组合模式有一个非常明显的缺点,直接使用了实现类,违反了依赖倒置原则。
1.3.5参与者
• Component
— 为组合中的对象声明接口。
— 在适当的情况下,实现所有类共有接口的缺省行为。
— 声明一个接口用于访问和管理C o m p o n e n t的子组件。
—(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
• Leaf
— 在组合中表示叶节点对象,叶节点没有子节点。
— 在组合中定义图元对象的行为。
• Composite
— 定义有子部件的那些部件的行为。
— 存储子部件。
— 在C o m p o n e n t接口中实现与子部件有关的操作。
• Client
— 通过C o m p o n e n t接口操纵组合部件的对象。
1. Component
public abstract class Component {
//
个体和整体都具有的共享
public void doSomething(){
//
编写业务逻辑
}
}
2.
Composite
public class Composite extends Component {
//
构件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
//
增加一个叶子构件或树枝构件
public void add(Component component){
this.componentArrayList.add(component);
}
//
删除一个叶子构件或树枝构件
public void remove(Component component){
this.componentArrayList.remove(component);
}
//
获得分支下的所有叶子构件和树枝构件
public ArrayList<Component> getChildren(){
return this.componentArrayList;
}
}
3.
Leaf
public class Leaf extends Component {
/*
*
可以覆写父类方法
* public void doSomething(){
*
* }
*/
}
4.
Client
public class Client {
public static void main(String[] args) {
//
创建一个根节点
Composite root = new Composite();
root.doSomething();
//
创建一个树枝构件
Composite branch = new Composite();
//
创建一个叶子节点
Leaf leaf = new Leaf();
//
建立整体
root.add(branch);
branch.add(leaf);
}
//
通过递归遍历树
public static void display(Composite root){
for(Component c:root.getChildren()){
if(c instanceof Leaf){ //
叶子节点
c.doSomething();
}else{ //
树枝节点
display((Composite)c);
}
}
}
}
1.4 装饰模式
1.4.1 意图
Decorator: 动态地给一个对象添加一些额外的职责。就添加功能来说,比生产子类更加灵活。
1.4.2实用性
以下情况使用D e c o r a t o r模式
• 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
• 处理那些可以撤消的职责。
• 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
1.4.3结构
1.4.4优缺点
装饰模式可以独立发展,而且不会相互耦合,但是多层的装饰比较复杂,尽量减少装饰类的数量,以便降低系统的复杂度。
1.4.5参与者
• Component
— 定义一个对象接口,可以给这些对象动态地添加职责。
• C o n c r e t e C o m p o n e n t
— 定义一个对象,可以给这个对象添加一些职责。
• D e c o r a t o r
— 维持一个指向C o m p o n e n t对象的指针,并定义一个与C o m p o n e n t接口一致的接口。
• C o n c r e t e D e c o r a t o r
— 向组件添加职责。
1. Component
public abstract class Component {
//
抽象的方法
public abstract void operate();
}
2.
ConcreteComponent
public class ConcreteComponent extends Component {
//
具体实现
@Override
public void operate() {
System.out.println("do Something");
}
}
3.
Decorator
public abstract class Decorator extends Component {
private Component component = null;
//
通过构造函数传递被修饰者
public Decorator(Component _component){
this.component = _component;
}
//
委托给被修饰者执行
@Override
public void operate() {
this.component.operate();
}
}
4.
ConcreteDecorator1
public class ConcreteDecorator1 extends Decorator {
//
定义被修饰者
public ConcreteDecorator1(Component _component){
super(_component);
}
//
定义自己的修饰方法
private void method1(){
System.out.println("method1
修饰");
}
//
重写父类的Operation方法
public void operate(){
this.method1();
super.operate();
}
}
5.
Client
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//
第一次修饰
component = new ConcreteDecorator1(component);
//
第二次修饰
component = new ConcreteDecorator2(component);
//
修饰后运行
component.operate();
}
}
1.5 门面模式
1.5.1 意图
Facade: 为子系统中的一组接口提供一个一致的界面。Façade模式定义了一个高层接口,这个接口是的这一子系统更加容易使用。
1.5.2实用性
在遇到以下情况使用F a c a d e模式
• 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。F a c a d e可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过f a c a d e层。
• 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入f a c a d e将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
• 当你需要构建一个层次结构的子系统时,使用f a c a d e模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过f a c a d e进行通讯,从而简化了它们之间的依赖关系。
1.5.3结构
1.5.4优缺点
减少了系统的相互依赖,提高了灵活性,安全性。缺点是不符合开闭原则。
1.5.5参与者
• F a c a d e
— 知道哪些子系统类负责处理请求。
— 将客户的请求代理给适当的子系统对象。
• Subsystem classes
— 实现子系统的功能。
— 处理由F a c a d e对象指派的任务。
— 没有f a c a d e的任何相关信息;即没有指向f a c a d e的指针。
1. ClassA
public class ClassA {
public void doSomethingA(){
//
业务逻辑
}
}
2.
ClassB
public class ClassB{
public void doSomethingB(){
//
业务逻辑
}
}
3.
ClassC
public class ClassC{
public void doSomethingC(){
//
业务逻辑
}
}
4.
Facade
public class Facade {
//
被委托的对象
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
//
提供给外部访问的方法
public void methodA(){
this.a.doSomethingA();
}
public void methodB(){
this.b.doSomethingB();
}
public void methodC(){
this.c.doSomethingC();
}
}
1.6 享元模式
1.6.1 意图
Flyweight: 运用共享技术有效地支持大量细粒度的对象。
1.6.2实用性
F l y w e i g h t模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用F l y w e i g h t模式:
• 一个应用程序使用了大量的对象。
• 完全由于使用大量的对象,造成很大的存储开销。
• 对象的大多数状态都可变为外部状态。
• 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
• 应用程序不依赖于对象标识。由于F l y w e i g h t对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
1.6.3结构
1.6.4参与者
• F l y w e i g h t
— 描述一个接口,通过这个接口f l y w e i g h t可以接受并作用于外部状态。
• C o n c r e t e F l y w e i g h t( C h a r a c t e r )
— 实现F l y w e i g h t 接口, 并为内部状态(如果有的话) 增加存储空间。
C o n c r e t e F l y w e i g h t对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于C o n c r e t e F l y w e i g h t对象的场景。
• U n s h a r e d C o n c r e t e F l y w e i g h t ( R o w, C o l u m n )
— 并非所有的F l y w e i g h t子类都需要被共享。F l y w e i g h t接口使共享成为可能,但它并不强制共享。在F l y w e i g h t对象结构的某些层次, U n s h a r e d C o n c r e t e F l y w e i g h t对象通常将C o n c r e t e F l y w e i g h t对象作为子节点(R o w和C o l u m n就是这样)。
• F l y w e i g h t F a c t o r y
— 创建并管理f l y w e i g h t对象。
— 确保合理地共享f l y w e i g h t。当用户请求一个f l y w e i g h t时,F l y w i g h t F a c t o r y对象提供一个已创建的实例或者创建一个(如果不存在的话)。
• Client
— 维持一个对f l y w e i g h t的引用。
— 计算或存储一个(多个) f l y w e i g h t的外部状态。
1. Flyweight
public abstract class Flyweight {
//
内部状态
private String intrinsic;
//
外部状态
protected final String Extrinsic;
//
要求享元角色必须接受外部状态
public Flyweight(String _Extrinsic){
this.Extrinsic = _Extrinsic;
}
//
定义业务操作
public abstract void operate();
//
内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
2.
ConcreteFlyweight1
public class ConcreteFlyweight1 extends Flyweight{
//
接受外部状态
public ConcreteFlyweight1(String _Extrinsic){
super(_Extrinsic);
}
//
根据外部状态进行逻辑处理
public void operate(){
//
业务逻辑
}
}
3.
ConcreteFlyweight2
public class ConcreteFlyweight2 extends Flyweight{
//
接受外部状态
public ConcreteFlyweight2(String _Extrinsic){
super(_Extrinsic);
}
//
根据外部状态进行逻辑处理
public void operate(){
//
业务逻辑
}
}
4.
FlyweightFactory
public class FlyweightFactory {
//
定义一个池容器
private static HashMap<String,Flyweight> pool= new HashMap<String,Flyweight>();
//
享元工厂
public static Flyweight getFlyweight(String Extrinsic){
//
需要返回的对象
Flyweight flyweight = null;
//
在池中没有改对象
if(pool.containsKey(Extrinsic)){
flyweight = pool.get(Extrinsic);
}else{
//
根据外部状态创建享元对象
flyweight = new ConcreteFlyweight1(Extrinsic);
//
放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
1.7 代理模式
1.7.1 意图
Proxy: 为其他对象提供一种代理以控制对这个对象的访问。
1.7.2实用性
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用P r o x y模式。下面是一
些可以使用P r o x y模式常见情况:
1) 远程代理( Remote Proxy ) 为一个对象在不同的地址空间提供局部代表。
NEXTSTEP[Add94] 使用N X P r o x y类实现了这一目的。Coplien[Cop92] 称这种代理为“大使”
2 ) 虚代理(Virtual Proxy)根据需要创建开销很大的对象。在动机一节描述的I m a g e P r o x y
就是这样一种代理的例子。
3) 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同
的访问权限的时候。例如,在C h o i c e s操作系统[ C I R M 9 3 ]中K e m e l P r o x i e s为操作系统对象提供
了访问保护。
4 ) 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。
它的典型用途包括:
• 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为S m a r t
P o i n t e r s[ E d e 9 2 ] )。
• 当第一次引用一个持久对象时,将它装入内存。
• 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
1.7.3结构
1.7.4参与者
• P r o x y
— 保存一个引用使得代理可以访问实体。若R e a l S u b j e c t和S u b j e c t的接口相同,P r o x y会引用S u b j e c t。
— 提供一个与S u b j e c t的接口相同的接口,这样代理就可以用来替代实体。
— 控制对实体的存取,并可能负责创建和删除它。
— 其他功能依赖于代理的类型:
• Remote Pro x y负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
• Vi rtual Pro x y可以缓存实体的附加信息,以便延迟对它的访问。例如,动机一节中提到的I m a g e P r o x y缓存了图像实体的尺寸。
• P rotection Pro x y检查调用者是否具有实现一个请求所必需的访问权限。
• S u b j e c t
— 定义RealSubject 和P r o x y的共用接口,这样就在任何使用R e a l S u b j e c t的地方都可以使用P r o x y。
• R e a l S u b j e c t
— 定义P r o x y所代表的实体。
1. Subject
public interface Subject {
//
定义一个方法
public void request();
}
2.
RealSubject
public class RealSubject implements Subject {
//
实现方法
public void request() {
//
业务逻辑处理
}
}
3.
Proxy
public class Proxy implements Subject {
//
要代理哪个实现类
private Subject subject = null;
//
默认被代理者
public Proxy(){
this.subject = new Proxy();
}
public Proxy(Subject _subject){
this.subject = _subject;
}
//
通过构造函数传递代理者
public Proxy(Object...objects ){
}
//
实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//
预处理
private void before(){
//do something
}
//
善后处理
private void after(){
//do something
}
}
4.
Client
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject());
proxy.request();
}
1.8 结构型模式的讨论
你可能已经注意到了结构型模式之间的相似性,尤其是它们的参与者和协作之间的相似性。这可能是因为结构型模式依赖于同一个很小的语言机制集合构造代码和对象:单继承和多重继承机制用于基于类的模式,而对象组合机制用于对象式模式。但是这些相似性掩盖了这些模式的不同意图。在本节中,我们将对比这些结构型模式,使你对它们各自的优点有所了解。
1.8.1Adapter与Bridge
A d a p t e r(4 . 1)模式和B r i d g e(4 . 2)模式具有一些共同的特征。它们都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。它们都涉及到从自身以外的一个接口向这个对象转发请求。
这些模式的不同之处主要在于它们各自的用途。A d a p t e r模式主要是为了解决两个已有接口之间不匹配的问题。它不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化。这种方式不需要对两个独立设计的类中的任一个进行重新设计,就能够使它们协同工作。另一方面, B r i d g e模式则对抽象接口与它的(可能是多个)实现部分进行桥接。虽然这一模式允许你修改实现它的类,它仍然为用户提供了一个稳定的接口。B r i d g e模式也会在系统演化时适应新的实现。
由于这些不同点, A d a p t e r和B r i d g e模式通常被用于软件生命周期的不同阶段。当你发现两个不兼容的类必须同时工作时,就有必要使用A d a p t e r模式,其目的一般是为了避免代码重复。此处耦合不可预见。相反, B r i d g e的使用者必须事先知道:一个抽象将有多个实现部分,并且抽象和实现两者是独立演化的。A d a p t e r模式在类已经设计好后实施;而B r i d g e模式在设计类之前实施。这并不意味着A d a p t e r模式不如B r i d g e模式,只是因为它们针对了不同的问题。你可能认为f a c a d e (参见F a c a d e ( 4 . 5 ) )是另外一组对象的适配器。但这种解释忽视了一个事实:即F a c a d e定义一个新的接口,而A d a p t e r则复用一个原有的接口。记住,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。
1.8.2Composite、Decorator与Proxy
C o m p o s i t e ( 4 . 3 )模式和D e c o r a t o r ( 4 . 4 )模式具有类似的结构图,这说明它们都基于递归组合来组织可变数目的对象。这一共同点可能会使你认为, d e c o r a t o r对象是一个退化的c o m p o s i t e,但这一观点没有领会D e c o r a t o r模式要点。相似点仅止于递归组合,同样,这是因为这两个式的目的不同。
Decorator 旨在使你能够不需要生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。C o m p o s i t e则有不同的目的,它旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理。它重点不在于修饰,而在于表示。尽管它们的目的截然不同,但却具有互补性。因此Composite 和D e c o r a t o r模式通常协同使用。在使用这两种模式进行设计时,我们无需定义新的类,仅需将一些对象插接在一起即可构建应用。这时系统中将会有一个抽象类,它有一些c o m p o s i t e子类和d e c o r a t o r子类,还有一些实现系统的基本构建模块。此时, composites 和d e c o r a t o r将拥有共同的接口。从
D e c o r a t o r模式的角度看,c o m p o s i t e是一个C o n c r e t e C o m p o n e n t。而从c o m p o s i t e模式的角度看,d e c o r a t o r则是一个L e a f。当然,他们不一定要同时使用,正如我们所见,它们的目的有很大的差别。
另一种与D e c o r a t o r模式结构相似的模式是P r o x y ( 4 . 7 )。这两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy 和d e c o r a t o r对象的实现部分都保留了指向另一个对象的指针,它们向这个对象发送请求。然而同样,它们具有不同的设计目的。像D e c o r a t o r模式一样, Proxy 模式构成一个对象并为用户提供一致的接口。但与D e c o r a t o r模式不同的是, Proxy 模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。在P r o x y模式中,实体定义了关键功能,而P r o x y提供(或拒绝)对它的访问。在D e c o r a t o r模式中,组件仅提供了部分功能,而一个或多个D e c o r a t o r负责完成其他功能。D e c o r a t o r模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为D e c o r a t o r模式中一个必不可少的部分。而在P r o x y模式中则不是这样,因为P r o x y模式强调一种关系( P r o x y与它的实体之间的关系),这种关系可以静态的表达。模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生问题的解决方法。但这并不意味着这些模式不能结合使用。可以设想有一个p r o x y - d e c o r a t o r,它可以给p r o x y添加功能,或是一个d e c o r a t o r- p r o x y用来修饰一个远程对象。