您的位置:首页 > 移动开发 > Android开发

Android中装饰者模式

2017-11-06 14:30 218 查看
设计模式系列:

        0. Android开发常用设计模式;

        1. Android中单例模式;

        2. Android中建造者(builder)模式;

        3. Android中观察者模式;

        4. Android中原型模式;

        5. Android中策略模式;

        6. Android中工厂模式;

        7. Android中代理模式;

        8. Android中装饰者模式;

        9. Android中适配器模式;

  三毛:“小白,你用过’饿了吗’app不”

  小白:嘿嘿,向我这样与时俱进的年轻人肯定用过吖 


  三毛:“像下面的就是‘饿了么’某店的水果拼盘商品,见过吧”

  


  小白:嗯嗯

一、常见需求场景

  三毛:假设现在某店分普通和VIP两种盒子装水果拼盘,现在只有下面4种水果套餐拼盘:

             (A)西瓜+哈密瓜

             (B)西瓜+葡萄

             (C)哈密瓜+葡萄

             (D)西瓜+哈密瓜+葡萄

顾客买了水果拼盘后就输出它的名字和价格,你来模拟下

二、基本解决方法

  小白:毛毛哥,我想到下面2种写法

方法1

//--------------------------水果类--------------------------//

//来个水果接口
public interface IFruit {
String getFruitName();
double getFruitPrice();
}

//西瓜
public class XiGua implements IFruit {
@Override
public String getFruitName() {
return "西瓜";
}

@Override
public double getFruitPrice() {
return 2.00;
}
}
//...其他哈密瓜,葡萄一样,这里就不写了

//--------------------------水果套餐类--------------------------//

//来个盒子接口
public interface IBox {
void boxType();//盒子类型
String getPackageName();//获取套餐名字
double getPackagePrice();//获取套餐价格
}

//普通盒子装的水果拼盘套餐A
public class NormalBoxA implements IBox {
private XiGua mXiGua;
private HaMiGua mHaMiGua;

public NormalBoxA() {
mXiGua = new XiGua();
mHaMiGua = new HaMiGua();
}

@Override
public void boxType() {
System.out.println("普通盒子装的");
}

@Override
public String getPackageName() {
boxType();
return mXiGua.getFruitName()+"+"+mHaMiGua.getFruitName();
}

@Override
public double getPackagePrice() {
return mXiGua.getFruitPrice()+mHaMiGua.getFruitPrice()+0.5;
}
}

//Vip盒子装的水果拼盘套餐A(和普通盒子没啥区别,价格贵了点)
public class VipBoxA implements IBox {
private XiGua mXiGua;
private HaMiGua mHaMiGua;

public VipBoxA() {
mXiGua = new XiGua();
mHaMiGua = new HaMiGua();
}

@Override
public void boxType() {
System.out.println("Vip盒子装的");
}

@Override
public String getPackageName() {
boxType();
return mXiGua.getFruitName()+"+"+mHaMiGua.getFruitName();
}

@Override
public double getPackagePrice() {
return mXiGua.getFruitPrice()+mHaMiGua.getFruitPrice();
}
}

//...其他B、C、D套餐也是一样,这里就不重复了

//使用
NormalBoxA normalBoxA = new NormalBoxA();
VipBoxA vipBoxA = new VipBoxA();

System.out.println(normalBoxA.getPackageName()+":"+normalBoxA.getPackagePrice());
System.out.println(vipBoxA.getPackageName()+":"+vipBoxA.getPackagePrice());
//其他套餐使用也一样...


方法2

//来个枚举或常量类(随便啦)
public enum FruitType {
XiGua(2.00,"西瓜"),HaMiGua(6.00,"哈密瓜"),PuTao(8.50,"葡萄");

private double price;
private String name;

FruitType(double price, String name) {
this.price = price;
this.name = name;
}

public double getPrice() {
return price;
}

public String getName() {
return name;
}
}

//------------------------------套餐类------------------------------//

//在来个套餐接口类
public interface IPackage {
void boxType();//盒子类型
String getPackageName();//得到套餐名字
double getPackagePrice();//得到套餐价格
}

//来个普通盒子装的套餐实现类

public class NormalBoxPackageImpl implements IPackage {
private double mPrice;
private String mFruitPackageName = "";

public void addXiGua() {
mPrice += FruitType.XiGua.getPrice();
mFruitPackageName += FruitType.XiGua.getName() + "+";
}

public void addHaMiGua() {
mPrice += FruitType.HaMiGua.getPrice();
mFruitPackageName += FruitType.HaMiGua.getName() + "+";
}

public void addPuTao() {
mPrice += FruitType.PuTao.getPrice();
mFruitPackageName += FruitType.PuTao.getName() + "+";
}

@Override
public void boxType() {
System.out.println("普通盒子装的");
}

@Override
public String getPackageName() {
boxType();
return mFruitPackageName.isEmpty() ? "" : mFruitPackageName.substring(0, mFruitPackageName.length() - 1);
}

@Override
public double getPackagePrice() {
return mPrice;
}
}

//另外VipBoxPackageImpl类省略,vip盒子装的和普通盒子装的没多少区别,只是套餐价格贵了点...

//使用

NormalBoxPackageImpl normalBoxPackage= new NormalBoxPackageImpl();
normalBoxPackage.addHaMiGua();//要什么水果套餐,自己拼就OK了
System.out.println(normalBoxPackage.getPackageName()+":"+normalBoxPackage.getPackagePrice());


  三毛:“小白,可以吖,鬼点子挺多的嘛”

  小白:还行,还行


