设计模式之结构型模式

  • adapter 、bridge、facade
–adapter用于两个不兼容接口之间的转接
–bridge用于将一个抽象与多个可能的实现连接起来
–facade用于为复杂的子系统定义一个新的简单易用的接口
  • composite、decorator和proxy
–composite用于构造对象组合结构
–decorator用于为对象增加新的职责
–proxy为目标对象提供一个替代者
  • flyweight
–针对细粒度对象的一种全局控制手段

装饰模式

意图:动态地给对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。装饰模式是为已有的功能动态地添加更多功能的一种方式。在起初的设计当中,当系统需要新功能的时候,是向旧的类中添加新的代码,这些新加的代码,通常装饰了原有类的核心职责或主要行为,在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能都放在单纯的类中,并让这个类包装它所要装饰的对象(就是抽象类Decorator中的Component字段),因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。

例如,商店为了促销,在不同时期将随售出的衣服附送一些小礼物,例如在圣诞节期间,附送圣诞树小挂件,在新年期间,附送贺卡红包等。
–在这种情况下,这些小礼物本身并不属于售出衣服的一部分,它们是在不同时期动态添加到售出衣服上的额外属性
–而且由于一年中会展开很多次这样的促销,因此,子类化售出衣服,使其适用于各个时期,就会造成类的数量膨胀,系统难易维护
–此时,装饰器模式就成了首选


角色及职责:

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任

特点:

  • 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互
  • 装饰对象包含一个真实对象的索引(reference)
  • 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象
  • 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展

适用范围:

  • 需要扩展一个类的功能,或给一个类增加附加责任。
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

优点:

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。

缺点:

  • 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
  • 一个装饰类和它装饰的组件并不是相同的。

实现:

  • 一个装饰类的接口必须与被装饰类的接口相容。
  • 尽量保持Component作为一个"轻"类,不要把太多的逻辑和状态放在Component类里。
  • 如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。
  • 如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。
  • 透明性的要求
    透明的装饰模式

    装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。换言之,下面的做法是对的:

    Component c = new ConcreteComponent();
    Component c1 = new ConcreteDecorator1(c);
    Component c2 = new ConcreteDecorator(c1);

    而下面的做法是不对的:

    ConcreteComponent c = new ConcreteDecorator();

    这就是前面所说的,装饰模式对客户端是完全透明的含义。

  • 半透明的装饰模式

    然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而雀儿有。这就意味着雀儿应当有一个新的fly()方法。这就导致了大多数的装饰模式的实现都是"半透明"(semi-transparent)的,而不是完全"透明"的。换言之,允许装饰模式改变接口,增加新的方法。即声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法。

 还是泡茶的问题,现在我们想要给泡好的茶加入新的作料使它成为一种新品种。

首先定义装饰类和被装饰类的共同接口类Tea:

public abstract class Tea
{
    boolean teaIsSteeped;
    public abstract void steepTea();
}
然后定义被装饰者TeaLeaves,它仅仅使用几片茶叶泡茶:
public class TeaLeaves extends Tea
{
    public TeaLeaves() {
      teaIsSteeped = false;
   }
   public void steepTea() {
      teaIsSteeped = true;
      System.out.println("tea leaves are steeping");
   }
}
装饰者ChaiDecrator,它持有一个被装饰者Tea的变量,并在构造函数中初始化它。它同样实现了接口中定义的方法steepTea(),在被装饰者中的 steepTea()运行后,它还为持有的被装饰者添加新的作料:

public class ChaiDecorator extends Tea {
    private Tea teaToMakeChai;
    private ArrayList chaiIngredients = new ArrayList();
    public ChaiDecorator(Tea teaToMakeChai) {
        this.addTea(teaToMakeChai);
        chaiIngredients.add("bay leaf");
        chaiIngredients.add("cinnamon stick");
        chaiIngredients.add("ginger");
        chaiIngredients.add("honey");
        chaiIngredients.add("soy milk");
        chaiIngredients.add("vanilla bean");
    }
    private void addTea(Tea teaToMakeChaiIn) {
        this.teaToMakeChai = teaToMakeChaiIn;
    }
    public void steepTea() {
        this.steepChai();
    }
    public void steepChai() {
        teaToMakeChai.steepTea();
        this.steepChaiIngredients();
        System.out.println("tea is steeping with chai");
    }
    public void steepChaiIngredients() {
        ListIterator listIterator = chaiIng        redients.listIterator();
        while (listIterator.hasNext()) {
            System.out.println(((String)(listIterator.next())) + " is steeping");
        }
        System.out.println("chai ingredients are steeping");
    }
}
测试代码:

