温故而知新:设计模式之装饰模式(Decorator)

简介: 小时候对日本的动画片十分着迷,“圣斗士”是我的最爱;长大后也曾经一度对“海贼王”十分痴迷;大学看武侠小说时,也特别喜欢那种主人公有奇遇的情况:吃到一颗千年异果,然后功夫大增60年... 这些个场景都有一个共同点:对象(或系统)会因为一些需求(通常这些需求之间没有任何关联),而扩展自己的功能。

小时候对日本的动画片十分着迷,“圣斗士”是我的最爱;长大后也曾经一度对“海贼王”十分痴迷;大学看武侠小说时,也特别喜欢那种主人公有奇遇的情况:吃到一颗千年异果,然后功夫大增60年...


这些个场景都有一个共同点:对象(或系统)会因为一些需求(通常这些需求之间没有任何关联),而扩展自己的功能。具体来说:青铜战士如果有幸能穿上黄金圣衣,不管你是不是黄金圣斗士,在穿上黄金圣衣的那一刻,你就具有黄金圣斗士的能力;海赋王中的人物,如果能吃到一颗奇异果,就能获得特别的能力(比如路飞就是吃了橡胶奇异果);武侠小说中,主角如果不经意间吃下了千年人参,从此功力大增,打遍天下无敌手...

ok,下面谈谈如何设计,就拿海贼王为例吧,假定系统中有三种奇异果:橡皮奇异果[RubberFruit]--吃了以后身体象橡皮一样有韧性,飞行奇异果[FlyFruit]--吃了以后可以飞起来,金属奇异果[MetalFruit]--吃了以后身体可能变得象金属一样坚硬.


按常规思路来设计:基本人物可能一种都没吃,也可以吃了几种,我们把各种功能扩展抽象成接口,再分别用基本人物的子类中实现这些接口

/// <summary>
    /// 橡皮功能接口
    /// </summary>
    public interface IRubberFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 飞行功能接口
    /// </summary>
    public interface IFlyFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 金属功能接口
    /// </summary>
    public interface IMetalFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 人物抽象类
    /// </summary>
    public abstract class Character
    {

    }

    /// <summary>
    /// 基本人物(无任何特殊能力)
    /// </summary>
    public class NormalCharacter : Character
    {
        public void GetAlility()
        {
            Console.WriteLine("默认基本人物,啥特殊能力都没有!");
        }
    }

    /// <summary>
    /// 吃了橡皮果后的人物
    /// </summary>
    public class RubberCharacter : Character, IRubberFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我的身体可以变得跟橡皮一样有韧性!");
        }
    }


    /// <summary>
    /// 吃了金属果后的人物
    /// </summary>
    public class MetalCharacter : Character, IMetalFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("如果我愿意,我的身体也可以变得象金属一样硬!");
        }
    }


    /// <summary>
    /// 吃了飞行果后的人物
    /// </summary>
    public class FlyCharacter : Character, IFlyFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我能飞!");
        }
    }

    /// <summary>
    /// 吃了橡皮果+飞行果后的人物
    /// </summary>
    public class RubberAndFlyCharacter : Character, IRubberFruit, IFlyFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我的身体可以变得跟橡皮一样有韧性,而且我也能飞!");
        }
    }


    /// <summary>
    /// 吃了橡皮果+金属果后的人物
    /// </summary>
    public class RubberAndMetalCharacter : Character, IRubberFruit, IMetalFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我的身体可以变得跟橡皮一样有韧性,如果我愿意,我的身体也可以变得象金属一样硬!");
        }
    }


    /// <summary>
    /// 吃了金属果+飞行果后的人物
    /// </summary>
    public class MetalAndFlyCharacter : Character, IMetalFruit, IFlyFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我能飞,如果我愿意,我的身体也可以变得象金属一样硬!");
        }
    }

    /// <summary>
    /// 吃了橡皮果+飞行果+金属果后的人物
    /// </summary>
    public class RubberAndFlyAndMetalCharacter : Character, IRubberFruit, IFlyFruit
    {
        public void GetAbility()
        {
            Console.WriteLine("我的身体可以变得跟橡皮一样有韧性,而且我也能飞,如果我愿意,我的身体也可以变得象金属一样硬!");
        }
    }

客户调用示例程序:

class Program
    {
        static void Main(string[] args)
        {
            RubberCharacter rc = new RubberCharacter();
            rc.GetAbility();

            RubberAndFlyAndMetalCharacter rfmc = new RubberAndFlyAndMetalCharacter();
            rfmc.GetAbility();

	    //...

            Console.ReadLine();
        }
    }

