zoukankan      html  css  js  c++  java
  • OO之装饰者模式

    以下为装饰者模式详解:

    引子:

          假如有一个快餐店,基本种类分为米饭,水饺,粉面等,但每一种类型的快餐又可以搭配不同的料,如米饭可以点各种不同的菜(排骨,青菜,土豆等),如果按照一般的设计,快餐为基类,加不同的料的搭配为一个类,分别继承到基类,那么N种搭配就有N个类,这是类的爆炸。

         要解决这个问题我们就可以用装饰者模式了,首先来了解一个OO设计原则:类对扩展的开放,对修改的关闭。这并不矛盾,像上面的类爆炸子类的行为(继承)是在编译时就静态决定了的,且所有子类都会继承到相同的行为,要解决问题就要做到动态的扩展,在类进行扩展时不修改现有的代码而添加新的行为,这样就要动态的组合对象,弹性的应对改变,可以使引进Bug的可能性大幅减少。

          注意:使用这种原则,组合对象并不是百利而无一害的,这需要花费更多的时间来设计且通常会引入新的抽象层,使代码变得复杂,所以不能过多地方使用,只要在最有可能改变的地方使用。

         下面我们用装饰者模式来设计这个快餐店,以快餐以主体,用菜码来“装饰”快餐。比如顾客要来一份排骨套餐,那么做法如下:

    1:先来一个米饭对象。

    2:用青菜来装饰它。

    3:用排骨来装饰它。

    4:最后调用排骨对象的cost()方法来计费(用委托把菜的价钱加上去)。

    如下图:

          这样的话米饭和装饰它的青菜,排骨都要有共同的基类,且基类中有一个抽象的cost()方法用来计费,这样每一层都必须复写cost()方法。青菜对象是一个装饰者,它的类型也反映(继承自同一个基类)了它所装饰的对象(米饭),当需要计算价格时调用最外层的cost()外层的又调用内层的算出价格再加上自己的价格(逐层委托)。

          理解如下:

    1:装饰者和被装饰者对象有相同的基类。

    2:可以用一个或多个装饰者包装一个对象。

    3:在任何需要原始对象(被包装者)的场合,可以用装饰过的对象来代替它(继承自同一父类)。

    4:装饰者可以在委托被装饰者行为之前或之后,加上自己的行为,以达到目的。

    5:对象可以在任何时候,动态地,不限量的用你喜欢的装饰者来装饰。

         下面为装饰者模式的基本类图:

         为什么用继承呢?上面说过装饰者和被装饰者必须是同一类型,这里运行继承不是要过得“行为 ”,而是要达到“类型的匹配”。当需要加入新的行为时,用不同的装饰者进行组合,混合着用,不需要改变已有代码(如果依赖继承就得修改之前的代码)。

         下面开始设计快餐店的代码:

    /**
     * 抽象组件类
     * @author Homg
     *
     */
    public abstract class FastFood {
        String description = "Unknown Food";
    
        //描述方法,已经实现,因为组件是可以单独使用的
        public String getDescription() {
            return description;
        }
        
        //抽象的计费方法,由具体组件去实现
        public abstract double cost();
    }
    /**
     * 继承自组件基类(快餐)的具体组件(水饺)
     * 
     * @author Homg
     * 
     */
    public class BoiledDumpling extends FastFood {
        // 描述自己,变量是继承自组件(快餐)基类的
        public BoiledDumpling() {
            description = "水饺";
        }
    
        // 计算自己的价格,没有加料的
        @Override
        public double cost() {
            return 8.5;
        }
    
    }
    /**
     * 继承自组件基类(快餐)的具体组件(米饭)
     * 
     * @author Homg
     * 
     */
    public class Rice extends FastFood {
        // 描述自己,变量是继承自组件基类的
        public Rice() {
            description = "米饭";
        }
    
        // 计算自己的价格,没有加料的
        @Override
        public double cost() {
            return 5.5;
        }
    
    }
    /**
     * 继承自组件的抽象装饰者
     * 
     * @author Homg
     * 
     */
    public abstract class CondimentDecorator extends FastFood {
        // 抽象的描述方法,因为下面的装饰者都要完整的描述出组件和所加的装饰,所以它们必须重新实现。
        public abstract String getDescription();
    }
    /**
     * 具体装饰者(香菜)
     * @author Homg
     *
     */
    public class Caraway extends CondimentDecorator {
        // 用一个实例变量记录组件(快餐)
        private FastFood fastFood;
    
        // 传入组件
        public Caraway(FastFood fastFood) {
            this.fastFood = fastFood;
        }
    
        // 通过组件的调用来获得加料后的完整描述
        @Override
        public String getDescription() {
            return fastFood.getDescription() + ", 香菜";
        }
    
        // 先计算被装饰者的价格再加上自己的价格
        @Override
        public double cost() {
            return fastFood.cost() + 1.5;
        }
    
    }
    /**
     * 具体装饰者(排骨)
     * 
     * @author Homg
     * 
     */
    public class PorkRibs extends CondimentDecorator {
    
        // 用一个实例变量记录组件(快餐)
        private FastFood fastFood;
    
        // 传入组件
        public PorkRibs(FastFood fastFood) {
            this.fastFood = fastFood;
        }
    
        // 通过组件的调用来获得加料后的完整描述
        @Override
        public String getDescription() {
            return fastFood.getDescription() + ", 排骨";
        }
    
        // 先计算被装饰者的价格再加上自己的价格
        @Override
        public double cost() {
            return fastFood.cost() + 12.5;
        }
    
    }

    装饰什么就把什么传进去:

    public static void main(String[] args) {
            /*------------1部分------------*/
            //创建一个水饺对象,不加一点料
    //        FastFood fastFood = new BoiledDumpling();
    //        System.out.println(fastFood.getDescription() + ",¥" + fastFood.cost());
            /*------------2部分------------*/
    //        //给水饺加上香菜和排骨
    //        fastFood = new Caraway(fastFood);
    //        fastFood = new PorkRibs(fastFood);
    //        System.out.println(fastFood.getDescription() + ",¥" + fastFood.cost());
            /*------------3部分------------*/
            //创建一个米饭对象,给米饭加上香菜和排骨
            FastFood fastFood2 = new Rice();
            fastFood2 = new Caraway(fastFood2);
            fastFood2 = new PorkRibs(fastFood2);
            System.out.println(fastFood2.getDescription() + ",¥" + fastFood2.cost());
    
        }

        上面main函数中逐个运行3个部分,注释掉另外两个部分。

        运行结果如下:

         1部分:
    水饺,¥8.5

         2部分:

    水饺, 香菜, 排骨,¥22.5

         3部分:

    米饭, 香菜, 排骨,¥19.5

         快餐店的类图:

          应用:

          Java的i/o,直接与文件接触的组件InputStream,装饰它的装饰者有BufferedInputStream等。  

          总结:

          装饰者模式是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

    完整代码下载地址(也可以留下邮箱发给你):

     http://download.csdn.net/detail/homg92/6862759

  • 相关阅读:
    【酷熊科技】工作积累 ----------- Unity3D UITable协程加载问题 (转载 ---- 关于NGUI中UiTable和UIGrid脚本的一点感受)
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 控制角色在真实的环境中寻路
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 动作event实际应用
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- Animator动画状态机
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- Legacy动画系统
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 代码控制音频视频
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 实际应用physic:控制你的角色在真实的环境中行走
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 实际应用physic:控制你的角色在真实的环境中行走
    【unity3d study ---- 麦子学院】---------- unity3d常用组件及分析 ---------- 组件的生命周期
    设计模式 之 《外观模式》
  • 原文地址:https://www.cnblogs.com/homg/p/3525539.html
Copyright © 2011-2022 走看看