class TestChaiDecorator {
    public static void main(String[] args) {
        Tea teaLeaves = new TeaLeaves();
        Tea chaiDecorator = new ChaiDecorator(teaLeaves);
        chaiDecorator.steepTea();
    }
}



 关于适配器模式和外观模式,《Head First Design Pattern》一书在第七章的封面很有意思:


适配器模式包装了某些对象,使它们的接口看起来不像自己而是像别的东西。这样做的目的是为了在设计中,将类的接口转换成想要的接口,以便实现不同的接口。而外观模式则是将对象包装起来以简化其接口。

适配器模式

定义:编写一个具有所需要接口的类,由他和拥有不同接口的类进行通信

意图将一个类的程序设计接口转换成另一个接口。复用已存在的接口与所需接口不一致的类。

现实生活中也有很多适配器,比如你买了一个台湾版的水果牌手机,在大陆就不能直接充电(插头的不一样),你得买个交流电适配器转换一下才能使用。OO中的适配器干得也是同样的事情。假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但这个新的厂商所设计出来的接口不同于旧厂商提供的接口。你不像改变现有的代码,这时就可以使用适配器模式。你可以写一个类,将新厂商接口转换成你希望的接口。



实现

  • 类适配器:从一个不一致的类派生出一个类,然后在派生类里面增加所需要的方法,使得派生类能够匹配所需要的接口。它使用 多重继承 来使得一个接口与另一个接口适配。
  • 对象适配器:将原始类包含在新类里,然后在新类里创建方法去转换调用。它依赖一个 对象的组合

两者比较:

角色及职责:

  • 源(Adaptee):已经存在的、需要适配的类。
  • 目标(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
  • 适配器(Adapter):适配器模式的核心类。有两种方式实现Adapter,对象适配器(Object Adapter)和类适配器(Class Adapter)。
  • 客户(Client):

UML图

补充:更高层次的适配器

从左图可以看出:Adapter将Adaptee接口适配为客户Client需要的接口Target,这样在整个系统中所有实现Adaptee接口的类都可以通过Adapter适配为Target对象,从而避免为每一个类都写一个适配器。后面会给大家带来一个JDK中使用此中适配的例子。我们不仅仅可以象上面一样对接口进行适配,也可以对抽象类进行适配!主要是根据系统的需求,确定此时的场景是否适合使用适配器模式!

适用范围:

  • 系统需要使用现有的类,而此类的接口不符合系统的需要
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
  • (对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。

 效果:

我们需要在类适配器和对象适配器之间做权衡

类适配器:

  • 因为类适配器是通过委托一个具体适配器类来使得被适配者适配于目标者的,这也意味着一个类适配器不可以适配一个类和它的所有子类。
  • 它不需要重新实现它的整个被适配者。必要的时候,还允许适配器覆盖被适配者的一些行为,因为适配器实际上就是被适配者的一个子类。
  • 仅仅需要一个对象——类适配器,而不需要一个适配器和一个被适配者,不需要其他间接的指针来得到被适配者。
  • 需要使用多重继承,这在像Java这样的语言中是不可能实现的。
对象适配器:
  • 允许一个适配器同时与多个被适配者,即被适配者本身和它所有的子类,工作。适配器还可以为所有被适配者增加新的功能。
  • 但是很难覆盖被适配者的行为。它需要把被适配者归入子类,而让适配器适用于子类而不是被适配者本身。
实现:
我们现在来看一个对象适配器的例子。考虑我们现在有一种茶叶,是由一种松散的茶叶构成。
被适配者(the adaptee):
public class LooseLeafTea {
    boolean teaIsSteeped;
    public LooseLeafTea() { 
        teaIsSteeped = false; 
    }
    public void steepTea() {
        teaIsSteeped = true;
        System.out.println("tea is steeping");
    }
}
而随着时代变迁,出现了茶包,即可以由很多不同的茶叶混合在一起做成一个包。我们有了一个新的目标(the target):
public class TeaBag {
    boolean teaBagIsSteeped;
    public TeaBag() { 
        teaBagIsSteeped = false; 
    }
    public void steepTeaInCup() {
        teaBagIsSteeped = true;
        System.out.println("tea bag is steeping in cup");
    }
}
我们再回顾下对象适配器的做法:它首先继承目标类(这里是TeaBag),然后 将原始类(这里是LooseLeafTea)包含在新类(适配器TeaBall)里,然后在新类里创建方法去转换调用。适配器(the adapter):
public class TeaBall extends TeaBag {
    LooseLeafTea looseLeafTea;
    public TeaBall(LooseLeafTea looseLeafTeaIn) {
        looseLeafTea = looseLeafTeaIn;
        teaBagIsSteeped = looseLeafTea.teaIsSteeped;
    }
    public void steepTeaInCup() {
        looseLeafTea.steepTea();
        teaBagIsSteeped = true;
    }
}
客户类(the client),它请求一个TeaBag来进行泡茶,通过适配器,我们使得它同样可以用以前的茶叶( LooseLeafTea)泡茶:
public class TeaCup {
    public void steepTeaBag(TeaBag teaBag) { 
        teaBag.steepTeaInCup(); 
    }
}
测试代码:
class TestTeaBagAdaptation {
    public static void main(String[] args) {
        TeaCup teaCup = new TeaCup();
        System.out.println("Steeping tea bag");
        TeaBag teaBag = new TeaBag();
        teaCup.steepTeaBag(teaBag);
        System.out.println("Steeping loose leaf tea");
        LooseLeafTea looseLeafTea = new LooseLeafTea();
        TeaBall teaBall = new TeaBall(looseLeafTea);
        teaCup.steepTeaBag(teaBall);
    }
}
通过上面的例子,我们可以看到,通过给适配器(TeaBall)传递一个被适配者(LooseLeafTea)的对象,我们得到了一个和目标(TeaBag)具有相同接口的对象,我们想泡哪种茶都可以啦!
总结:
我们再来看看各部分的关系。

客户使用适配器的过程如下:1)客户通过目标接口调用适配器的方法对适配器发出请求;2)适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口;3)客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用,因此客户和被适配者是解耦的,一个不知道另一个。


