美文网首页
生活中的设计模式之装饰器模式

生活中的设计模式之装饰器模式

作者: chzne | 来源:发表于2021-12-08 12:39 被阅读0次

情景

假设,不久前你在饿了么开了一家面店,只卖一种面既原味面,但生意却很火爆。昨天,你查看店铺的留言时发现:有很多客户要求增加鸡蛋和番茄等配菜。
为了不流失客户,你决定通过继承的方式新增鸡蛋面和番茄面,来满足客户需求。扩展后的菜单如下:

avatar

新品推出后,得到了部份客户的一致好评,但也有给差评的。差评的原因大多都是因为无法组合配菜,其中一条差评留言是:无法下单两个鸡蛋的番茄面。
为了不影响店铺的口碑,你决定再次通过继承新增番茄鸡蛋面和两个鸡蛋的番茄面。菜单如下:

avatar

问题

显然,继承将功能(配菜)和对象(面)静态的绑定在了一起,使原本可选的功能失去了动态组合的灵活性,导致无法动态地组合已有的功能来扩展对象的功能。
如果可选的功能(配菜)很多,且可以相互组合;那么通过继承的方式会导致类膨胀,代码重复、难以维护等问题。所以,这时我们应该考虑另一种更灵活的方式——装饰器模式。

方案

avatar

装饰器模式通过一个独立类即装饰器包装对象的方式,给对象扩展新功能。
装饰器即对象的功能,它和对象之间是一种组合关系而非继承关系,且装饰器之间可嵌套。
装饰器实现了和原对象一致的接口,所以它可以替原对象接收客户请求,并在转发请求前后对其功能进行扩展。

avatar

public class Client {

    public static void main(String[] args){
        //制作二个鸡蛋的番茄鸡蛋面

        //原味面
        OriginalNoddles noddles = OriginalNoddles();
        //第一个鸡蛋面装饰器
        EggDecorator eggDecorator = new EggDecorator(noddles);

        //第二个鸡蛋面装饰器
        EggDecorator eggDecoratorTwo = new EggDecorator(eggDecorator);

        //番茄装饰器
        TomatoDecorator noddlesWithTwoEggAndTomato = new TomatoDecorator(eggDecoratorTwo);
    }
}

结构

avatar

抽象构件(Component):定义了具体构件和抽象装饰器需要实现的接口。

具体构件(ConcreteComponent):被包装的原始对象,实现了Component接口。

抽象装饰器(Decorator):抽象类,持有一个指向被包装对象的引用。

具体装饰器(ConcreteDecorator):实现了具体的扩展功能,在被包装的对象方法调用前后进行功能扩展。

第一步:创建抽象构件。


public interface Component {
    public void operation();
}

第二步:创建具体构件。


public class ConcreteComponent implements Component{

    @Override
    public void operation() {
        System.out.println("the original operation");
    }
}

第三步:创建抽象装饰器,并实现抽象构件接口。


public abstract class Decorator implements Component{

    private final Component component;

    public Decorator(Component component){
        this.component = component;
    }

    @Override
    public void operation() {
        this.component.operation();
    }
}

第四步:创建具体装饰器并继承抽象装饰器。


public  class ConcreteDecoratorA extends Decorator{

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalFunction();
    }

    protected void additionalFunction(){
        System.out.println("this is an additional functionA ");
    }
}


第五步:使用具体装饰器包装具体构件。


public  class ConcreteDecoratorA extends Decorator{

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalFunction();
    }

    protected void additionalFunction(){
        System.out.println("this is an additional functionA ");
    }
}


第六步:使用具体装饰器扩展对象的功能。


public class Client2 {

    public static void main(String[] args) {
        //被装饰者
        Component p = new ConcreteComponent();
        //使用装饰器A包装对象p
        Component a = new ConcreteDecoratorA(p);
        //使用装饰器B包装对象a
        Component b = new ConcreteDecoratorB(a);
        //执行操作
        b.operation();
    }
}

应用

假设,我们有一个发送短信的服务类,它可以根据具体的短信通道来发送验证码和活动通知。短信通道我们接入了两个,一个是腾讯短信通道,一个是阿里短信通道。短信服务类如下:


public enum Channel {
    ALI,TENXUN
}

public interface MessageService {

    public String send(Message message,Channel channel);

}

public class SMSService implements MessageService{

    @Override
    public String send(Message message, Channel channel) {
        //......
        if(Channel.ALI.equals(channel)){
            aliChannel.send(message);
        }else if(Channel.TENXUN.equals(channel)){
            tenxunChannel.send(message);
        }
        //......
        //0001表示对方服务异常
        return "0001";
    }
}

现在,我们希望给短信服务扩展一个功能:当阿里或腾讯的短信服务因异常收不到我们发送的短信时,及时使用另一个短信通道重试一次。

第一步:创建抽象的消息服务装饰器MessageServiceDecorator。


public abstract class MessageServiceDecorator implements MessageService{
    private final MessageService messageService;

    public MessageServiceDecorator(MessageService messageService){
        this.messageService = messageService;
    }
    @Override
    public String send(Message message, Channel channel) {
        return messageService.send(message,channel);
    }
}

第二步:创建具体装饰类重试消息服务RetryMessageService。


public class RetryMessageService extends MessageServiceDecorator{

    public RetryMessageService(MessageService messageService) {
        super(messageService);
    }

    @Override
    public String send(Message message, Channel channel) {
        String result = super.send(message, channel);
        //当前短信通道异常,切换另一个通道
        if("0001".equals(result)){
             Channel anotherChannel = Channel.ALI;
             if(anotherChannel==channel){
                 anotherChannel=Channel.TENXUN;
             }
             return super.send(message, anotherChannel);
        }
        return result;
    }
}

第三步:客户类使用带重试功能的短信服务发送短信,使用没有被装饰的短信服务发送营销活动短信。


public class Client {

    public static void main(String[] args){

        SMSService smsService = new SMSService();
        //发送营销短信
        smsService.send(new Message(),Channel.ALI);

        //用重试装饰器包装短信服务对象
        RetryMessageService retryMessageService = new RetryMessageService(smsService);
        retryMessageService.send(new Message(),Channel.ALI);
    }
}

通过装饰器我们就给短信服务添加了一个可选的扩展功能"切换通道重试",其它的功能如黑名单、频次控制等都可以用同样的方式进行扩展。

相关文章

网友评论

      本文标题:生活中的设计模式之装饰器模式

      本文链接:https://www.haomeiwen.com/subject/dpgvxrtx.html