zoukankan      html  css  js  c++  java
  • 从汉堡加料说起——浅谈C#中的Decorator模式

    相信大家都在都在汉堡店吃过汉堡,有些汉堡店很有特色,推出了汉堡订制服务,即,可以在汉堡中加料,加肉饼,加生菜之类(有点类似我们本地的肥肠粉里面加冒结子)。更是让不少吃货大快朵颐,大呼过瘾,加6,7层肉饼的感觉简直不要太好。
     
    那么大饱口福之后,让我们来思考一个问题,汉堡是要钱的,加的料,比如肉饼,生菜,也都是收费的,如果让我们来设计出一套类,计算客户买汉堡的消费,我们应该怎么做比较合适?这里为了简单起见,我们就假定加的肉饼是beef,生菜是tomatto。
     

    第一种设计

    建立3个类,一个表示汉堡,另外两个表示肉饼和生菜。汉堡类中有办法添加肉饼和生菜。结算费用的时候,直接调用汉堡类的方法。

    在代码中则以这样的形式呈现。

        class Beef
        {
            public double GetCost()
            {
                return 10;
            }
        }
    
        class Tomatto
        {
            public double GetCost()
            {
                return 5;
            }
        }
        class Hamburg
        {
            public List<Beef> Beefs { get; private set; } = new List<Beef>();
            public List<Tomatto> Tomattos { get; private set; } = new List<Tomatto>();
    
            public void AddBeef(Beef beef)
            {
                Beefs.Add(beef);
            }
    
            public void AddTomatto (Tomatto tomatto)
            {
                Tomattos.Add(tomatto);
            }
    
            public double GetCost()
            {
                var result = 20d; //hamburg's cost
                Beefs.ForEach(b => result += b.GetCost());
                Tomattos.ForEach(t => result += t.GetCost());
    
                return result;
            }
        }
    

    这就是最简单的一种实现方法,然而它有以下几个弊端。

    • 类数量过多,应该通过抽象减少类数量,如果以后还有鸡肉饼,小龙虾饼,岂不是又要加新的类?而其实这些类彼此都是相似的。
    • 不满足开闭原则。如果以后有了其他可以添加的料,我们会不可避免的修改Hamburg类。
    • Hamburg类与具体的料耦合。

    所以,这种最简单的做法,如果对于一个小项目或者很简单的案例,我们还可以容忍,如果对于一个大项目,或者预计到未来会出现需求改变的时候,我们就需要改进我们的设计方案。
     

    第二种设计,抽象出料接口

    第一种设计中很大的一个缺陷来自于,不管是牛肉饼也罢,生菜也罢,它们都汉堡的一种添加物,对于计费系统,关心的也仅仅是添加物的名字和价格而已,所以,我们应该抽象出接口来进行汉堡类和具体添加物类的解耦。

    代码实现如下:

        interface Addin
        {
            double GetCost();
        }
    
        class Beef:Addin
        {
            public double GetCost()
            {
                return 10;
            }
        }
    
        class Tomatto:Addin
        {
            public double GetCost()
            {
                return 5;
            }
        }
    
        class Hamburg
        {
            public List<Addin> Addins { get; private set; } = new List<Addin>();
            public void AddAddin(Addin addin)
            {
                Addins.Add(addin);
            }
    
            public double GetCost()
            {
                var result = 20d;
                Addins.ForEach(a => result += a.GetCost());
                return result;
            }
        }
    

    在第二版设计中,我们提炼出了接口addin,使得hamburg类依赖于addin而不直接依赖于具体某个添加物类。同时也保证了开闭原则的实现,就算新的添加物上线,我们也不用修改hamburg类了,我们似乎达到了设计的理想境界。
    但是真的这样就无懈可击了吗?
     

    第三种设计,Decorator模式

    虽然我们第二种设计解决了依赖于具体类的问题并实现了开闭原则,但是还是会有人觉得不爽,因为大家觉得,虽然第二种设计没有什么大问题了,但是在语义上面,我们希望能保证hamburg类的纯洁性。什么意思呢,就是说,hamburg自己代表自己的价格就行了,添加物毕竟是外来物,没有必要深入到hamburg类的内部。
    所以,我们就再次更新我们的设计,这次我们祭出Decorator模式。

    以下是Decorotor模式中需要注意的点:

    • 装饰类基类和被装饰对象都继承自同一个接口,装饰基类内部还聚合了一个此接口对象。
    • 装饰具体类在计算中,先计算自己那部分,再调用基类方法,基类方法一般是计算内部聚合的那个对象, 这样确保了装饰模式可以一层嵌套一层。

    我们看看具体代码。

        abstract class Food
        {
            public abstract double GetCost();
        }
    
        class Hamburger : Food
        {
            public override double GetCost()
            {
                return 20;
            }
        }
    
        class FoodDecorate : Food
        {
            private Food _food = null;
            public FoodDecorate(Food food)
            {
                _food = food;
            }
    
            public override double GetCost()
            {
                return _food.GetCost();
            }
        }
    
        class TomatoDecorator : FoodDecorate
        {
            public TomatoDecorator(Food food) : base(food) { }
            public override double GetCost()
            {
                return 5 + base.GetCost();
            }
        }
    
        class BeefDecorator : FoodDecorate
        {
            public BeefDecorator(Food food) : base(food) { }
            public override double GetCost()
            {
                return 10 + base.GetCost();
            }        
        }
    

    因为不管是Hamburg还是Decorator,大家都实现了Food接口,同时Decorator聚合的也是Food对象,所以在客户端我们可以很方便的写

        BeefDecorator beefAddHamburg = new BeefDecorator(new BeefDecorator(new Hamburger()));
        Console.WriteLine(beefAddHamburg.GetCost());
    

    以此来表示加了两层牛肉的hamburg。怎么样,这是不是比第二种设计又方便了一点呢?
     
    总结一下,Decorator主要用于如下场景:

    • 想要方便的添加一些行为,而这些行为又不属于类的核心行为。
    • 添加行为的时候,不希望出现类数量爆炸的时候。
  • 相关阅读:
    胡昊—第9次作业--接口及接口回调
    胡昊—第8次作业--继承
    软件工程第三次作业——关于软件质量保障初探
    胡昊—第7次作业--访问权限、对象使用
    胡昊—第6次作业—static关键字、对象
    20194670自动生成四则运算题第一版报告
    《现代软件工程—构建之法》第一章总结
    第四次博客作业-结对项目
    第9次作业--接口及接口回调
    第八次作业--继承
  • 原文地址:https://www.cnblogs.com/deatharthas/p/13027989.html
Copyright © 2011-2022 走看看