外观模式(Facade)

意图:为子系统提供了一个更高层次、更简单的接口,从而降低了子系统的复杂度和依赖。这使得子系统更易于使用和管理。对外提供一个统一的接口用来访问子系统中的一群接口。外观是一个能为子系统和客户提供简单接口的类。当正确的应用外观,客户不再直接和子系统中的类交互,而是与外观交互。外观承担与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统和客户的耦合度。外观对象隔离了客户和子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受到影响。

角色及职责:

  • 门面(Facade)这个外观类为子系统中Packages 1、2、3提供一个共同的对外接口
  • 客户(Clients)客户对象通过一个外观接口读写子系统中各接口的数据资源。
  • 子系统(Packages)客户可以通过外观接口读取的内部库。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。 

适用范围:

  • 为复杂的子系统提供一个简单的接口。不需要使用一个复杂系统的所有功能,而且可以创建一个新的类,包含访问系统的所有规则。如果只需要使用系统的部分功能,那么你为新类所创建的API将比原系统的API简单的多。
  • 在客户代码和一个抽象的实现之间存在很多依赖关系,通过一个facade来让客户代码和子系统之间实现解耦。
  • 希望将子系统分层,用多个facade为每个子系统等级定义一个入口。
  • 希望使用原系统的功能,而且还希望增加一些新的功能。
  • 编写新类的成本小于所有人学会使用或者未来维护原系统上所需的成本。

 效果:

  • 它将客户从子系统组件中分离出来,降低了客户代码交互的对象的数量,使得子系统更加易于使用。
  • 它使得子系统和它的客户实现松耦合。
  • 它并不对应用程序访问子系统造成阻碍,它依然将系统完整的功能暴露出来,以供需要的人使用。
来看一个例子。假想你给家里买了一套家庭影院系统(发奖金啦),这套系统很高级,内置DVD播放器、投影仪、自动屏幕、环绕立体声,甚至还有爆米花机……
这些组件如下:

你好不容易排好线,连接好各种设备,终于可以看电影啦!可是…… 看影片哪有那么容易,说明书上告诉你必须这么做:

而且看完之后,你还得倒着做一边来关掉电影。这时你就非常需要一个外观模式啦!
那么如何构造家庭影院外观呢?第一步,使用组合让外观能够访问子系统中所有的组件(这里指投影仪、DVD机、灯光等等):
然后实现看电影和关电影的方法:
剩下的就很简单啦!
附加:装饰者模式、适配器模式和外观模式的区别。
  • 适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观模式将一群对象“包装”起来以简化其接口。

