意图:动态地给对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。装饰模式是为已有的功能动态地添加更多功能的一种方式。在起初的设计当中,当系统需要新功能的时候,是向旧的类中添加新的代码,这些新加的代码,通常装饰了原有类的核心职责或主要行为,在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能都放在单纯的类中,并让这个类包装它所要装饰的对象(就是抽象类Decorator中的Component字段),因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。
例如,商店为了促销,在不同时期将随售出的衣服附送一些小礼物,例如在圣诞节期间,附送圣诞树小挂件,在新年期间,附送贺卡红包等。
–在这种情况下,这些小礼物本身并不属于售出衣服的一部分,它们是在不同时期动态添加到售出衣服上的额外属性
–而且由于一年中会展开很多次这样的促销,因此,子类化售出衣服,使其适用于各个时期,就会造成类的数量膨胀,系统难易维护
–此时,装饰器模式就成了首选
角色及职责:
特点:
适用范围:
优点:
缺点:
实现:
装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明一个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中的适配器干得也是同样的事情。假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但这个新的厂商所设计出来的接口不同于旧厂商提供的接口。你不像改变现有的代码,这时就可以使用适配器模式。你可以写一个类,将新厂商接口转换成你希望的接口。
实现:
两者比较:
角色及职责:
UML图:
补充:更高层次的适配器
从左图可以看出:Adapter将Adaptee接口适配为客户Client需要的接口Target,这样在整个系统中所有实现Adaptee接口的类都可以通过Adapter适配为Target对象,从而避免为每一个类都写一个适配器。后面会给大家带来一个JDK中使用此中适配的例子。我们不仅仅可以象上面一样对接口进行适配,也可以对抽象类进行适配!主要是根据系统的需求,确定此时的场景是否适合使用适配器模式!
适用范围:
效果:
我们需要在类适配器和对象适配器之间做权衡
类适配器:
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)具有相同接口的对象,我们想泡哪种茶都可以啦!
意图:为子系统提供了一个更高层次、更简单的接口,从而降低了子系统的复杂度和依赖。这使得子系统更易于使用和管理。对外提供一个统一的接口用来访问子系统中的一群接口。外观是一个能为子系统和客户提供简单接口的类。当正确的应用外观,客户不再直接和子系统中的类交互,而是与外观交互。外观承担与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统和客户的耦合度。外观对象隔离了客户和子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受到影响。
角色及职责:
适用范围:
效果:
意图:将抽象部分与实现部分分离,使得它们两个部分可以独立的变化。
等级结构:
角色及职责:
桥接模式可以从接口分离实现功能,使得设计更具有扩展性,这样,客户调用方法是根本不需要知道实现的细节。桥接模式的优点是减少了子类,如果程序中要在2个操作系统中实现查看6种图像格式,那么就会有2*6个类。使用桥接模式时候就会变成2+6个类(2个类定义了两个操作系统的抽象,6个类定义了查看了六种图像格式,因为没有把抽象和实现绑定在一起,因此只需要组合一下2种抽象和6种实现就可以达到目的)了,它使代码变得更清洁了,生成的执行程序更小了。但是桥接模式的缺陷是抽象类与实现类的双向连接使得运行速度更慢了。
适用场景:
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) 角色:
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(" "); } }
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; } }
public abstract class SodaImp { public abstract void pourSodaImp(); }三种 具体实现化 (Concrete Implementor) 角色:
public class CherrySodaImp extends SodaImp { CherrySodaImp() {} public void pourSodaImp() { System.out.println("Yummy Cherry Soda!"); } }
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模式使得用户对单个对象和组合对象的使用具有一致性。组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
例如论坛里的回帖及每个回帖的回复,设计人员希望能够以统一的方式处理某个具体的帖子和某个帖子以及跟帖构成的树或子树,就可以使用组合模式。
透明模式
安全模式
分类:
效果及实现要点:
适用性:
我们还是考虑茶包。现在假设我们有一个大包可以装下很多小的单个茶叶包,同时它也可以装其他包含单个茶叶包的茶叶包,因为大家都是茶叶包嘛~
首先设计一个公共的接口类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.");
}
}
意图:用一个共享来避免大量拥有相同内容对象的开销。这种开销中最常见、直观的就是内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象。
对象的状态:
适用范围:
分类:
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)角色:本角色还需要自行存储所有享元对象的外蕴状态。
优点: 大幅度地降低内存中对象的数量。
缺点:
总结:
享元模式一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用.
意图:为其他对象提供一种代理以控制对这个对象的访问。
角色及职责:
适用范围:
实现:
在调用处理器的invoke()方法中采取处理,一方面将调用传递给真是对象,另一方面执行各种所需要的操作。