如果奇异果的种类不增加,这样也没啥问题,但是问题就在于,海贼王中的奇异果肯定不止这几种,随着这种超能力奇异果的种类增加,整个系统很快就会陷入一种类爆炸的局面。
看看装饰者模式的做法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Decorator
{
    class Program
    {
        static void Main(string[] args)
        {

            RubberCharacter rc = new RubberCharacter(new NormalCharacter());
            rc.GetAbility();
           

            MetalCharacter rfmc = new MetalCharacter(new FlyCharacter(rc));
            rfmc.GetAbility();

            Console.ReadLine();
        }
    }

    /// <summary>
    /// 橡皮功能接口
    /// </summary>
    public interface IRubberFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 飞行功能接口
    /// </summary>
    public interface IFlyFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 金属功能接口
    /// </summary>
    public interface IMetalFruit
    {
        void GetAbility();
    }

    /// <summary>
    /// 人物抽象类
    /// </summary>
    public abstract class Character
    {
        protected Character _character;

        public string Ability { set; get; }


    }

    /// <summary>
    /// 基本人物(无任何特殊能力)
    /// </summary>
    public class NormalCharacter : Character
    {
        public NormalCharacter()
        {
            Ability = "";
        }
        public void GetAlility()
        {
            Console.WriteLine("默认基本人物,啥特殊能力都没有!");
        }
    }

    /// <summary>
    /// 吃了橡皮果后的人物
    /// </summary>
    public class RubberCharacter : Character, IRubberFruit
    {
        public RubberCharacter(Character c)
        {
            _character = c;
            Ability += _character.Ability + "我的身体可以变得跟橡皮一样有韧性!";
        }

        public void GetAbility()
        {
            
            Console.WriteLine(Ability);
        }
    }


    /// <summary>
    /// 吃了金属果后的人物
    /// </summary>
    public class MetalCharacter : Character, IMetalFruit
    {
        public MetalCharacter(Character c)
        {
            _character = c;
            Ability += _character.Ability + "如果我愿意,我的身体也可以变得象金属一样硬!";
        }
        public void GetAbility()
        {
            
            Console.WriteLine(Ability);
        }
    }


    /// <summary>
    /// 吃了飞行果后的人物
    /// </summary>
    public class FlyCharacter : Character, IFlyFruit
    {
        public FlyCharacter(Character c)
        {
            _character = c;
            Ability += _character.Ability + "我能飞!";
        }
        public void GetAbility()
        {
            
            Console.WriteLine(Ability);
        }
    }


}

接口部分并没有变,关键就在于实现接口的各个类,巧妙的用对象组合方式解决了类爆炸的问题(有心的读者可以比较一下前后类的数量变化),这样在客户程序调用时,可以把原来的对象层层包裹,附加上一层又一层的功能,最后给出该模式的意图:

意图

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。[GOF 《设计模式》]

类图如下:

img_1e5316e270b4c281f27f1e9c886fd829.jpg

 

后记:该模式其实与桥接模式(Bridge)有些相似,只不过区别在于桥模式用于应对多个维度的变化,而装饰模式只用来处理一个维度的变化。

目录
相关文章
|
6月前
|
设计模式 PHP
php设计模式--装饰模式(七)装饰模式完成文章编辑
php设计模式--装饰模式(七)装饰模式完成文章编辑
36 0
|
6月前
|
设计模式 中间件 PHP
设计模式 | 装饰模式
设计模式 | 装饰模式
33 0
|
3月前
|
设计模式 Java
【八】设计模式~~~结构型模式~~~装饰模式(Java)
文章详细介绍了装饰模式(Decorator Pattern),这是一种对象结构型模式,用于在不使用继承的情况下动态地给对象添加额外的职责。装饰模式通过关联机制,使用装饰器类来包装原有对象,并在运行时通过组合的方式扩展对象的行为。文章通过图形界面构件库的设计案例,展示了装饰模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和应用示例。装饰模式提高了系统的灵活性和可扩展性,适用于需要动态、透明地扩展对象功能的情况。
【八】设计模式~~~结构型模式~~~装饰模式(Java)
|
6月前
|
设计模式
设计模式之装饰器 Decorator
设计模式之装饰器 Decorator
46 1
|
5月前
|
设计模式
结构型设计模式之装饰模式
结构型设计模式之装饰模式
|
6月前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~结构型] 扩展系统功能——装饰模式
[设计模式Java实现附plantuml源码~结构型] 扩展系统功能——装饰模式
|
6月前
|
设计模式 Go
[设计模式 Go实现] 结构型~装饰模式
[设计模式 Go实现] 结构型~装饰模式
|
6月前
|
设计模式 Java
小谈设计模式(7)—装饰模式
小谈设计模式(7)—装饰模式
|
6月前
|
设计模式
设计模式之装饰模式--优雅的增强
设计模式之装饰模式--优雅的增强
设计模式之装饰模式--优雅的增强
|
6月前
|
设计模式 人工智能 移动开发
认真学习设计模式之装饰者模式(Decorator Pattern)
写在前言 利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。
62 0