桥接模式:

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

等级结构:

  • 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
  • 由实现化角色和两个具体实现化角色所组成的实现化等级结构。

角色及职责:

  • 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用
  • 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
  • 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。
优势和缺陷:

桥接模式可以从接口分离实现功能,使得设计更具有扩展性,这样,客户调用方法是根本不需要知道实现的细节。桥接模式的优点是减少了子类,如果程序中要在2个操作系统中实现查看6种图像格式,那么就会有2*6个类。使用桥接模式时候就会变成2+6个类(2个类定义了两个操作系统的抽象,6个类定义了查看了六种图像格式,因为没有把抽象和实现绑定在一起,因此只需要组合一下2种抽象和6种实现就可以达到目的)了,它使代码变得更清洁了,生成的执行程序更小了。但是桥接模式的缺陷是抽象类与实现类的双向连接使得运行速度更慢了

适用场景

  • 避免抽象方法和其实现方法永久地绑定在一起。
  • 抽象接口和它的实现都需要扩展出子类以备使用。
  • 变动实现的方法根本不会影响客户程序调用的部分(甚至不用重新编译)。
  • (C++)你想要对客户隐藏一个抽象类的实现。
  • 表明需要把一个对象分解成两个部分。
  • 你想要与多个对象共享一种实现方法(可能使用引用计数),并且这个事实应当对客户隐藏。
效果:
  • 将接口和实现进行解耦。
  • 增加了可扩展性。
  • 对客户隐藏了实现细节。
实现:
  • 只有一个实现者(Implementor)
  • 创建正确的实现者对象。
  • 共享实现。
  • 使用多重继承。
还是来看一个例子。考虑我们现在要制作两种沙拉(2中抽象化角色):五分熟沙拉(MediumSoda)和超级沙拉(SuperSizeSoda),每种沙拉我们又希望有3种不同的口味(3种实现方法):草莓味(CherrySodaImp)、葡萄味(GrapeSodaImp)和橘子味(OrangeSodaImp)。如果不使用桥接模式,我们需要定义六个类:草莓味五分熟沙拉、葡萄味五分熟沙拉、橘子味五分熟沙拉和草莓味超级沙拉、葡萄味超级沙拉、橘子味超级沙拉,而且以后如果我们有了更多的口味(比如西瓜味、水蜜桃味等等),我们需要定义更多的类!而使用桥接模式后,我们就可以把抽象和实现方法隔离开来。

首先看抽象化(Abstraction)角色,它保存一个对实现化对象(这里指SodaImp)的引用:
public abstract class Soda {
    SodaImp sodaImp;
    public void setSodaImp() {
        this.sodaImp = SodaImpSingleton.getTheSodaImp();
    }
    public SodaImp getSodaImp() {
        return this.sodaImp;
    }
    public abstract void pourSoda();
}
两个 修正抽象化 (Refined Abstraction) 角色:
五分熟沙拉(MediumSoda):
public class MediumSoda extends Soda {
    public MediumSoda() {
        setSodaImp();
    }
    public void pourSoda() {
        SodaImp sodaImp = this.getSodaImp();
        for (int i = 0; i < 2; i++) {
            System.out.print("...glug...");
            sodaImp.pourSodaImp();
        }
        System.out.println(" ");
    }
}
超级沙拉(SuperSizeSoda):
public class SuperSizeSoda extends Soda {
    public SuperSizeSoda() {
        setSodaImp();
    }
    public void pourSoda() {
        SodaImp sodaImp = this.getSodaImp();
        for (int i = 0; i < 5; i++) {
            System.out.print("...glug...");
            sodaImp.pourSodaImp();
        }
        System.out.println(" ");
    }
}
可以看到两种沙拉都持有一个沙拉的实现化角色,并在构造函数中初始化它。在我们的例子里,通过一个单件来控制当前是哪种实现方法:
public class SodaImpSingleton {
    private static SodaImp sodaImp;
    public SodaImpSingleton(SodaImp sodaImpIn) {
        this.sodaImp = sodaImpIn;
    }
    public static SodaImp getTheSodaImp() { 
        return sodaImp; 
    }
}

