1. 需求
现在需要设计一个可以给人搭配不同服饰的系统,比如类似QQ、电商平台或者游戏都有的Avatar系统。通俗的来说,就是设计一个可以换各种各样衣服裤子的个人形象系统。
2. 代码版本1.0
2.1 Person类
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public void wearTShirts(){
System.out.println("大T恤");
}
public void wearBigTrouser(){
System.out.println("垮裤");
}
public void wearSneakers(){
System.out.println("球鞋");
}
public void wearSuit(){
System.out.println("西装");
}
public void wearTie(){
System.out.println("领带");
}
public void wearLeatherShoes(){
System.out.println("皮鞋");
}
public void show(){
System.out.println("装扮的"+name);
}
}
2.2 客户端代码
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();
运行结果如下:
第一种装扮:
大T恤 垮裤 球鞋装扮的小菜
第二种装扮:
西装 领带 皮鞋装扮的小菜
3. 代码版本2.0
显然易见,代码1.0版本违背了设计模式中开放----封闭原则,因此,我们需要将服饰写成子类。
UML类图
3.1 Person类
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public void show(){
System.out.println("装扮的"+name);
}
}
3.2 服饰抽象类
public abstract class Finery{
public abstract void show();
}
3.3 各种服饰子类
public class TShirts extends Finery{
public void show(){
System.out.println("大T恤");
}
}
public class BigTrouser extends Finery{
public void show(){
System.out.println("垮裤");
}
}
注意上面都是继承Finery类,其余类类似,省略。
3.4 客户端代码
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();
3.5 弊端
如下代码
dtx.show();
kk.show();
pqx.show();
xc.show();
就相当于你光着身子,当着大家的面,先穿T恤,再穿裤子,再穿鞋,仿佛在跳穿衣舞。因此,我们需要把所需的功能按正确的顺序串联起来进行控制,这就需要用到装饰模式。
4. 装饰模式案例
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
4.1 UML类图
Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职 责 。 Decorator是装饰抽象类,继承了 Component,从外类来扩展Component类的功能,但对于Component来说,是无须知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能
4.2 Component类
//Component类
abstract class Component{
public abstract void Operation();
}
4.3 ConcreteComponent类
//ConcreteComponent类
class ConcreteComponent extends Component{
public void Operation(){
System.out.println("具体对象的实际操作");
}
}
4.4 Decorator类
//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();
}
}
}
4.5 ConcreteDecoratorA类
//ConcreteDecoratorA类
class ConcreteDecoratorA extends Decorator{
private String addState;//本类独有字段,以区别于ConcreteDecorator类
public void Operation(){
super.Operation(); //首先运行原有Component的Operation()
this.addState = "具体装饰对象A的都有操作";
System.out.println(this.addState);
}
}
4.6 ConcreteDecoratorB类
//ConcreteDecoratorB类
class ConcreteDecoratorB extends Decorator{
public void Operation(){
super.Operation();
this.AddedBehavior();
}
//本类都有方法,以区别于ConcreteDecoratorA类
private void AddedBehavior(){
System.out.println("具体装饰对象B的独有操作");
}
}
4.7 客户端代码
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
d1.SetComponent(c); //首先用d1包装c
d2.SetComponent(d1); //再用d2来包装d1
d2.Operation(); //最终执行d2的Operation
可见,装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。
5. 装饰模式实现
5.1 UML类图
5.2 ICharacter接口(Component)
//人物形象接口
public interface ICharacter{
public void show();
}
5.3 Person类(ConcreteComponent)
//具体人类
public class Person implements ICharacter{
private String name;
public Person(String name){
this.name = name;
}
public void show(){
System.out.println("装扮的"+name);
}
}
5.4 Finery类(Decorator)
//服饰类
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();
}
}
}
5.5 具体服饰类(ConcreteDecorator)
//服饰类
public class TShirts extends Finery{
public void show(){
System.out.println("大T恤");
super.show();
}
}
其余类类似,省略。
5.6 客户端代码
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();
dtx.decorate(kk);
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();
结果显示如下:
第一种装扮:
大T恤垮裤球鞋装扮的小菜
第二种装扮:
西装领带皮鞋装扮的小菜
5.7 代码拓展
如果换一种装饰方式,比如说,增加草帽装扮,再重新组合一下服饰,则需要增加一个草帽子类,再修改一下装饰方案就好了。
5.7.1 草帽子类
public class Strawhat extends Finery{
public void show(){
System.out.println("草帽");
super.show();
}
}
5.7.2客户端代码
System.out.println("第三种装扮:");
Sneakers pqx2 = new Sneakers();
pqx2.decorate(xc);
LeatherShoes px2 = new LeatherShoes();
px2.decorate(pqx2);
Tie ld2 = new Tie();
ld2.decorate(kk2);
Strawhat cm2 = new Strawhat();
cm2.decorate(ld2);
cm2.show();
运行结果如下:
第三种装扮:
草帽 领带 垮裤 皮鞋 球鞋装扮的小菜
6. 商品收银软件回顾
在策略模式的读书笔记中(策略模式读书笔记)介绍了商品收银软件的设计,现在再次增加需求:所有商品,在总价打8折的基础上,再满300返100,当然这只是一种销售方案,还可以是打7折,再满200送50,或者满300返50,再打7折,也就是说,可以多种促销方案组合起来使用。
我们的要求是最终实现的代码,影响面越小越好,也就是原来的代码能利用就要利用,改动尽量小一些,因此,可以使用本文提到的装饰模式。但是在使用装饰模式之前,我们先用策略模式进行实现,下面给出UML类图。
6.1 策略模式实现
6.1.1 UML类图
6.1.2 先折扣再返利的算法子类
public class CashReturnRebate extends CashSuper{
private double moneyRebate = 1d;
private double moneyCondition = 0d;
private double moneyReturn = 0d;
//先折扣,再返利,初始化时需要输入折扣参数,再输入返利条件和返利值
//比如”先八折,再满300返100“,就是moneyRebate=0.8,moneyCondition=300,moneyReturn=100
public 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;
}
}
6.1.3 修改后的CashContext类
public class CashContext{
private CashSuper cs; //声明一个CashSuper对象
//通过构造方法,传入具体的收费策略
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:
this.cs = new Strawhat.CashReturnRebate(0.9d,300d,100d);
break;
}
}
public double getResult(double price,int num){
//根据收费策略不同,获取计算结果
return this.cs.acceptCash(price,num);
}
}
6.1.4 弊端
观察上述代码可发现:新类CashReturnRebate有原来的两个类:CashReturn和CashRebate有大量重复的代码,为了解决上述问题,可以用装饰模式
6.2 简单工厂+策略+装饰模式实现
6.2.1 UML类图
6.2.2 ISale类
public interface ISale {
public double acceptCash(double price,int num);
}
6.2.3 CashSuper
CashSuper原来是抽象类,改成普通类,但实现ISale接口。
public double acceptCash(double price,int num){
double result = 0d;
if (this.component != null){
//若装饰对象存在。则执行装饰的算法运算
result = this.component.acceptCash(price,num);
}
return result;
}
6.2.4 CashNormal
CashNormal,相当于ConcreteComponent,是最基本的功能实现,也就是单价×数量的原价算法。
public class CashNormal implements ISale {
//正常收费,原价返回
public double acceptCash(double price,int num){
return price * num;
}
}
6.2.5 CashReturn和CashRebate
public class CashRebate extends CashSuper {
private double moneyRebate = 1d;
//打折收费,初始化时必须输入折扣率。八折就输入0.8
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
//收费计算时需要在原价的基础上乘以折扣率
public double acceptCash(double price,int num){
double result = price * num * this.moneyRebate;
return super.acceptCash(result,1);
}
}
public class CashReturn extends CashSuper {
private double moneyCondition = 0d; //返利条件
private double moneyReturn = 0d; //返利值
//返利收费。初始化时需要输入返利条件和返利值
//比如满300返100,就是moneyCondition=300,moneyReturn=100
public 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);
}
}
6.2.6 CashContext
重点在CashContext类,因为涉及组合算法,所以用装饰模式的方式进行包装,这里需要注意包装的顺序,先打折后满多少返多少,与先满多少返多少,再打折会得到完全不同的结果。
public class CashContext {
private ISale cs; //声明一个ISale接口对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
switch (cashType){
case 1:
this.cs = new CashNormal();
break;
.....
case 5:
//先打八折,再慢300返100
CashNormal cn = new CashNormal();
CashReturn cr1 = new CashReturn(300d,100d);
CashRebate cr2 = new CashRebate(0.8d);
cr1.decorate(cn);
cr2.decorate(cr1);
this.cs = cr2;
break;
case 6:
//先满200返50,再打七折
CashNormal cn2 = new CashNormal();
CashRebate cr3 = new CashRebate(0.7d);
CashReturn cr4 = new CashReturn(200d,50d);
cr3.decorate(cn2);
cr4.decorate(cr3);
this.cs = cr4;
break;
}
public double getResult(double price,int num){
return this.cs.acceptCash(price,num);
}
}
}
测试结果:
单价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元。
7. 总结
装饰模式是为已有功能动态地添加更多功能的一种方式。
当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,这就会增加原有代码的复杂度。
因此我们需要使用装饰模式它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。