设计模式的艺术之道--装饰模式

来源:互联网 发布:淘宝类目网址 编辑:程序博客网 时间:2024/05/19 02:01

设计模式的艺术之道–装饰模式

声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。


本系列开始讲解结构型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。

  • 类结构型模式
    关心类的组合,由多个类组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构型模式
    关心类与对象的组合,通过关联关系,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法
    7种常见的结构型模式
    这里写图片描述

装饰模式

独自北漂的小王,生活拮据,只能住得起毛坯房。但是小王也是个有生活追求的人。对出租房进行了简单的装修工作,装修可以让小王住的更加舒服。在软件设计中,我们也有一种类似新房装修的技术可以对已有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象,使得对象具有更加强大的功能。

1.1定义

  • 装饰模式 (Decorator Pattern):动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
  • 以对客户透明的方式动态地给一个对象附加上更多的责任
  • 可以在不需要创建更多子类的情况下,让对象的功能得以扩展

1.2情景实例

问题描述
- 图形界面构件库的设计
菜鸟软件公司基于面向对象技术开发了一套图形界面构件库VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特效显示效果,如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能
这里写图片描述
初步思路
一个基于继承复用的初始设计方案,在抽象类Component中声明了抽象方法display(),其子类Window、TextBox等实现了display()方法,可以显示最简单的控件,再通过它们的子类来对功能进行扩展,例如,在Window的子类ScrollBarWindow、BlackBorderWindow中对Window中的display()方法进行扩展,分别实现带滚动条和带黑色边框的窗体。
UML类图
这里写图片描述

现有缺点(未来变化)
(1) 系统扩展麻烦,在某些编程语言中无法实现。有多继承(C# Java不支持)
(2)代码重复。如滚动条设置方法,重复太多。
(3) 系统庞大,类的数目非常多。如果增加新的控件需要增加大量的具体类,这将导致系统变得非常庞大
如何改进
如何让系统中的类可以进行扩展但是又不会导致类数目的急剧增加?不用着急,让我们先来分析为什么这个设计方案会存在如此多的问题。根本原因在于复用机制的不合理。
根据“合成复用原则”,在实现功能复用时,我们要多用关联,少用继承。
改进UML类图
这里写图片描述
实例关键代码

namespace DecoratorSample{    abstract class VisualComponent    {        //抽象的组件        public abstract void Display();    }    //其他类似省略    class ListBox : VisualComponent    {        public override void Display()        {            Console.WriteLine("显示列表框!");        }    }    class ComponentDecorator : VisualComponent    {        private VisualComponent component;  //维持对抽象构件类型对象的引用        //注入抽象构件类型的对象        public ComponentDecorator(VisualComponent component)        {            this.component = component;        }        public override void Display()        {            component.Display();        }    }    //其他类似省略    class ScrollBarDecorator : ComponentDecorator    {        public ScrollBarDecorator(VisualComponent component)            : base(component)        {        }        public override void Display()        {            this.SetScrollBar();            base.Display();        }        public void SetScrollBar()         {            Console.WriteLine("为构件增加滚动条!");        }    }    class Program    {        static void Main(string[] args)        {            VisualComponent component, componentSB, componentBB; //使用抽象构件定义            component = new Window(); //定义具体构件            componentSB = new ScrollBarDecorator(component); //定义装饰后的构件            componentBB = new BlackBorderDecorator(componentSB); //将装饰了一次之后的对象继续注入到另一个装饰类中,进行第二次装饰            componentBB.Display();            Console.Read();        }    }}

1.3模式分析

动机和意图

  • 如何在不改变一个对象本身功能的基础上给对象增加额外的新行为?

一般结构

  • 装饰模式包含4个角色:
  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,包含共有的业务方法
  • ConcreteComponent(具体构件):用于定义具体的构件对象,实现抽象构件中声明的方法
  • Decorator(抽象装饰类):用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用。
  • ConcreteDecorator(具体装饰类):负责向构件添加新的职责。

    装饰模式UML类图
    这里写图片描述

改进后的优点

  • 对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加

  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类

  • 可以对一个对象进行多次装饰
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类

现存的缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,在一定程度上影响程序的性能
  • 比继承更加易于出错,排错也更困难

优化空间
如果客户端希望单独调用具体装饰类新增的方法,而不想通过抽象构件中声明的方法来调用新增方法时将遇到一些麻烦?
透明装饰模式的设计难度较大(上边的就是透明模式),而且有时我们需要单独调用新增的业务方法。为了能够调用到新增方法,我们不得不用具体装饰类型来定义装饰之后的对象,而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式,也就是说,对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的。
举例菜鸟软件公司开发的OA系统中,采购单(PurchaseRequest)和请假条(LeaveRequest)等文件(Document)对象都具有显示功能,现在要为其增加审批、删除等功能,使用装饰模式进行设计。
这里写图片描述
Approver类继承了抽象装饰类Decorator的display()方法,同时新增了业务方法approve(),但这两个方法是独立的,没有任何调用关系。如果客户端需要分别调用这两个方法。

Document  doc; //使用抽象构件类型定义doc = new PurchaseRequest();Approver newDoc; //使用具体装饰类型定义newDoc = new Approver(doc);newDoc.display();//调用原有业务方法newDoc.approve();//调用新增业务方法

完整代码部分见下载源码。

注意事项
(1) 尽量保持装饰类的接口与被装饰类的接口相同,尽量使用透明模式
(2) 尽量保持具体构件类ConcreteComponent是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,我们可以通过装饰类对其进行扩展。
(3) 如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类。

适用场景
(1) 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
(2)当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时

举例:Word字体的装饰功能 字体本身是为了显示 但是可以变成新的颜色 变成新的字体 新的字号

实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1skItmUh 密码: 7igv

原创粉丝点击