下面是实现化 (Implementor)角色,它给出实现化角色的接口,但不给出具体的实现:
public abstract class SodaImp {
    public abstract void pourSodaImp();
}
三种 具体实现化 (Concrete Implementor) 角色:
草莓味(CherrySodaImp):
public class CherrySodaImp extends SodaImp {
    CherrySodaImp() {}
    public void pourSodaImp() {
        System.out.println("Yummy Cherry Soda!"); 
    }
}
葡萄味(GrapeSodaImp):
public class GrapeSodaImp extends SodaImp {
    GrapeSodaImp() {}
    public void pourSodaImp() { 
        System.out.println("Delicious Grape Soda!"); 
    }
}
橘子味( OrangeSodaImp):
public class OrangeSodaImp extends SodaImp {
    OrangeSodaImp() {}
    public void pourSodaImp() { 
        System.out.println("Citrusy Orange Soda!"); 
    }
}
最后是测试代码,我们用三种口味来制作沙拉:
class TestBridge {
    public static void testCherryPlatform() {
        SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new CherrySodaImp());
        MediumSoda mediumSoda = new MediumSoda();
        mediumSoda.pourSoda();
        SuperSizeSoda superSizeSoda = new SuperSizeSoda();
        superSizeSoda.pourSoda();
    }
    public static void testGrapePlatform() {
        SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new GrapeSodaImp());
        MediumSoda mediumSoda = new MediumSoda();
        mediumSoda.pourSoda();
        SuperSizeSoda superSizeSoda = new SuperSizeSoda();
        superSizeSoda.pourSoda();
    }
    public static void testOrangePlatform() {
        SodaImpSingleton sodaImpSingleton = new SodaImpSingleton(new OrangeSodaImp());
        MediumSoda mediumSoda = new MediumSoda();
        mediumSoda.pourSoda();
        SuperSizeSoda superSizeSoda = new SuperSizeSoda();
        superSizeSoda.pourSoda();
    }
    public static void main(String[] args) {
        testCherryPlatform();
        testGrapePlatform();
        testOrangePlatform();
    }
}

组合模式

意图:将对象组合成树形结构以表示"部分-整体"的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

例如论坛里的回帖及每个回帖的回复,设计人员希望能够以统一的方式处理某个具体的帖子和某个帖子以及跟帖构成的树或子树,就可以使用组合模式。

透明模式

安全模式

分类:

  • 安全式的组合模式:管理聚集的方法(add()、remove()、getChild()等)只出现在组合对象中,而不出现在简单对象中。
  • 透明式的组合模式:所有的对象,不论组合对象还是简单对象,均符合一个固定的接口。在简单对象中管理聚集的方法的实现为空。

效果及实现要点:

  • Composite模式采用树形结构来实现普遍存在的对象容器,从而将"一对多"的关系转化"一对一"的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
  • 将"客户代码与复杂的对象容器结构"解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能"应对变化"。
  • Composite模式中,是将"Add和Remove等和对象容器相关的方法"定义在"表示抽象对象的Component类"中,还是将其定义在"表示对象容器的Composite类"中,是一个关乎"透明性"和"安全性"的两难问题,需要仔细权衡。这里有可能违背面向对象的"单一职责原则",但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
  • Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

适用性:

  • 你想表示对象的部分-整体层次结构
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

 我们还是考虑茶包。现在假设我们有一个大包可以装下很多小的单个茶叶包,同时它也可以装其他包含单个茶叶包的茶叶包,因为大家都是茶叶包嘛~


首先设计一个公共的接口类TeaBags,它定义了不管是简单对象(OneTeaBag)还是对象容器(TinOfTeaBags)功能的接口:

public abstract class TeaBags {
   LinkedList teaBagList;
   TeaBags parent;
   String name;
   public abstract int countTeaBags();
   public abstract boolean add(TeaBags teaBagsToAdd);
   public abstract boolean remove(TeaBags teaBagsToRemove);
   public abstract ListIterator createListIterator();
   public void setParent(TeaBags parentIn) {
      parent = parentIn;
   }
   public TeaBags getParent() {
      return parent;
   }
   public void setName(String nameIn) {
      name = nameIn;
   }
   public String getName() {
      return name;
   }
}
接下来是简单对象(OneTeaBag),注意都实际上所有的聚集操作(public boolean add(TeaBags teaBagsToAdd),public boolean remove(TeaBags teaBagsToRemove),public ListIterator createListIterator())它都不用实现:

