组合的魔力
——模式系列谈之Decorator模式
一、上帝的智慧
《圣经》上说上帝花了六天造好世间万物。我们就会感叹,世间万物,何止上万,上帝是如何一个一个的造出来的呢?他老人家要是一个一个的造来,不要说费心劳力,光过程的繁琐、单调、乏味都不是常人所能够忍受的,虽然他老人家是神,忍耐力要比凡人好得多,但也不一定能承受下来。
哈哈,不用瞎操心,上帝自有他老人家的智慧。他老人家大手一挥,造出108种元素出来(这可要容易得多),然后他老人家撒手不管了,让这些元素去自由组合。比如,他老人家造出C、H、O来,于是C可以自己组成碳,H自己组成氢气,O自己组成氧气,C和O组成二氧化碳,H和O组成水,等等。
上帝的大智慧,其实就是组合的魔力。在我们的软件编码过程中,也会遇到各种各样的类,这些类,虽然我们用到了继承,但有时候也是很不够用的。
请看一个例子:
二、从一个例子说起
我们经常喜欢去咖啡馆喝咖啡。咖啡的种类也是非常之多:有黑咖啡(原咖啡)、加冰、加糖、加奶、加巧克力、加冰加糖、加奶加糖、加冰加奶等等。
假如我们现在希望帮咖啡馆设计一个咖啡贩卖系统。我们直接的想法就是:第一、给黑咖啡设计一个类Coffee;第二、给加冰咖啡设计一个继承Coffee类的类IceCoffee;……
依次类推,会得出二十多个类,我们可以看到,这里的类已经相当庞大了,已经是相当冗余的一个系统了。但这还不是最可怕的,最可怕的是,如果咖啡馆又推出来新的种类,如蒸汽加压咖啡,那么这个系统又该产生多少个类来呢?
很显然,在这里,我们要借助上帝的智慧了。想法很简单:我们是不是也能设定一些基本的类,然后再由这些基本的类来生成各种各样的对象呢?
这种方法就是我们要讲到的Decorator模式。
三、Decorator模式
谈到Decorator模式,我们还是先从组合谈起。
假设有类A:
class A
{
public void Ado()
{
……
}
}
类B:
class B
{
public void Bdo()
{
……
}
}
那么类C为他们之间的组合:
class C
{
private A a;
private B b;
public C()
{
a = new A();
b = new B();
}
public void Cdo()
{
a.Ado();
b.Bdo();
……
}
}
这是我们最能理解的类的组合,其中A类和B类能单独存在,然后他们又共同组成C类。
现在我们要考虑这样一种组合的模式,如上面的咖啡的例子中:咖啡,也就是黑咖啡可以单独组成一个类;而加冰这个类却是咖啡加冰,而不是单独的冰这个类;同样,加糖这个类也是咖啡加糖,而不是单独的糖这个类;……
这点理解是十分重要的,Decorate是装饰的意思,而Decorator则是装饰者的意思。很明显:Decorator模式中只有一个类是主体类,其他的都是Decorator类,要依附于主体类。
如,上面的咖啡例子中,咖啡是一个主体类,而其他的如冰、糖等都是Decorator类,都要依附于主体类,成为咖啡加冰类、咖啡加糖类……不能单独存在。
上例中,如果我们的类A为主体类,那么B类不能单独存在,C类却需要修改如下:
class AB
{
private A a;
public AB()
{
a = new A();
}
public ABdo()
{
a.Ado();
//do what B will do
……
}
}
这种组合方式构成了我们Decorator模式的基础。
我们再来看看类AB,可以看到类AB和类A有依赖关系。我们知道,类的依赖不要依赖具体类,而要依赖于抽象类。所以我们需要给出类A和类AB的抽象类,即他们的接口:
interface Ince
{
public void do();
}
那么我们的类A需要改写为:
class A implements Ince
{
public void do()
{
……
}
}
类AB需要改写为:
class AB implements Ince
{
private Ince ince;
public AB()
{
ince = new A();
}
public void do
{
ince.do();
//do what B will do
……
}
}
这样,依赖就满足了面向对象基本原则的依赖颠倒原则。但这样还不够,我们可以看到类AB既依赖接口类Ince,又依赖具体的实现类A。我们能不能把这个依赖也去掉呢?
我们说,当然可以。
Class AB implements Ince
{
private Ince ince;
public AB(Ince ince)
{
this.ince = ince;
}
public void do()
{
ince.do();
//do what B will do
……
}
}
这样,具体的类由客户端注入,使得类AB不但可以接受类A的对象,同样也可以接受所有实现了Ince接口的类。这就是Decorator模式的一个典型做法。
Decorator模式是一个结构性模式。他主要有以下的优点:
通过类之间的组合,来解决由于实际情况的复杂所引起的类的个数过于庞大的问题。Decorator模式的办法是定义一些类,然后由这些类来组合成各种各样的对象。
注意到我上面说的是Decorator模式通过一定一些类,然后由这些类来自由组合成其他的对象。注意,我说的是组合成其他的对象,而不是类。这又是Decorator模式跟类的继承不同之处,也就是它的第二个优点:Decorator模式组合得到的其他对象是动态的、或者说运行时的,这使得Decorator模式有了我们梦寐以求的灵活性和可扩展性。
下面,我们通过对上面的咖啡的例子的解决来详细看看Decorator模式的解决问题的思路:
四、问题的解决
我们的咖啡售卖系统要做两个事:第一,对售出的商品进行描述;第二,对售出的商品给出应收款项。
首先,我们做一个接口:
package decorator;
public interface Drinking {
public void describe();
public double pay();
}
我们可以看到,这个Drinking的接口里声明了两个方法,分别用来对Drinking进行描述和计算应收款项。
我们来看咖啡类的实现:
package decorator;
public class Coffee implements Drinking {
public void describe()
{
System.out.print("Coffee!");
}
public double pay()
{
return 50;
}
}
很明显,Coffee类是我们的主体类。
然后我们来看加冰咖啡类:
package decorator;
public class IceCoffee implements Drinking {
private Drinking drinking;
public IceCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("ice adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+8.5;
}
}
我们可以看到,类IceCoffee就是一个Decorator类。对于这个类,我们首先要往它的构造器注入一个实现了Drinking接口的类的对象;然后,它的decribe()方法的实现是先实现自己的行为,然后又执行组合类的行为;方法pay()也是这样。
我们来看其他的类,都跟IceCoffee类的实现一样:
package decorator;
public class SugarCoffee implements Drinking {
private Drinking drinking;
public SugarCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("sugar adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+11.5;
}
}
package decorator;
public class CreamCoffee implements Drinking {
private Drinking drinking;
public CreamCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("cream adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+20.5;
}
}
package decorator;
public class ChocolateCoffee implements Drinking {
private Drinking drinking;
public ChocolateCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("chocolate adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+33.5;
}
}
到此为止,我们实现了用Decrator模式来解决咖啡贩卖的问题,下面是我们的测试类:
package decorator;
public class Test {
public static void main(String[] args) {
Drinking coffee = new Coffee();
//coffee added sugar
Drinking sold1 = new SugarCoffee(coffee);
sold1.describe();
System.out.println();
System.out.println(sold1.pay());
Drinking sold2 = new ChocolateCoffee(new SugarCoffee(new Coffee()));
sold2.describe();
System.out.println();
System.out.println(sold2.pay());
Drinking sold3 = new CreamCoffee(new Coffee());
sold3.describe();
System.out.println();
System.out.println(sold3.pay());
Drinking sold4 = new CreamCoffee(new IceCoffee(new SugarCoffee(new ChocolateCoffee(new Coffee()))));
sold4.describe();
System.out.println();
System.out.println(sold4.pay());
}
}
测试结果如下:
sugar adding,Coffee!
61.5
chocolate adding,sugar adding,Coffee!
95.0
cream adding,Coffee!
70.5
cream adding,ice adding,sugar adding,chocolate adding,Coffee!
124.0
以上就是咖啡馆问题的一个完整的解决过程,如果过了一段时间,咖啡馆又增加了一个新的咖啡品种:蒸汽加压咖啡;没关系,只要增加这个新类,就可以在客户端自由的使用这个类了,对已经存在的类不会做任何的改动:
package decorator;
public class Espresso implements Drinking {
private Drinking drinking;
public Espresso(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("espresso adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+18.5;
}
}
现在,我们已经Decorator模式的开发过程以及它的优势全部展现给大家。最后,我们将以一个实际的例子来结束,并且将扩展该模式,使之在扩展功能的时候,连客户端都不需要做任何的修改。
五、实际的应用
如上图,是一个Web页面的一部分。最上面是五个复选框,其中第一个是必选的,其余四个是可选的。如果选中其中的一个或几个选择框,那么他们对应的编辑栏就会在网页中出现;如果没有选中,则不在网页中出现。
每一个编辑栏都有一些域,我们现在简单为文本域。我们可以在struts的配置文件里定义一个动态Form,将这些所有的域定义出来。然后我们在后台也会有一个POJO的类,用来在Action和Business层和DAO层传递数据。
我们来看POJO类
public class product {
//must be
private String mustA;
private String mustB;
//append one
private String oneA;
private String oneB;
//append two
private String twoA;
private String twoB;
private String twoC;
//append three
private String threeA;
private String threeB;
//append four
private String fourA;
private String fourB;
private String fourC;
private String fourD;
public String getFourA() {
return fourA;
}
public void setFourA(String fourA) {
this.fourA = fourA;
}
public String getFourB() {
return fourB;
}
public void setFourB(String fourB) {
this.fourB = fourB;
}
public String getFourC() {
return fourC;
}
public void setFourC(String fourC) {
this.fourC = fourC;
}
public String getFourD() {
return fourD;
}
public void setFourD(String fourD) {
this.fourD = fourD;
}
public String getMustA() {
return mustA;
}
public void setMustA(String mustA) {
this.mustA = mustA;
}
public String getMustB() {
return mustB;
}
public void setMustB(String mustB) {
this.mustB = mustB;
}
public String getOneA() {
return oneA;
}
public void setOneA(String oneA) {
this.oneA = oneA;
}
public String getOneB() {
return oneB;
}
public void setOneB(String oneB) {
this.oneB = oneB;
}
public String getThreeA() {
return threeA;
}
public void setThreeA(String threeA) {
this.threeA = threeA;
}
public String getThreeB() {
return threeB;
}
public void setThreeB(String threeB) {
this.threeB = threeB;
}
public String getTwoA() {
return twoA;
}
public void setTwoA(String twoA) {
this.twoA = twoA;
}
public String getTwoB() {
return twoB;
}
public void setTwoB(String twoB) {
this.twoB = twoB;
}
public String getTwoC() {
return twoC;
}
public void setTwoC(String twoC) {
this.twoC = twoC;
}
}
在这个类里,每个栏的域都有标出来。接下来我们要在Action里将页面取到的域的值赋给Product类对象,或者将Product类对象的值赋给页面,如下:
product.setMustA((String)form.get(“mustA”));
对于这样的取值赋值,我们知道,如果域mustA在页面上不存在的话,系统就会出错。而我们的这些值又恰恰是不能确定它们是否存在于页面上的。需要看复选框select的返回值。那么这样的赋值取值我们该怎么办呢?
解决的办法就是使用Decorator模式。
我们首先做一个Value接口:
public interface Value {
public void getFromWeb(Product product,DynaActionForm form);
public void setToWeb(Product product,DynaActionForm form);
}
然后必输栏为我们的主体类:
import org.apache.struts.action.DynaActionForm;
public class MustColumn implements Value {
public void getFromWeb(Product product,DynaActionForm form)
{
product.setMustA((String)form.get("mustA"));
product.setMustB((String)form.get("mustB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
form.set("mustA",product.getMustA());
form.set("mustB",product.getMustB());
}
}
来看看我们四个可选的栏位:
import org.apache.struts.action.DynaActionForm;
public class AppendOne implements Value {
private Value value;
public AppendOne(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setOneA((String)form.get("oneA"));
product.setOneB((String)form.get("oneB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("oneA",product.getOneA());
form.set("oneB",product.getOneB());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendTwo implements Value {
private Value value;
public AppendTwo(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setTwoA((String)form.get("twoA"));
product.setTwoB((String)form.get("twoB"));
product.setTwoC((String)form.get("twoC"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("twoA",product.getTwoA());
form.set("twoB",product.getTwoB());
form.set("twoC",product.getTwoC());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendThree implements Value {
private Value value;
public AppendThree(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setThreeA((String)form.get("threeA"));
product.setThreeB((String)form.get("threeB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("threeA",product.getThreeA());
form.set("threeB",product.getThreeB());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendFour implements Value {
private Value value;
public AppendFour(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setFourA((String)form.get("fourA"));
product.setFourB((String)form.get("fourB"));
product.setFourC((String)form.get("fourC"));
product.setFourD((String)form.get("fourD"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("fourA",product.getFourA());
form.set("fourB",product.getFourB());
form.set("fourC",product.getFourC());
form.set("fourD",product.getFourD());
}
}
现在来看它们的客户端:
String[] selects = request.getParameterValues("select");
Value value = new MustColumn();
For(int I=0;I<selects.length;I++)
{
if(select[i].equals(“1”))
{
value = new AppendOne(value);
}
else if(select[i].equals(“2”))
{
value = new AppendTwo(value);
}
else if(select[i].equals(“3”))
{
value = new AppendThree(value);
}
else if(select[i].equals(“4”))
{
value = new AppendFour(value);
}
}
value.getFormWeb(product,form);
我们可以看到,使用了Decorator模式后,这种回来赋值的确容易了很多。
但我们同时可以看到还可以再简化,那段if…else if…应该是产生对象的过程,可以放在Factory类中去,这就是工厂模式:
public class Factory
{
public static Value getInstance(String lable,Value value)
{
If(label.equals(“0”))
{
value = new MustColumn();
}
else if(lable.equals(“1”))
{
value = new AppendOne(value);
}
else if(lable.equals(“2”))
{
value = new AppendTwo(value);
}
else if(lable.equals(“3”))
{
value = new AppendThree(value);
}
else if(lable.equals(“4”))
{
value = new AppendFour(value);
}
else value = null;
return value;
}
}
有了这个工厂,则客户端可以改为:
String[] selects = request.getParameterValues("select");
Value value = Factory.getInstance(“0”,value);
For(int I=0;I<selects.length;I++)
{
value = Factory.getInstance(selects[i],value);
}
value.getFormWeb(product,form);
这样,客户端对装饰者类的依赖就没有了。
——模式系列谈之Decorator模式
一、上帝的智慧
《圣经》上说上帝花了六天造好世间万物。我们就会感叹,世间万物,何止上万,上帝是如何一个一个的造出来的呢?他老人家要是一个一个的造来,不要说费心劳力,光过程的繁琐、单调、乏味都不是常人所能够忍受的,虽然他老人家是神,忍耐力要比凡人好得多,但也不一定能承受下来。
哈哈,不用瞎操心,上帝自有他老人家的智慧。他老人家大手一挥,造出108种元素出来(这可要容易得多),然后他老人家撒手不管了,让这些元素去自由组合。比如,他老人家造出C、H、O来,于是C可以自己组成碳,H自己组成氢气,O自己组成氧气,C和O组成二氧化碳,H和O组成水,等等。
上帝的大智慧,其实就是组合的魔力。在我们的软件编码过程中,也会遇到各种各样的类,这些类,虽然我们用到了继承,但有时候也是很不够用的。
请看一个例子:
二、从一个例子说起
我们经常喜欢去咖啡馆喝咖啡。咖啡的种类也是非常之多:有黑咖啡(原咖啡)、加冰、加糖、加奶、加巧克力、加冰加糖、加奶加糖、加冰加奶等等。
假如我们现在希望帮咖啡馆设计一个咖啡贩卖系统。我们直接的想法就是:第一、给黑咖啡设计一个类Coffee;第二、给加冰咖啡设计一个继承Coffee类的类IceCoffee;……
依次类推,会得出二十多个类,我们可以看到,这里的类已经相当庞大了,已经是相当冗余的一个系统了。但这还不是最可怕的,最可怕的是,如果咖啡馆又推出来新的种类,如蒸汽加压咖啡,那么这个系统又该产生多少个类来呢?
很显然,在这里,我们要借助上帝的智慧了。想法很简单:我们是不是也能设定一些基本的类,然后再由这些基本的类来生成各种各样的对象呢?
这种方法就是我们要讲到的Decorator模式。
三、Decorator模式
谈到Decorator模式,我们还是先从组合谈起。
假设有类A:
class A
{
public void Ado()
{
……
}
}
类B:
class B
{
public void Bdo()
{
……
}
}
那么类C为他们之间的组合:
class C
{
private A a;
private B b;
public C()
{
a = new A();
b = new B();
}
public void Cdo()
{
a.Ado();
b.Bdo();
……
}
}
这是我们最能理解的类的组合,其中A类和B类能单独存在,然后他们又共同组成C类。
现在我们要考虑这样一种组合的模式,如上面的咖啡的例子中:咖啡,也就是黑咖啡可以单独组成一个类;而加冰这个类却是咖啡加冰,而不是单独的冰这个类;同样,加糖这个类也是咖啡加糖,而不是单独的糖这个类;……
这点理解是十分重要的,Decorate是装饰的意思,而Decorator则是装饰者的意思。很明显:Decorator模式中只有一个类是主体类,其他的都是Decorator类,要依附于主体类。
如,上面的咖啡例子中,咖啡是一个主体类,而其他的如冰、糖等都是Decorator类,都要依附于主体类,成为咖啡加冰类、咖啡加糖类……不能单独存在。
上例中,如果我们的类A为主体类,那么B类不能单独存在,C类却需要修改如下:
class AB
{
private A a;
public AB()
{
a = new A();
}
public ABdo()
{
a.Ado();
//do what B will do
……
}
}
这种组合方式构成了我们Decorator模式的基础。
我们再来看看类AB,可以看到类AB和类A有依赖关系。我们知道,类的依赖不要依赖具体类,而要依赖于抽象类。所以我们需要给出类A和类AB的抽象类,即他们的接口:
interface Ince
{
public void do();
}
那么我们的类A需要改写为:
class A implements Ince
{
public void do()
{
……
}
}
类AB需要改写为:
class AB implements Ince
{
private Ince ince;
public AB()
{
ince = new A();
}
public void do
{
ince.do();
//do what B will do
……
}
}
这样,依赖就满足了面向对象基本原则的依赖颠倒原则。但这样还不够,我们可以看到类AB既依赖接口类Ince,又依赖具体的实现类A。我们能不能把这个依赖也去掉呢?
我们说,当然可以。
Class AB implements Ince
{
private Ince ince;
public AB(Ince ince)
{
this.ince = ince;
}
public void do()
{
ince.do();
//do what B will do
……
}
}
这样,具体的类由客户端注入,使得类AB不但可以接受类A的对象,同样也可以接受所有实现了Ince接口的类。这就是Decorator模式的一个典型做法。
Decorator模式是一个结构性模式。他主要有以下的优点:
通过类之间的组合,来解决由于实际情况的复杂所引起的类的个数过于庞大的问题。Decorator模式的办法是定义一些类,然后由这些类来组合成各种各样的对象。
注意到我上面说的是Decorator模式通过一定一些类,然后由这些类来自由组合成其他的对象。注意,我说的是组合成其他的对象,而不是类。这又是Decorator模式跟类的继承不同之处,也就是它的第二个优点:Decorator模式组合得到的其他对象是动态的、或者说运行时的,这使得Decorator模式有了我们梦寐以求的灵活性和可扩展性。
下面,我们通过对上面的咖啡的例子的解决来详细看看Decorator模式的解决问题的思路:
四、问题的解决
我们的咖啡售卖系统要做两个事:第一,对售出的商品进行描述;第二,对售出的商品给出应收款项。
首先,我们做一个接口:
package decorator;
public interface Drinking {
public void describe();
public double pay();
}
我们可以看到,这个Drinking的接口里声明了两个方法,分别用来对Drinking进行描述和计算应收款项。
我们来看咖啡类的实现:
package decorator;
public class Coffee implements Drinking {
public void describe()
{
System.out.print("Coffee!");
}
public double pay()
{
return 50;
}
}
很明显,Coffee类是我们的主体类。
然后我们来看加冰咖啡类:
package decorator;
public class IceCoffee implements Drinking {
private Drinking drinking;
public IceCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("ice adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+8.5;
}
}
我们可以看到,类IceCoffee就是一个Decorator类。对于这个类,我们首先要往它的构造器注入一个实现了Drinking接口的类的对象;然后,它的decribe()方法的实现是先实现自己的行为,然后又执行组合类的行为;方法pay()也是这样。
我们来看其他的类,都跟IceCoffee类的实现一样:
package decorator;
public class SugarCoffee implements Drinking {
private Drinking drinking;
public SugarCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("sugar adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+11.5;
}
}
package decorator;
public class CreamCoffee implements Drinking {
private Drinking drinking;
public CreamCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("cream adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+20.5;
}
}
package decorator;
public class ChocolateCoffee implements Drinking {
private Drinking drinking;
public ChocolateCoffee(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("chocolate adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+33.5;
}
}
到此为止,我们实现了用Decrator模式来解决咖啡贩卖的问题,下面是我们的测试类:
package decorator;
public class Test {
public static void main(String[] args) {
Drinking coffee = new Coffee();
//coffee added sugar
Drinking sold1 = new SugarCoffee(coffee);
sold1.describe();
System.out.println();
System.out.println(sold1.pay());
Drinking sold2 = new ChocolateCoffee(new SugarCoffee(new Coffee()));
sold2.describe();
System.out.println();
System.out.println(sold2.pay());
Drinking sold3 = new CreamCoffee(new Coffee());
sold3.describe();
System.out.println();
System.out.println(sold3.pay());
Drinking sold4 = new CreamCoffee(new IceCoffee(new SugarCoffee(new ChocolateCoffee(new Coffee()))));
sold4.describe();
System.out.println();
System.out.println(sold4.pay());
}
}
测试结果如下:
sugar adding,Coffee!
61.5
chocolate adding,sugar adding,Coffee!
95.0
cream adding,Coffee!
70.5
cream adding,ice adding,sugar adding,chocolate adding,Coffee!
124.0
以上就是咖啡馆问题的一个完整的解决过程,如果过了一段时间,咖啡馆又增加了一个新的咖啡品种:蒸汽加压咖啡;没关系,只要增加这个新类,就可以在客户端自由的使用这个类了,对已经存在的类不会做任何的改动:
package decorator;
public class Espresso implements Drinking {
private Drinking drinking;
public Espresso(Drinking drinking)
{
this.drinking = drinking;
}
public void describe()
{
System.out.print("espresso adding,");
this.drinking.describe();
}
public double pay()
{
return this.drinking.pay()+18.5;
}
}
现在,我们已经Decorator模式的开发过程以及它的优势全部展现给大家。最后,我们将以一个实际的例子来结束,并且将扩展该模式,使之在扩展功能的时候,连客户端都不需要做任何的修改。
五、实际的应用
如上图,是一个Web页面的一部分。最上面是五个复选框,其中第一个是必选的,其余四个是可选的。如果选中其中的一个或几个选择框,那么他们对应的编辑栏就会在网页中出现;如果没有选中,则不在网页中出现。
每一个编辑栏都有一些域,我们现在简单为文本域。我们可以在struts的配置文件里定义一个动态Form,将这些所有的域定义出来。然后我们在后台也会有一个POJO的类,用来在Action和Business层和DAO层传递数据。
我们来看POJO类
public class product {
//must be
private String mustA;
private String mustB;
//append one
private String oneA;
private String oneB;
//append two
private String twoA;
private String twoB;
private String twoC;
//append three
private String threeA;
private String threeB;
//append four
private String fourA;
private String fourB;
private String fourC;
private String fourD;
public String getFourA() {
return fourA;
}
public void setFourA(String fourA) {
this.fourA = fourA;
}
public String getFourB() {
return fourB;
}
public void setFourB(String fourB) {
this.fourB = fourB;
}
public String getFourC() {
return fourC;
}
public void setFourC(String fourC) {
this.fourC = fourC;
}
public String getFourD() {
return fourD;
}
public void setFourD(String fourD) {
this.fourD = fourD;
}
public String getMustA() {
return mustA;
}
public void setMustA(String mustA) {
this.mustA = mustA;
}
public String getMustB() {
return mustB;
}
public void setMustB(String mustB) {
this.mustB = mustB;
}
public String getOneA() {
return oneA;
}
public void setOneA(String oneA) {
this.oneA = oneA;
}
public String getOneB() {
return oneB;
}
public void setOneB(String oneB) {
this.oneB = oneB;
}
public String getThreeA() {
return threeA;
}
public void setThreeA(String threeA) {
this.threeA = threeA;
}
public String getThreeB() {
return threeB;
}
public void setThreeB(String threeB) {
this.threeB = threeB;
}
public String getTwoA() {
return twoA;
}
public void setTwoA(String twoA) {
this.twoA = twoA;
}
public String getTwoB() {
return twoB;
}
public void setTwoB(String twoB) {
this.twoB = twoB;
}
public String getTwoC() {
return twoC;
}
public void setTwoC(String twoC) {
this.twoC = twoC;
}
}
在这个类里,每个栏的域都有标出来。接下来我们要在Action里将页面取到的域的值赋给Product类对象,或者将Product类对象的值赋给页面,如下:
product.setMustA((String)form.get(“mustA”));
对于这样的取值赋值,我们知道,如果域mustA在页面上不存在的话,系统就会出错。而我们的这些值又恰恰是不能确定它们是否存在于页面上的。需要看复选框select的返回值。那么这样的赋值取值我们该怎么办呢?
解决的办法就是使用Decorator模式。
我们首先做一个Value接口:
public interface Value {
public void getFromWeb(Product product,DynaActionForm form);
public void setToWeb(Product product,DynaActionForm form);
}
然后必输栏为我们的主体类:
import org.apache.struts.action.DynaActionForm;
public class MustColumn implements Value {
public void getFromWeb(Product product,DynaActionForm form)
{
product.setMustA((String)form.get("mustA"));
product.setMustB((String)form.get("mustB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
form.set("mustA",product.getMustA());
form.set("mustB",product.getMustB());
}
}
来看看我们四个可选的栏位:
import org.apache.struts.action.DynaActionForm;
public class AppendOne implements Value {
private Value value;
public AppendOne(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setOneA((String)form.get("oneA"));
product.setOneB((String)form.get("oneB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("oneA",product.getOneA());
form.set("oneB",product.getOneB());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendTwo implements Value {
private Value value;
public AppendTwo(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setTwoA((String)form.get("twoA"));
product.setTwoB((String)form.get("twoB"));
product.setTwoC((String)form.get("twoC"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("twoA",product.getTwoA());
form.set("twoB",product.getTwoB());
form.set("twoC",product.getTwoC());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendThree implements Value {
private Value value;
public AppendThree(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setThreeA((String)form.get("threeA"));
product.setThreeB((String)form.get("threeB"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("threeA",product.getThreeA());
form.set("threeB",product.getThreeB());
}
}
import org.apache.struts.action.DynaActionForm;
public class AppendFour implements Value {
private Value value;
public AppendFour(Value value)
{
this.value = value;
}
public void getFromWeb(Product product,DynaActionForm form)
{
this.value.getFromWeb(product,form);
product.setFourA((String)form.get("fourA"));
product.setFourB((String)form.get("fourB"));
product.setFourC((String)form.get("fourC"));
product.setFourD((String)form.get("fourD"));
}
public void setToWeb(Product product,DynaActionForm form)
{
this.value.setToWeb(product,form);
form.set("fourA",product.getFourA());
form.set("fourB",product.getFourB());
form.set("fourC",product.getFourC());
form.set("fourD",product.getFourD());
}
}
现在来看它们的客户端:
String[] selects = request.getParameterValues("select");
Value value = new MustColumn();
For(int I=0;I<selects.length;I++)
{
if(select[i].equals(“1”))
{
value = new AppendOne(value);
}
else if(select[i].equals(“2”))
{
value = new AppendTwo(value);
}
else if(select[i].equals(“3”))
{
value = new AppendThree(value);
}
else if(select[i].equals(“4”))
{
value = new AppendFour(value);
}
}
value.getFormWeb(product,form);
我们可以看到,使用了Decorator模式后,这种回来赋值的确容易了很多。
但我们同时可以看到还可以再简化,那段if…else if…应该是产生对象的过程,可以放在Factory类中去,这就是工厂模式:
public class Factory
{
public static Value getInstance(String lable,Value value)
{
If(label.equals(“0”))
{
value = new MustColumn();
}
else if(lable.equals(“1”))
{
value = new AppendOne(value);
}
else if(lable.equals(“2”))
{
value = new AppendTwo(value);
}
else if(lable.equals(“3”))
{
value = new AppendThree(value);
}
else if(lable.equals(“4”))
{
value = new AppendFour(value);
}
else value = null;
return value;
}
}
有了这个工厂,则客户端可以改为:
String[] selects = request.getParameterValues("select");
Value value = Factory.getInstance(“0”,value);
For(int I=0;I<selects.length;I++)
{
value = Factory.getInstance(selects[i],value);
}
value.getFormWeb(product,form);
这样,客户端对装饰者类的依赖就没有了。