资讯专栏INFORMATION COLUMN

美颜相机中的设计模式 —— 装饰者模式

anonymoussf / 3398人阅读

摘要:这是设计模式系列的第二篇,系列文章目录如下用一句话总结那些殊途同归的设计模式工厂策略模版方法美颜相机中的设计模式装饰者模式几乎所有的设计模式都是通过增加一层抽象来解决问题。

这是设计模式系列的第二篇,系列文章目录如下:

    用一句话总结那些殊途同归的设计模式:工厂=?策略=?模版方法

    美颜相机中的设计模式——装饰者模式

几乎所有的设计模式都是通过增加一层抽象来解决问题。

上一篇中提到的三个设计模式通过相同的手段来达到相同的目的:它们通过接口和抽象方法来新增抽象层以应对变化。

这一系列的后续几篇中会提到的四个设计模式通过相同的手段来达到不同的目的:它们通过新增一个类并持有原有类的方式实现对其扩展或限制。

这一篇先来看看装饰者模式。

装饰者模式就好像美颜相机,通过添加不同的装饰品,它可以让你变成另一个你。(虽然可能面目全非,但本质上还是你)

只复用类型

假设有四种饰品:耳环、钻石、黄金、羽毛。不同装饰品有不同价格,通常我们会这样做抽象:

//抽象饰品
public abstract class Accessory {
    public abstract String name();//饰品名称
    public abstract int cost();//饰品价格
}

//耳环
public class Ring extends Accessory {
    @Override
    public String name() { return "Ring"; }
    @Override
    public int cost() { return 20; }
}

//钻石
public class Diamond extends Accessory {
    @Override
    public String name() { return "Diamond"; }
    @Override
    public int cost() { return 1000; }
}

//黄金
public class Gold extends Accessory {
    @Override
    public String name() { return "Gold"; }
    @Override
    public int cost() { return 300; }
}

//羽毛
public class Feather extends Accessory {
    @Override
    public String name() { return "Feather"; }
    @Override
    public int cost() { return 90; }
}

现推出两款新饰品:黄金耳环,羽毛黄金耳环。同样的思路,使用继承可以解决问题:

public class GoldRing extends Accessory {
    @Override
    public String name() { return "GoldRing"; }
    @Override
    public int cost() { return 320; }
}

public class FeatherGoldRing extends Accessory {
    @Override
    public String name() {  "FeatherGoldRing"; }
    @Override
    public int cost() { return 1110; }
}

如果继续推出更多的新品,比如羽毛耳环,钻石耳环,羽毛钻石耳环。。。每个新产品都用一个新的类表示,这样就会遇到子类膨胀的问题。

除此之外,继承还有一个更致命的缺点:对单个类型的饰品没有统一的控制力。如果黄金涨价了,我们需要分别修改GoldRingFeatherGoldRing的价格,如果和黄金相关的饰品有好几十个,那简直是一场噩梦。

在计算GoldRing价格的时候,我们并没有复用现有代码,即没有复用GoldRing已经定义的cost()行为,而只是通过继承复用了类型(GoldRing是一个Accessory)。只复用类型而没有复用行为的后果是:当Gold涨价时,GoldRing无感知。

有没有一种比继承更好的方案在现有饰品基础上扩展新的饰品?

既复用类型又复用行为

采用组合的方式就可以实现既复用类型又复用行为:

public class Gold extends Accessory {
    private Accessory accessory;
    public Gold(Accessory accessory) { this.accessory = accessory; }
    
    @Override
    public String name() {
        return "Gold " + accessory.name();
    }
    @Override
    public int cost() {
        return 300 + accessory.cost();
    }
}

public class Feather extends Accessory {
    private Accessory accessory;
    public Feather(Accessory accessory) { this.accessory = accessory; }

    @Override
    public String name() {
        return "Feather " + accessory.name();
    }
    @Override
    public int cost() {
        return 90 + accessory.cost();
    }
}

上述四种饰品其实分为两类,耳环属于基本饰品,而羽毛、黄金、钻石属于附加饰品,附加饰品可以装饰基本饰品。

附加饰品和基础饰品拥有相同的超类型Accessory,但附加饰品还通过组合的方式持有一个超类型实例,这样就可以通过注入超类型的方式将其和任意基础饰品组合到一起形成新的饰品。

用组合的方式实现羽毛黄金耳环:

Accessory ring = new Gold(new Feather(new Ring()));

为了说明装饰与被装饰的关系,使用了带有俄罗斯套娃既视感的代码(虽然这样的代码可读性较差)。

Ring作为基础饰品被Feather装饰成羽毛耳环,羽毛耳环接着被Gold装饰成换羽毛黄金耳环。

过程中并没有为羽毛黄金耳环新增一个叫FeatherGoldRing的子类,而是复用了现有的FeatherGold的行为。这样就解决了子类泛滥和控制力的问题。如果黄金涨价,只需要修改Gold.cost(),所有被Gold装饰的饰品价格都会随之而涨。

这个方案还有一个更有用的好处:在运行时动态新增类型。通过继承新增的类型都是在编译时定死的,而通过组合的方式只要新增一行俄罗斯套娃式的代码,程序运行起来后就新增了一个类型,比如要新增“双倍黄金羽毛耳环”这个类型,只需要如下的代码:

Accessory ring = new Gold(new Gold(new Feather(new Ring())));
抽象的装饰者?

新的需求来了:基础饰品镶嵌附加饰品收取 10% 的一次性加工费。我们可以为所有附加饰品增加一层抽象:

public abstract class Decorator extends Accessory{
    private Accessory accessory;
    public Decorator(Accessory accessory) { this.accessory = accessory; }

    @Override
    public int cost() {
        return  1.1 * accessory.cost();
    }
}

Decorator通过组合持有超类型Accessory且规定了在构造时必须注入超类型,它还定义了镶嵌加工费的收费标准。

现在就可以像这样重新定义附加饰品:

public class Gold extends Decorator {
    public Gold(Accessory accessory){ super(accessory); }

    @Override
    public String name() {
        return "Gold " + accessory.name();
    }
    @Override
    public int cost() {
        return 300 + super.cost();
    }
}

其实对于装饰者模式来说,为装饰者定义一个抽象的父类不是必须的,只要满足继承超类型,以及持有超类型引用这两点就是装饰者模式。除非需要统一操作所有装饰者,比如在美颜相机这个场景中,需要通过遍历找出所有附加饰品。

总结

装饰者模式是一种复用原有类并对其进行扩展的方式,它是继承的替代方法。

装饰者模式通过继承原有类型实现复用类型。这一点很重要,因为所有使用原有类型的地方不需要修改代码就可以替换成装饰者。

装饰者模式通过组合持有原有类实例实现复用行为。

装饰者模式通过在调用原有类方法的前后插入新的逻辑实现功能扩展。

装饰者模式符合开闭原则,即在新增功能的时候没有修改原有代码。

装饰者模式特别适用于子类型之间可以有随机组合的场景,比如美颜相机的各种道具组合之后形成新的道具。

运用组合的设计模式不止装饰者一个,该系列的后续文章会继续分析“组合”在设计模式中的运用。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7858.html

相关文章

  • 5 分钟即可掌握的 JavaScript 装饰模式与 AOP

    摘要:下装饰者的实现了解了装饰者模式和的概念之后,我们写一段能够兼容的代码来实现装饰者模式原函数拍照片定义函数装饰函数加滤镜用装饰函数装饰原函数这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过函数来添加。 showImg(https://segmentfault.com/img/bVbueyz?w=852&h=356); 什么是装饰者模式 当我们拍了一张照片准备发朋友...

    chunquedong 评论0 收藏0
  • PHP设计模式系列之入门

    摘要:设计模式系列之入门设计模式是一套被反复使用多数人知晓的经过分类编目的代码设计经验的总结。毫无疑问,设计模式于己于他人于系统都是多赢的设计模式使代码编制真正工程化设计模式是软件工程的基石脉络,如同大厦的结构一样。 PHP设计模式系列之入门 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易...

    animabear 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<