1.1 穿什么有这么重要?
约会穿什么?
"那要看你想给人家什么印象?是比较年轻,还是比较干练;是比较颓废,还是要比较阳光;也有可能你想给人家一种极其难忘的印象,那穿法又大不一样了!"
"你这话怎么讲?"
"年轻,不妨走点Hip-Hop路线,大T恤、垮裤、球鞋,典型的年轻人装扮。"
"啊,这不是我喜欢的风格,我从来也没这样穿过。"
"那就换一种,所谓干练,就是要有外企高级白领的样,黑西装、黑领带、黑墨镜、黑皮鞋……"
"你这叫白领?我看是社会人。不行不行。"
"哈,来颓废的吧,颓废其实也是一种个性,可以吸引一些喜欢叛逆的女生。一般来说,其标志是:头发可养鸟、胡子能生虫、衬衣没纽扣、香烟加狐臭。"
"这根本就是'肮脏'的代表吗,开什么玩笑。你刚才提到给人家难忘印象,是什么样的穿法?"
"哈,这当然是绝妙的招了,如果你照我说的去做,娇娇想忘都难。"
"快说快说,是什么?"
"大红色披风下一身蓝色紧身衣,胸前一个大大的'S',表明你其实穿的是'小号',还有最重要的是,一定要'内裤外穿'……"
"喂,你拿我寻开心呀,那是'超人'的打扮呀,'S'代表的也不是'Small',是'Super'的意思。"哈,你终于明白了!我其实想表达的意思就是,你完全可以随便一些,平时穿什么,明天还是穿什么,男生嘛,只要干净一些就可以了,关键不在于你穿什么,而在于你人怎么样。对自己都这么没信心,如何追求女孩子?"
1.2 扮靓第一版
package code.chapter6.decorator1;public class Person {private String name;public Person(String name) {this.name = name;}public void wearTShirts() {System.out.print(" 大T恤");}public void wearBigTrouser() {System.out.print(" 垮裤");}public void wearSneakers() {System.out.print(" 球鞋");}public void wearSuit() {System.out.print(" 西装");}public void wearTie() {System.out.print(" 领带");}public void wearLeatherShoes() {System.out.print(" 皮鞋");}public void show() {System.out.println("装扮的"+name);}
}
package code.chapter6.decorator1;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); Person xc = new Person("小菜");System.out.println(" 第一种装扮:");xc.wearTShirts();xc.wearBigTrouser();xc.wearSneakers();xc.show();System.out.println(" 第二种装扮:");xc.wearSuit();xc.wearTie();xc.wearLeatherShoes();xc.show();System.out.println();System.out.println("**********************************************");}
}
现在需要增加超人的装扮。如果改Person类,就违背了开放-封闭原则了,应该把这些服饰都写成了子类就好了。
1.3 扮靓第二版
package code.chapter6.decorator2;public class Person {private String name;public Person(String name) {this.name = name;}public void show() {System.out.println("装扮的"+name);}
}
package code.chapter6.decorator2;public abstract class Finery {public abstract void show();}
package code.chapter6.decorator2;public class TShirts extends Finery {public void show(){System.out.print(" 大T恤");}}
package code.chapter6.decorator2;public class BigTrouser extends Finery {public void show(){System.out.print(" 垮裤");}}
package code.chapter6.decorator2;public class LeatherShoes extends Finery {public void show(){System.out.print(" 皮鞋");}}
package code.chapter6.decorator2;public class Sneakers extends Finery {public void show(){System.out.print(" 球鞋");}}
package code.chapter6.decorator2;public class Suit extends Finery {public void show(){System.out.print(" 西装");}}
package code.chapter6.decorator2;public class Tie extends Finery {public void show(){System.out.print(" 领带");}}
package code.chapter6.decorator2;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); Person xc = new Person("小菜");System.out.println(" 第一种装扮:");Finery dtx = new TShirts();Finery kk = new BigTrouser();Finery pqx = new Sneakers();dtx.show();kk.show();pqx.show();xc.show();System.out.println(" 第二种装扮:");Finery xz = new Suit();Finery ld = new Tie();Finery px = new LeatherShoes();xz.show();ld.show();px.show();xc.show();System.out.println();System.out.println("**********************************************");}
}
这样写就好比:你光着身子,当着大家的面,先穿T恤,再穿裤子,再穿鞋,仿佛在跳穿衣舞。难道你穿衣服都是在众目睽睽下穿的吗?"
"你的意思是,应该在内部组装完毕,然后再显示出来?这好像是建造者模式呀。"
"不是的,建造者模式要求建造的过程必须是稳定的,而现在我们这个例子,建造过程是不稳定的,比如完全可以内穿西装,外套T恤,再加披风,打上领带,皮鞋外再穿上破球鞋;当然也完全可以只穿条裤衩就算完成。换句话就是说,通过服饰组合出一个有个性的人完全可以有无数种方案,并非固定的。"
"啊,你说得对,其实先后顺序也是有讲究的,如你所说,先穿内裤后穿外裤,这叫凡人,内裤穿到外裤外面,那就是超人了。"
"哈,很会举一反三嘛,那你说该如何办呢?"
"我们需要把所需的功能按正确的顺序串联起来进行控制,这好像很难办哦。"
"不懂就学,其实也没什么稀罕的,这可以用一个非常有意思的设计模式来实现。"
1.4 装饰模式
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
"啊,装饰这词真好,无论衣服、鞋子、领带、披风其实都可以理解为对人的装饰。"
"Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无须知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能[DPE]。"
"来看看基本的代码实现。"
package com.lhx.design.pattern.test;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); ConcreteComponent c = new ConcreteComponent();ConcreteDecoratorA d1 = new ConcreteDecoratorA();ConcreteDecoratorB d2 = new ConcreteDecoratorB();d1.SetComponent(c); //首先用d1来包装cd2.SetComponent(d1);//再用有来包装d1d2.Operation(); //最终执行d2的Operation()System.out.println();System.out.println("**********************************************");}
}//Component类
abstract class Component {public abstract void Operation();
}//ConcreteComponent类
class ConcreteComponent extends Component {public void Operation() {System.out.println("具体对象的实际操作");}}//Decorator类
abstract class Decorator extends Component {protected Component component;//装饰一个Component对象public void SetComponent(Component component) {this.component = component;}//重写Operation(),实际调用component的Operation方法public void Operation() {if (component != null) {component.Operation();}}
}//ConcreteDecoratorA类
class ConcreteDecoratorA extends Decorator {private String addedState;//本类独有子段,以区别于ConcreteDecoratorB类public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.addedState = "具体装饰对象A的独有操作";//再执行本类独有功能System.out.println(this.addedState);}
}//ConcreteDecoratorB类
class ConcreteDecoratorB extends Decorator {public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.AddedBehavior();//再执行本类独有功能}//本类独有方法,以区别于ConcreteDecoratorA类private void AddedBehavior() { System.out.println("具体装饰对象B的独有操作");}
}
package com.lhx.design.pattern.test;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); ConcreteComponent c = new ConcreteComponent();ConcreteDecoratorA d1 = new ConcreteDecoratorA();ConcreteDecoratorB d2 = new ConcreteDecoratorB();d1.SetComponent(c); //首先用d1来包装cd2.SetComponent(d1);//再用有来包装d1d2.Operation(); //最终执行d2的Operation()System.out.println();System.out.println("**********************************************");}
}//Component类
abstract class Component {public abstract void Operation();
}//ConcreteComponent类
class ConcreteComponent extends Component {public void Operation() {System.out.println("具体对象的实际操作");}}//Decorator类
abstract class Decorator extends Component {protected Component component;//装饰一个Component对象public void SetComponent(Component component) {this.component = component;}//重写Operation(),实际调用component的Operation方法public void Operation() {if (component != null) {component.Operation();}}
}//ConcreteDecoratorA类
class ConcreteDecoratorA extends Decorator {private String addedState;//本类独有子段,以区别于ConcreteDecoratorB类public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.addedState = "具体装饰对象A的独有操作";//再执行本类独有功能System.out.println(this.addedState);}
}//ConcreteDecoratorB类
class ConcreteDecoratorB extends Decorator {public void Operation() {super.Operation();//首先运行了原有Component的Operation()this.AddedBehavior();//再执行本类独有功能}//本类独有方法,以区别于ConcreteDecoratorA类private void AddedBehavior() { System.out.println("具体装饰对象B的独有操作");}
}
我明白了,原来装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中[DPE]。用刚才的例子来说就是,我们完全可以先穿外裤,再穿内裤,而不一定要先内后外。
"还有个问题,刚才写的那个例子中'人'类是Component还是ConcreteComponent呢?"
"哈,学习模式要善于变通,如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。"
"啊,原来如此。在这里我们就没有必要有Component类了,直接让服饰类Decorator继承人类ConcreteComponent即可。"
1.5 扮靓第三版
package code.chapter6.decorator3;//人物形象接口
public interface ICharacter {public void show();}
package code.chapter6.decorator3;//服饰类
public class Finery implements ICharacter {protected ICharacter component;public void decorate(ICharacter component) {this.component=component;}public void show() {if (this.component != null){this.component.show();}}}
具体服饰类(ConcreteDecorator)
package code.chapter6.decorator3;public class TShirts extends Finery {public void show(){System.out.print(" 大T恤");super.show();}}
package code.chapter6.decorator3;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); Person xc = new Person("小菜");System.out.println(" 第一种装扮:");Sneakers pqx = new Sneakers(); //生成球鞋实例pqx.decorate(xc); //球鞋装饰小菜BigTrouser kk = new BigTrouser(); //生成垮裤实例kk.decorate(pqx); //垮裤装饰“有球鞋装饰的小菜”TShirts dtx = new TShirts(); //生成T恤实例dtx.decorate(kk); //T恤装饰“有垮裤球鞋装饰的小菜”dtx.show(); //执行形象展示System.out.println(" 第二种装扮:");LeatherShoes px = new LeatherShoes();//生成皮鞋实例px.decorate(xc); //皮鞋装饰小菜Tie ld = new Tie(); //生成领带实例ld.decorate(px); //领带装饰“有皮鞋装饰的小菜”Suit xz = new Suit(); //生成西装实例xz.decorate(ld); //西装装饰“有领带皮鞋装饰的小菜”xz.show(); //执行形象展示System.out.println(" 第三种装扮:");Sneakers pqx2 = new Sneakers(); //生成球鞋实例pqx2.decorate(xc); //球鞋装饰小菜LeatherShoes px2 = new LeatherShoes();//生成皮鞋实例px2.decorate(pqx2); //皮鞋装饰“有球鞋装饰的小菜”BigTrouser kk2 = new BigTrouser(); //生成垮裤实例kk2.decorate(px2); //垮裤装饰“有皮鞋球鞋装饰的小菜”Tie ld2 = new Tie(); //生成领带实例ld2.decorate(kk2); //领带装饰“有垮裤皮鞋球鞋装饰的小菜”Strawhat cm2 = new Strawhat(); //生成草帽实例cm2.decorate(ld2); //草帽装饰“有领带垮裤皮鞋球鞋装饰的小菜”cm2.show(); //执行形象展示System.out.println();System.out.println("**********************************************");}
}
如果我换一种装饰方式,比如说,增加草帽装扮,再重新组合一下服饰?
那就增加一个草帽子类,再修改一下装饰方案就好了。
package code.chapter6.decorator3;public class Strawhat extends Finery {public void show(){System.out.print(" 草帽");super.show();}}
1.6 商场收银程序再升级
先打8折,再满300返回100,算法可以是下面这样。
增加一个先折扣再返利的算法子类
package com.lhx.design.pattern.decoratorMode;public class CashReturnRebate extends CashSuper {private double moneyRebate = 1d;private double moneyCondition = 0d; //返利条件private double moneyReturn = 0d; //返利值//先折扣,再返利。初始化时需要折扣参数,再输入返利条件和返利值。//比如“先8折,再满300返100”,就是moneyRebate=0.8,moneyCondition=300,moneyReturn=100public CashReturnRebate(double moneyRebate,double moneyCondition,double moneyReturn){this.moneyRebate = moneyRebate;this.moneyCondition = moneyCondition;this.moneyReturn = moneyReturn;}//先折扣,再返利public double acceptCash(double price,int num){double result = price * num * this.moneyRebate;if (moneyCondition>0 && result >= moneyCondition)result = result - Math.floor(result / moneyCondition) * moneyReturn; return result;}}
package com.lhx.design.pattern.decoratorMode;public class CashContext {private CashSuper cs; //声明一个CashSuper对象//通过构造方法,传入具体的收费策略public CashContext(int cashType){switch(cashType){case 1:cs = new CashNormal();break;case 2:this.cs = new CashRebate(0.8d);break;case 3:this.cs = new CashRebate(0.7d);break;case 4:this.cs = new CashReturn(300d,100d);break;case 5:this.cs = new CashReturnRebate(0.8d,300d,100d);break;}}public double getResult(double price,int num){//根据收费策略的不同,获得计算结果return this.cs.acceptCash(price,num);}
}
新类CashReturnRebate有原来的两个类:CashReturn和CashRebate有大量重复的代码,另外,如果我现在希望增加一个先满300返100,再打折扣的算法,你如何修改呢?再写一个新类吗?如果我们再增加购买送积分、购买抽奖、购买送小礼品等算法,并且有了各种各样的先后组合,你打算怎么处理呢?
1.7 简单工厂+策略+装饰模式实现
你这样是可以实现同样的功能,但与装饰模式比较起来,并不完美。仔细对比观察一下,还有什么东西没有?"
ConcreteComponent类不存在。那它应该是什么呢?
装饰模式有一个重要的优点,把类中的装饰功能从类中搬移去除,这样可以简化原有的类。我们现在的三个算法类,有没有最基础的呢?
CashSuper原来是抽象类,改成普通类,但实现ISale接口。
package com.lhx.design.pattern.decoratorMode.optimizeOne;public interface ISale {public double acceptCash(double price,int num);}
CashNormal,相当于ConcreteComponent,是最基本的功能实现,也就是单价×数量的原价算法。
package com.lhx.design.pattern.decoratorMode.optimizeOne;public class CashNormal implements ISale {//正常收费,原价返回public double acceptCash(double price,int num){return price * num; }
}
另外两个CashSuper的子类算法,都在计算后,再增加一个super.acceptCash(result, 1)返回。
package com.lhx.design.pattern.decoratorMode.optimizeOne;public class CashRebate extends CashSuper {private double moneyRebate = 1d;//打折收费。初始化时必需输入折扣率。八折就输入0.8public CashRebate(double moneyRebate){this.moneyRebate = moneyRebate;}//计算收费时需要在原价基础上乘以折扣率public double acceptCash(double price,int num){double result = price * num * this.moneyRebate;return super.acceptCash(result,1);}}
package com.lhx.design.pattern.decoratorMode.optimizeOne;public class CashReturn extends CashSuper {private double moneyCondition = 0d; //返利条件private double moneyReturn = 0d; //返利值//返利收费。初始化时需要输入返利条件和返利值。//比如“满300返100”,就是moneyCondition=300,moneyReturn=100public CashReturn(double moneyCondition,double moneyReturn){this.moneyCondition = moneyCondition;this.moneyReturn = moneyReturn;}//计算收费时,当达到返利条件,就原价减去返利值public double acceptCash(double price,int num){double result = price * num;if (moneyCondition>0 && result >= moneyCondition)result = result - Math.floor(result / moneyCondition) * moneyReturn; return super.acceptCash(result,1); }}
重点在CashContext类,因为涉及组合算法,所以用装饰模式的方式进行包装,这里需要注意包装的顺序,先打折后满多少返多少,与先满多少返多少,再打折会得到完全不同的结果。
package com.lhx.design.pattern.decoratorMode.optimizeOne;public class CashContext {private ISale cs; //声明一个ISale接口对象//通过构造方法,传入具体的收费策略public CashContext(int cashType){switch(cashType){case 1:this.cs = new CashNormal();break;case 2:this.cs = new CashRebate(0.8d);break;case 3:this.cs = new CashRebate(0.7d);break;case 4:this.cs = new CashReturn(300d,100d);break;case 5://先打8折,再满300返100CashNormal cn = new CashNormal();CashReturn cr1 = new CashReturn(300d,100d); CashRebate cr2 = new CashRebate(0.8d);cr1.decorate(cn); //用满300返100算法包装基本的原价算法cr2.decorate(cr1); //打8折算法装饰满300返100算法this.cs = cr2; //将包装好的算法组合引用传递给cs对象break;case 6://先满200返50,再打7折CashNormal cn2 = new CashNormal();CashRebate cr3 = new CashRebate(0.7d);CashReturn cr4 = new CashReturn(200d,50d); cr3.decorate(cn2); //用打7折算法包装基本的原价算法cr4.decorate(cr3); //满200返50算法装饰打7折算法this.cs = cr4; //将包装好的算法组合引用传递给cs对象break;}}public double getResult(double price,int num){//根据收费策略的不同,获得计算结果return this.cs.acceptCash(price,num);}
}
package com.lhx.design.pattern.decoratorMode.optimizeOne;import java.util.Scanner;/*** 简单工厂+策略+装饰模式实现*/public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); int discount = 0; //商品折扣模式double price = 0d; //商品单价int num = 0; //商品购买数量double totalPrices = 0d;//当前商品合计费用double total = 0d; //总计所有商品费用Scanner sc = new Scanner(System.in);do {System.out.println("商品折扣模式如下:"); System.out.println("1.正常收费"); System.out.println("2.打八折"); System.out.println("3.打七折"); System.out.println("4.满300送100"); System.out.println("5.先打8折,再满300送100"); System.out.println("6.先满200送50,再打7折"); System.out.println("请输入商品折扣模式:"); discount = Integer.parseInt(sc.nextLine());System.out.println("请输入商品单价:"); price = Double.parseDouble(sc.nextLine());System.out.println("请输入商品数量:"); num = Integer.parseInt(sc.nextLine());System.out.println(); if (price>0 && num>0){//根据用户输入,将对应的策略对象作为参数传入CashContext对象中CashContext cc = new CashContext(discount);//通过Context的getResult方法的调用,可以得到收取费用的结果//让具体算法与客户进行了隔离totalPrices = cc.getResult(price,num);total = total + totalPrices;System.out.println(); System.out.println("单价:"+ price + "元 数量:"+ num +" 合计:"+ totalPrices +"元"); System.out.println();System.out.println("总计:"+ total+"元"); System.out.println();}}while(price>0 && num>0);System.out.println();System.out.println("**********************************************");}
}
客户端算法不变:
结果显示:
单位1000元,数量为1的商品,原需支付1000元,如果选择先8折再满300送100算法的话,就是1000×0.8=800元,满足两个300元,返200元,最终结果是客户只需支付600元。
单位500元,数量为4的商品,原需支付2000元,如果选择先满200送50再7折算法的话,就是2000中有10个200,送10×50=500元,所以2000-500=1500元,再打7折,1500×0.7,最终结果是客户只需支付1050元。
现在无论如何组合算法,哪怕是先打折再返现,再打折再返现,我都只需要更改CashContext类就可以了。目前代码确实做到了开放封闭。设计模式真是好!
1.8 装饰模式总结
"我觉得装饰模式是为已有功能动态地添加更多功能的一种方式。但到底什么时候用它呢?"
"答得很好,问的问题更加好。你起初的设计中,当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,比如用西装或嘻哈服来装饰小菜,但这种做法的问题在于,它们在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,就像你起初的那个'人'类,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了[DP]。所以就出现了上面那个例子的情况,我可以通过装饰,让你全副武装到牙齿,也可以让你只挂一丝到内裤。"
"就像你所说的,装饰模式的优点是,把类中的装饰功能从类中搬移去除,这样可以简化原有的类。像前面的原价算法就是最基础的类,而打折或返现,都算是装饰算法了。"
"是的,这样做更大的好处就是有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。我们不必去重复编写类似打折后再返现,或返现后再打折的代码,对于装饰模式来说,只是多几种组合而已。"
"这个模式真不错,我以后要记着常使用它。"
"你可要当心哦,装饰模式的装饰顺序很重要哦,比如加密数据和过滤词汇都可以是数据持久化前的装饰功能,但若先加密了数据再用过滤功能就会出问题了,最理想的情况,是保证装饰类之间彼此独立,这样它们就可以以任意的顺序进行组合了。"