三、基本解决方法存在的问题

  三毛:“鬼点子多是多,但是上面的两种方法都存在相应的问题喔”

  小白:


  三毛:”不相信的话,那我来分析分析给你听,我们是讲道理的嘛”

  小白:好吧,毛毛哥你分析分析,我听听看

  三毛:“方法1,白,假设我多加5种水果,套餐就达到26种,有2种盒子,那你不得写26*2个套餐类喔,而且我说的只是5种,如果更加多的话,岂不是累死你了”

  小白:

有点道理

  三毛:”方法2,这个写法确实没有方法1那种问题了,但是,如果功能扩展的时候你是不是得在NormalBoxPackageImpl或VipBoxPackageImpl类里做修改,比如加多个火龙果,你就加多个火龙果方法,套餐中有火龙果的话,你就相当于改变了价格和套餐名字(你要计算价格…),很明显违反了设计模式六大原则中的开闭原则的闭,也就是,新增功能的时候,不能去修改原有的代码”

  小白:我觉得第二种方法挺好的吖,简单粗暴,违反就违反呗

  三毛:”我只是和你说你那样写存在那样的问题,并不是说不能用,其实看场景和需求考虑就好”

  小白:我就是这样想滴,毛毛哥哥

  三毛:”小白,如果装饰者模式能像你上面方法2一样,但是又不违反设计模式规则,你觉得怎么样”

  小白:

 快给我讲讲,毛毛哥

四、装饰者模式写法

装饰者模式定义:动态给一个对象添加一些额外的职责。装饰着模式相比用生成子类方式达到功能的扩展显得更加灵活。

  三毛:”睁大眼睛看好了,使用装饰者模式完成上面需求的正确姿势”

//装饰者模式标准写法,分为4个部分 -->(1)接口;(2)实现接口;(3)抽象类;(4)实现抽象类。

//先来个接口
public interface IGoods {
String getName();//获取商品名字
double getPrice();//获取商品价格
}

//实现商品接口的西瓜
public class XiGua implements IGoods {

private IGoods mIGoods;

public XiGua(IGoods mIGoods) {
this.mIGoods = mIGoods;
}

@Override
public String getName() {
return null == mIGoods ?"西瓜":mIGoods.getName() + "+西瓜";
}

@Override
public double getPrice() {
return null == mIGoods ?2.00:mIGoods.getPrice() + 2.00;
}

}
//哈密瓜、普通...和上面一样,代码省略...

//来个套餐抽象类
public abstract class AbstractPackage implements IGoods {

public IGoods mIGoods;
//你也可以在这里加一些商品接口没有的抽象方法,实现扩展...

public AbstractPackage(IGoods mIGoods) {
this.mIGoods = mIGoods;
}
//获取套餐(商品)名字
public String getPackageName() {
return mIGoods.getName();
}
//获取套餐(商品)价格
public double getPackagePrice() {
return mIGoods.getPrice();
}
}

//实现抽象类 -->普通盒子装的水果套餐类
public class NormalBox extends AbstractPackage {

NormalBox(IGoods mIGoods) {
super(mIGoods);
}

@Override
public String getName() {
return "普通盒子装的:" + super.getPackageName();
}

@Override
public double getPrice() {
return 0.5 + super.getPackagePrice();
}

}

//实现抽象类 -->Vip盒子装的水果套餐类
public class VipBox extends AbstractPackage {

public VipBox(IGoods mIGoods) {
super(mIGoods);
}

@Override
public String getName() {
return "Vip盒子装的:" + super.getPackageName();
}

@Override
public double getPrice() {
return 1.5 + super.getPackagePrice();
}
}

//使用
XiGua xiGua = new XiGua(null);
HaMiGua haMiGua = new HaMiGua(xiGua);//随意组装你想要的水果(拼盘)
NormalBox normalBox = new NormalBox(haMiGua);//放到你想要的盒子里
System.out.println(normalBox.getName() + ":" + normalBox.getPrice());


  小白:使用装饰者模式后,不管套餐有几百个,我们只需要增加对应的水果,然后自己随心所欲组合就好了,而且相互独立不影响


  三毛:”嗯,装饰者模式就像穿衣服,一层套一层,先穿什么,在穿什么都可以,只要是衣服(实现同一接口)类型”

  小白:呐,毛毛哥,装饰者模式一定要按上面的标准写四个东西嘛

  三毛:”我的白,你不要误解喔,上面只是标准做法,实际当中你要根据业务来做,虽然模式是这样,但是只要你代码写得好就行,管它怎样讷,比如上面的普通盒子和Vip盒子类我可以不按上面那样写,可以像下面这样写”

//不实现抽象类了,改成实现商品接口
public class NormalBox implements IGoods {
public IGoods mIGoods;

public NormalBox(IGoods mIGoods) {
this.mIGoods = mIGoods;
}

@Override
public String getName() {
return "普通盒子装的:" + mIGoods.getName();
}

@Override
public double getPrice() {
return 0.5 + mIGoods.getPrice();
}

}
//Vip盒子实现类也可以换成这样写....

//使用还是没变化...


  小白:”这样写比标准写法少了个抽象类啊,这样不会有什么影响嘛”

  三毛;”多个套餐抽象类看起来比较清晰明了啊,如果非要说有影响的话,就是当你扩展业务时,比如在使用盒子装水果套餐前询问用户是否使用本店优惠券,你还不是得在多加一个接口或抽象类?不过我认为怎么写没关系,模式只是提供你一些思路,写代码考虑好问题就没毛病”

  小白:


五、装饰者模式和普通写法区别

1、结构清晰,耦合度更低,扩展性更强;

2、符合设计原则中的开闭原则;

3、比某些正常写法少了很多类和工作;

4、随心所欲按你的顺序组合,灵活多变。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: