乐趣区

关于java:趣谈装饰器模式让你一辈子不会忘

本文节选自《设计模式就该这样学》

1 应用装璜器模式解决煎饼加码问题

来看这样一个场景,上班族大多有睡懒觉的习惯,每天早上下班都工夫很缓和,于是很多人为了多睡一会儿,就用更不便的形式解决早餐问题,有些人早餐可能会吃煎饼。煎饼中能够加鸡蛋,也能够加香肠,然而不管怎么加码,都还是一个煎饼。再比方,给蛋糕加上一些水果,给房子装修,都是装璜器模式。

上面用代码来模仿给煎饼加码的业务场景,先来看不必装璜器模式的状况。首先创立一个煎饼 Battercake 类。


public class Battercake {protected String getMsg(){return "煎饼";}

    public int getPrice(){return 5;}

}

而后创立一个加鸡蛋的煎饼 BattercakeWithEgg 类。


public class BattercakeWithEgg extends Battercake{
    @Override
    protected String getMsg() {return super.getMsg() + "+ 1 个鸡蛋";
    }

    @Override
    // 加 1 个鸡蛋加 1 元钱
    public int getPrice() {return super.getPrice() + 1;
    }
}

再创立一个既加鸡蛋又加香肠的 BattercakeWithEggAndSausage 类。


public class BattercakeWithEggAndSausage extends BattercakeWithEgg{
    @Override
    protected String getMsg() {return super.getMsg() + "+ 1 根香肠";
    }

    @Override
    // 加 1 根香肠加 2 元钱
    public int getPrice() {return super.getPrice() + 2;
    }
}

最初编写客户端测试代码。


public static void main(String[] args) {Battercake battercake = new Battercake();
        System.out.println(battercake.getMsg() + ", 总价格:" + battercake.getPrice());

        Battercake battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ", 总价格:" + 
            battercakeWithEgg.getPrice());

        Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
        System.out.println(battercakeWithEggAndSausage.getMsg() + ", 总价格:" + 
            battercakeWithEggAndSausage.getPrice());

    }
        

运行后果如下图所示。

运行后果没有问题。然而,如果用户须要一个加 2 个鸡蛋和 1 根香肠的煎饼,则用当初的类构造是创立不进去的,也无奈主动计算出价格,除非再创立一个类做定制。如果需要再变,那么始终加定制显然是不迷信的。
上面用装璜器模式来解决下面的问题。首先创立一个煎饼的形象 Battercake 类。


public abstract class Battercake {protected abstract String getMsg();
    protected abstract int getPrice();}

创立一个根本的煎饼(或者叫根底套餐)BaseBattercake。


public class BaseBattercake extends Battercake {protected String getMsg(){return "煎饼";}

    public int getPrice(){ return 5;}
}

而后创立一个扩大套餐的形象装璜器 BattercakeDecotator 类。


public abstract class BattercakeDecorator extends Battercake {
    // 动态代理,委派
    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {this.battercake = battercake;}
    protected abstract void doSomething();

    @Override
    protected String getMsg() {return this.battercake.getMsg();
    }
    @Override
    protected int getPrice() {return this.battercake.getPrice();
    }
}

接着创立鸡蛋装璜器 EggDecorator 类。


public class EggDecorator extends BattercakeDecorator {public EggDecorator(Battercake battercake) {super(battercake);
    }

    protected void doSomething() {}

    @Override
    protected String getMsg() {return super.getMsg() + "+ 1 个鸡蛋";
    }

    @Override
    protected int getPrice() {return super.getPrice() + 1;
    }
}

创立香肠装璜器 SausageDecorator 类。


public class SausageDecorator extends BattercakeDecorator {public SausageDecorator(Battercake battercake) {super(battercake);
    }

    protected void doSomething() {}

    @Override
    protected String getMsg() {return super.getMsg() + "+ 1 根香肠";
    }
    @Override
    protected int getPrice() {return super.getPrice() + 2;
    }
}

再编写客户端测试代码。


public class BattercakeTest {public static void main(String[] args) {
        Battercake battercake;
        // 买一个煎饼
        battercake = new BaseBattercake();
        // 煎饼有点小,想再加 1 个鸡蛋
        battercake = new EggDecorator(battercake);
        // 再加 1 个鸡蛋
        battercake = new EggDecorator(battercake);
        // 很饿,再加 1 根香肠
        battercake = new SausageDecorator(battercake);

        // 与动态代理的最大区别就是职责不同
        // 动态代理不肯定要满足 is- a 的关系
        // 动态代理会做性能加强,同一个职责变得不一样

        // 装璜器更多思考的是扩大
        System.out.println(battercake.getMsg() + ", 总价:" + battercake.getPrice());
    }
}

运行后果如下图所示。

最初来看类图,如下图所示。

2 应用装璜器模式扩大日志格局输入

为了加深印象,咱们再来看一个利用场景。需要大抵是这样的,零碎采纳的是 SLS 服务监控我的项目日志,以 JSON 格局解析,因而须要将我的项目中的日志封装成 JSON 格局再打印。现有的日志体系采纳 Log4j + Slf4j 框架搭建而成。客户端调用如下。


  private static final Logger logger = LoggerFactory.getLogger(Component.class);
        logger.error(string);
                

这样打印进去的是毫无规定的一行行字符串。当思考将其转换成 JSON 格局时,笔者采纳装璜器模式。目前有的是对立接口 Logger 和其具体实现类,笔者要加的就是一个装璜类和真正封装成 JSON 格局的装璜产品类。创立装璜器类 DecoratorLogger。


public class DecoratorLogger implements Logger {

    public Logger logger;

    public DecoratorLogger(Logger logger) {this.logger = logger;}

    public void error(String str) {}

    public void error(String s, Object o) { }
    // 省略其余默认实现
}

创立具体组件 JsonLogger 类。


public class JsonLogger extends DecoratorLogger {public JsonLogger(Logger logger) {super(logger);
    }
        
    @Override
    public void info(String msg) {JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.info(result.toString());
    }
    
    @Override
    public void error(String msg) {JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.error(result.toString());
    }
    
    public void error(Exception e) {JSONObject result = composeBasicJsonResult();
        result.put("EXCEPTION", e.getClass().getName());
        String exceptionStackTrace = Arrays.toString(e.getStackTrace());
        result.put("STACKTRACE", exceptionStackTrace);
        logger.error(result.toString());
    }
    
    private JSONObject composeBasicJsonResult() {
        // 拼装了一些运行时的信息
        return new JSONObject();}
}

能够看到,在 JsonLogger 中,对于 Logger 的各种接口,咱们都用 JsonObject 对象进行一层封装。在打印的时候,最终还是调用原生接口 logger.error(string),只是这个 String 参数曾经被装璜过了。如果有额定的需要,则能够再写一个函数去实现。比方 error(Exception e),只传入一个异样对象,这样在调用时就十分不便。
另外,为了在新老交替的过程中尽量不扭转太多代码和应用形式,笔者又在 JsonLogger 中退出了一个外部的工厂类 JsonLoggerFactory(这个类转移到 DecoratorLogger 中可能更好一些)。它蕴含一个静态方法,用于提供对应的 JsonLogger 实例。最终在新的日志体系中,应用形式如下。


    private static final Logger logger = JsonLoggerFactory.getLogger(Client.class);

    public static void main(String[] args) {logger.error("错误信息");
    }
        

对于客户端而言,惟一与原先不同的中央就是将 LoggerFactory 改为 JsonLoggerFactory 即可,这样的实现,也会更快更不便地被其余开发者承受和习惯。最初看如下图所示的类图。

装璜器模式最实质的特色是将原有类的附加性能抽离进去,简化原有类的逻辑。通过这样两个案例,咱们能够总结进去,其实形象的装璜器是可有可无的,具体能够依据业务模型来抉择。

【举荐】Tom 弹架构:珍藏本文,相当于珍藏一本“设计模式”的书

本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!

退出移动版