public class OneTeaBag extends TeaBags {
    public OneTeaBag(String nameIn) {
        this.setName(nameIn);
    }
    public int countTeaBags() {
        return 1;
    }
    public boolean add(TeaBags teaBagsToAdd) {
        return false;
    }
    public boolean remove(TeaBags teaBagsToRemove) {
        return false;
    }
    public ListIterator createListIterator() {
        return null;
    }
}
对象容器(TinOfTeaBag):

public class TinOfTeaBags extends TeaBags {
    public TinOfTeaBags(String nameIn) {
        teaBagList = new LinkedList();
        this.setName(nameIn);
    }
    public int countTeaBags() {
        int totalTeaBags = 0;
        ListIterator listIterator = this.createListIterator();
        TeaBags tempTeaBags;
        while (listIterator.hasNext()) {
            tempTeaBags = (TeaBags)listIterator.next();
            totalTeaBags += tempTeaBags.countTeaBags();
        }
        return totalTeaBags;
    }
    public boolean add(TeaBags teaBagsToAdd) {
        teaBagsToAdd.setParent(this);
        return teaBagList.add(teaBagsToAdd);
    }
    public boolean remove(TeaBags teaBagsToRemove) {
        ListIterator listIterator = this.createListIterator();
        TeaBags tempTeaBags;
        while (listIterator.hasNext()){
            tempTeaBags = (TeaBags)listIterator.next();
            if (tempTeaBags == teaBagsToRemove) {
                listIterator.remove();
                return true;
            }
        }
        return false;
    }
    public ListIterator createListIterator() {
        ListIterator listIterator = teaBagList.listIterator();
        return listIterator;
    }
}
最后是测试代码:

class TestTeaBagsComposite
{
    public static void main(String[] args){
        TeaBags tinOfTeaBags = new TinOfTeaBags("tin of tea bags"); // 先创建一个对象容器tinOfTeaBags
        TeaBags teaBag1 = new OneTeaBag("tea bag 1");  //再创建两个简单对象
        TeaBags teaBag2 = new OneTeaBag("tea bag 2");

        tinOfTeaBags.add(teaBag1);  // 向对象容器中添加这两个简单对象
        tinOfTeaBags.add(teaBag2);

        System.out.println("The tinOfTeaBags now has " + tinOfTeaBags.countTeaBags() + " tea bags in it.");
        System.out.println(" ");

        TeaBags smallTinOfTeaBags = new TinOfTeaBags("small tin of tea bags");  // 再定义另一个对象容器smallTinOfTeaBags
        TeaBags teaBag3 = new OneTeaBag("tea bag 3");  // 同样向这个新的对象容器中添加一个新的简单对象
        smallTinOfTeaBags.add(teaBag3);
        System.out.println("The smallTinOfTeaBags now has " + smallTinOfTeaBags.countTeaBags() + " tea bags in it.");

        tinOfTeaBags.add(smallTinOfTeaBags); // 向对象容器tinOfTeaBags添加对象容器smallTinOfTeaBags,可以看出它和添加一个简单对象是完全相同的
        System.out.println("The tinOfTeaBags now has " + t inOfTeaBags.countTeaBags() + " tea bags in it.");
        System.out.println(" ");

        tinOfTeaBags.remove(teaBag2);  // 从对象容器tinOfTeaBag中移除另一个对象容器
        System.out.println("The tinOfTeaBags now has " + tinOfTeaBags.countTeaBags() + " tea bags in it.");
    }
}


享元模式

意图:用一个共享来避免大量拥有相同内容对象的开销。这种开销中最常见、直观的就是内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象。

对象的状态:

  • 内蕴状态(Internal State)内蕴状态存储在享元对象内部且不会随环境改变而改变。因此内蕴状态并可以共享。
  • 外蕴状态(External State)。外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。

适用范围:

  • 一个系统有大量的对象。
  • 这些对象耗费大量的内存。
  • 这些对象的状态中的大部分都可以外部化。
  • 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
  • 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

分类:

  • 单纯享元模式 

角色及职责:

1>:抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入

/// <summary>
    
/// "Flyweight"
    
/// </summary>
    abstract class Flyweight
    {
        
// Methods
        /// <summary>
        
/// 抽象享元对象的商业方法
        
/// </summary>
        
/// <param name="extrinsicstate">外蕴状态</param>
        abstract public void Operation(int extrinsicstate);
    }

2>:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。

/// <summary>
    
/// "ConcreteFlyweight"
    
/// </summary>
    class ConcreteFlyweight : Flyweight
    {
        
private string intrinsicstate = "A";
        
// Methods
        override public void Operation(int extrinsicstate)
        {
            Console.WriteLine(
"ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}",intrinsicstate, extrinsicstate);
        }
    }

3>:享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

注意:客户端不可以直接实例化享元类,必须通过享元工厂类来创建,因为享元工厂类在系统中只能有一个,所以可以结合单件模式来改善。当客户端需要单纯享元对象时,需要调用享元工厂的Singleton()方法,此时工厂会取得所有的单纯享元对象,然后传入所需的单纯享元对象的内蕴状态,工厂方法负责产生所需要的享元对象。

/// <summary>
    
/// "FlyweightFactory"
    
/// </summary>
    class FlyweightFactory
    {
        
// Fields
        private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
        
private static readonly FlyweightFactory instance = new FlyweightFactory();
        
/// <summary>
        
/// Constructors
        
/// </summary>
        private  FlyweightFactory()
        {          
        }
        
// Methods
        /// <summary>
        
/// 从享元工厂中生产出一个具体的享元对象
        
/// </summary>
        
/// <param name="key">内蕴状态</param>
        
/// <returns></returns>
        public Flyweight GetFlyweight(string key)
        {
            
return ((Flyweight)flyweights[key]);
        }
        
/// <summary>
        
/// 享元工厂单例方法
        
/// </summary>
        
/// <returns></returns>
        public static  FlyweightFactory Singleton()
        {
            
return FlyweightFactory.instance;
        } 
        
/// <summary>
        
/// 向享元工厂对象增加一个享元对象
        
/// </summary>
        
/// <param name="sKey">内蕴状态</param>
        
/// <param name="_Flyweight">具体享元对象</param>
        public void AddFlyweight(string sKey, Flyweight _Flyweight)
        {
            flyweights.Add(sKey , _Flyweight); 
        }
        
public Flyweight factory(string sKey)
        {
            
if (flyweights.ContainsKey(sKey))
            {
                
return this.GetFlyweight(sKey);
            }
            
else
            {
                
this.AddFlyweight(sKey, new ConcreteFlyweight());
                
return this.GetFlyweight(sKey);
            }      
        }
    }

4>:客户端(Client)角色:需要维护一个对所有享元对象的引用;需要自行存储所有享元对象外蕴状态。

// 初始化外蕴状态值
            int extrinsicstate = 22;
            
//享元工厂对象使用单例
            FlyweightFactory f = FlyweightFactory.Singleton () ;
         
            
//调用过程
            //向享元工厂对象请求一个内蕴状态为"X"的单纯享元对象
            Flyweight fx = f.factory("X");
            
//调用X的商业方法,X的外蕴状态值为21
            fx.Operation(--extrinsicstate);

            Flyweight fy = f.factory(
"Y");
            fy.Operation(--extrinsicstate);

            Flyweight fz = f.factory(
"Z");
            fz.Operation(--extrinsicstate);

复合享元模式。

         

1>:抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
2>:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
3>:复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象。
4>:享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
5>:客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。

优点: 大幅度地降低内存中对象的数量。

缺点:

  • 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
  • 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

总结:

享元模式一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用.

 

代理模式

意图:为其他对象提供一种代理以控制对这个对象的访问。



角色及职责

  • 抽象主题角色:声明了真实主题和代理主题的共同接口。
  • 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。
  • 真实主题角色:定义真实的对象。

适用范围:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。比如:你可以将一个在世界某个角落一台机器通过代理假象成你局域网中的一部分。
  • 虚拟(Virtual)代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建。比如:如果一个很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,这个大图片可能就影响了文档的阅读,这时需要做个图片Proxy来代替真正的图片。
  • 保护(Protect or Access)代理:控制对一个对象的访问权限。比如:在论坛中,不同的身份登陆,拥有的权限是不同的,使用代理模式可以控制权限(当然,使用别的方式也可以实现)。
  • 智能引用(Smart Reference)代理:提供比对目标对象额外的服务。比如:纪录访问的流量(这是个再简单不过的例子),提供一些友情提示等等。

实现:

  • 指明一系列接口创建一个代理对象
  • 创建调用处理器对象
  • 将这个代理指定为其他代理的代理对象

在调用处理器的invoke()方法中采取处理,一方面将调用传递给真是对象,另一方面执行各种所需要的操作。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值