结构型模式--装饰模式

下面先用java,然后用Objective-C行对装饰模式的讲解:

对于java的装饰模式讲解和使用比较详细和难度有点偏高,而对于Objective-C的装饰模式讲解和使用方面比较简单,而且和java的装饰模式略有差异,差异在于:java的装饰模式是通过面向抽象来编程的,而Objective-C就没有用到抽象或则协议这个应用,而是直接用对象引用被装饰类来扩展对象的方法。我个人觉得这主要原因应该是java语言的实用场景往往是结构复杂的大项目,涉及的问题领域比较广,而Objective-C应用领域仅仅是IOS前端app的开发,对设计模式结构上设计可能没有java那么高的要求。

(Java)

尽管目前房价依旧很高,但还是阻止不了大家对新房的渴望和买房的热情。如果大家买的是毛坯房,无疑还有一项艰巨的任务要面对,那就是装修。对新房进 行装修并没有改变房屋用于居住的本质,但它可以让房子变得更漂亮、更温馨、更实用、更能满足居家的需求。在软件设计中,我们也有一种类似新房装修的技术可 以对已有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象,使得对象具有更加强大的功能。这种技术对应于一种被称之为装饰模式的设计模 式,本章将介绍用于扩展系统功能的装饰模式。

图形界面构件库的设计

Sunny 软件公司基于面向对象技术开发了一套图形界面构件库 VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特效显示效果,如 带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能,如图所示:

如何提高图形界面构件库性的可扩展性并降低其维护成本是 Sunny 公司开发人员必须面对的一个问题。

Sunny 软件公司的开发人员针对上述要求,提出了一个基于继承复用的初始设计方案,其基本结构如图所示:

图中,在抽象类 Component 中声明了抽象方法 display(),其子类 Window、TextBox 等实现了 display() 方法,可以显示最简单的控件,再通过它们的子类来对功能进行扩展,例如,在 Window 的子类 ScrollBarWindow、BlackBorderWindow 中对 Window 中的 display() 方法进行扩展,分别实现带滚动条和带黑色边框的窗体。仔细分析该设计方案,我们不难发现存在如下几个问题:

(1)系统扩展麻烦,在某些编程语言中无法实现。如果用户需要一个既带滚动条又带黑色边框的窗体,在图中通过增 加了一个新的类 ScrollBarAndBlackBorderWindow 来实现,该类既作为 ScrollBarWindow 的子类,又作为 BlackBorderWindow 的子类;但现在很多面向对象编程语言,如 Java、C# 等都不支持多重类继承,因此在这些语言中无法通过继承来实现对来自多个父类的方法的重用。此外,如果还需要扩展一项功能,例如增加一个透明窗体类 TransparentWindow,它是 Window 类的子类,可以将一个窗体设置为透明窗体,现在需要一个同时拥有三项功能(带滚动条、带黑色边框、透明)的窗体,必须再增加一个类作为三个窗体类的子类, 这同样在 Java 等语言中无法实现。系统在扩展时非常麻烦,有时候甚至无法实现。

(2)代码重复。从图中我们可以看出,不只是窗体需要设置滚动条,文本框、列表框等都需要设置滚动条,因此在 ScrollBarWindow、ScrollBarTextBox和ScrollBarListBox等类中都包含用于增加滚动条的方法 setScrollBar(),该方法的具体实现过程基本相同,代码重复,不利于对系统进行修改和维护。

(3)系统庞大,类的数目非常多。如果增加新的控件或者新的扩展功能系统都需要增加大量的具体类,这将导致系统 变得非常庞大。在图中,3 种基本控件和 2 种扩展方式需要定义 9 个具体类;如果再增加一个基本控件还需要增加 3 个具体类;增加一种扩展方式则需要增加更多的类,如果存在 3 种扩展方式,对于每一个控件而言,需要增加7个具体类,因为这 3 种扩展方式存在 7 种组合关系(大家自己分析为什么需要 7 个类?)。

总之,图不是一个好的设计方案,怎么办?如何让系统中的类可以进行扩展但是又不会导致类数目的急剧增加?不用着急,让我们先来分析为什么这个设计方案会存在如此多的问题。根本原因在于复用机制的不合理, 图采用了继承复用,例如在 ScrollBarWindow 中需要复用 Window 类中定义的 display() 方法,同时又增加新的方法 setScrollBar(),ScrollBarTextBox 和 ScrollBarListBox 都必须做类似的处理,在复用父类的方法后再增加新的方法来扩展功能。根据“合成复用原则”,在实现功能复用时,我们要多用关联,少用继承 因此我们可以换个角度来考虑,将 setScrollBar() 方法抽取出来,封装在一个独立的类中,在这个类中定义一个 Component 类型的对象,通过调用 Component的 display() 方法来显示最基本的构件,同时再通过 setScrollBar() 方法对基本构件的功能进行增强。由于 Window、ListBox 和 TextBox 都是 Component 的子类,根据“里氏代换原则”,程序在运行时,我们只要向这个独立的类中注入具体的 Component 子类的对象即可实现功能的扩展。这个独立的类一般称为装饰器(Decorator)或装饰类,顾名思义,它的作用就是对原有对象进行装饰,通过装饰来扩展 原有对象的功能。

装饰类的引入将大大简化本系统的设计,它也是装饰模式的核心,下面让我们正式进入装饰模式的学习。

 

装饰模式概述

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本 身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。

装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

装饰模式定义如下:

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在装饰模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类,装饰模式结构如图所示:

在装饰模式结构图中包含如下几个角色:

  • Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

  • ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计,其典型代码如下所示:

 1 class Decorator implements Component
 2 {
 3        private Component component;  //维持一个对抽象构件对象的引用
 4        public Decorator(Component component)  //注入一个抽象构件类型的对象
 5        {
 6               this.component=component;
 7        }
 8 
 9        public void operation()
10        {
11               component.operation();  //调用原有业务方法
12        }
13 }

在抽象装饰类 Decorator 中定义了一个 Component 类型的对象 component,维持一个对抽象构件对象的引用,并可以通过构造方法或 Setter 方法将一个 Component 类型的对象注入进来,同时由于 Decorator 类实现了抽象构件Component 接口,因此需要实现在其中声明的业务方法 operation(),需要注意的是在 Decorator 中并未真正实现 operation() 方法,而只是调用原有 component 对象的 operation() 方法,它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。

在 Decorator 的子类即具体装饰类中将继承 operation() 方法并根据需要进行扩展,典型的具体装饰类代码如下:

 1 class ConcreteDecorator extends Decorator
 2 {
 3        public ConcreteDecorator(Component  component)
 4        {
 5               super(component);
 6        }
 7 
 8        public void operation()
 9        {
10               super.operation();  //调用原有业务方法
11               addedBehavior();  //调用新增业务方法
12        }
13 
14      //新增业务方法
15        public  void addedBehavior()
16        {    
17          ……
18     }
19 }

在具体装饰类中可以调用到抽象装饰类的 operation() 方法,同时可以定义新的业务方法,如 addedBehavior()。

由于在抽象装饰类 Decorator 中注入的是 Component 类型的对象,因此我们可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;此外,我们还可以将一个已经装饰过的 Decorator 子类的对象再注入其中进行多次装饰,从而对原有功能的多次扩展。

思考

能否在装饰模式中找出两个独立变化的维度?试比较装饰模式和桥接模式的相同之处和不同之处?

(这个问题我还没有去思考,因为我还没学习桥接模式)

完整解决方案

为了让系统具有更好的灵活性和可扩展性,克服继承复用所带来的问题,Sunny 公司开发人员使用装饰模式来重构图形界面构件库的设计,其中部分类的基本结构如图所示:

在图中,Component 充当抽象构件类,其子类 Window、TextBox、ListBox 充当具体构件类,Component 类的另一个子类 ComponentDecorator 充当抽象装饰类,ComponentDecorator 的子类 ScrollBarDecorator 和 BlackBorderDecorator 充当具体装饰类。完整代码如下所示:

 1 //抽象界面构件类:抽象构件类,为了突出与模式相关的核心代码,对原有控件代码进行了大量的简化
 2 abstract class Component
 3 {
 4        public  abstract void display();
 5 }
 6 
 7 //窗体类:具体构件类
 8 class Window extends Component
 9 {
10        public  void display()
11        {
12               System.out.println("显示窗体!");
13        }
14 }
15 
16 //文本框类:具体构件类
17 class TextBox extends Component
18 {
19        public  void display()
20        {
21               System.out.println("显示文本框!");
22        }
23 }
24 
25 //列表框类:具体构件类
26 class ListBox extends Component
27 {
28        public  void display()
29        {
30               System.out.println("显示列表框!");
31        }
32 }
33 
34 //构件装饰类:抽象装饰类
35 class ComponentDecorator extends Component
36 {
37        private Component component;  //维持对抽象构件类型对象的引用
38 
39        public ComponentDecorator(Component  component)  //注入抽象构件类型的对象
40        {
41               this.component = component;
42        }
43 
44        public void display()
45        {
46               component.display();
47        }
48 }
49 
50 //滚动条装饰类:具体装饰类
51 class ScrollBarDecorator extends  ComponentDecorator
52 {
53        public ScrollBarDecorator(Component  component)
54        {
55               super(component);
56        }
57 
58        public void display()
59        {
60               this.setScrollBar();
61               super.display();
62        }
63 
64        public  void setScrollBar()
65        {
66               System.out.println("为构件增加滚动条!");
67        }
68 }
69 
70 //黑色边框装饰类:具体装饰类
71 class BlackBorderDecorator extends  ComponentDecorator
72 {
73        public BlackBorderDecorator(Component  component)
74        {
75               super(component);
76        }
77 
78        public void display()
79        {
80               this.setBlackBorder();
81               super.display();
82        }
83 
84        public  void setBlackBorder()
85        {
86               System.out.println("为构件增加黑色边框!");
87        }
88 }

编写如下客户端测试代码:

 1 class Client
 2 {
 3        public  static void main(String args[])
 4        {
 5               Component component,componentSB;  //使用抽象构件定义
 6               component = new Window(); //定义具体构件
 7               componentSB = new  ScrollBarDecorator(component); //定义装饰后的构件
 8               componentSB.display();
 9        }
10 }

编译并运行程序,输出结果如下:

为构件增加滚动条!
显示窗体!

在客户端代码中,我们先定义了一个 Window 类型的具体构件对象 component,然后将 component 作为构造函数的参数注入到具体装饰类 ScrollBarDecorator 中,得到一个装饰之后对象 componentSB,再调用 componentSB 的 display() 方法后将得到一个有滚动条的窗体。如果我们希望得到一个既有滚动条又有黑色边框的窗体,不需要对原有类库进行任何修改,只需将客户端代码修改为如下所示:

 1 class Client
 2 {
 3        public  static void main(String args[])
 4        {
 5               Component  component,componentSB,componentBB; //全部使用抽象构件定义
 6               component = new Window();
 7               componentSB = new  ScrollBarDecorator(component);
 8               componentBB = new  BlackBorderDecorator(componentSB); //将装饰了一次之后的对象继续注入到另一个装饰类中,进行第二次装饰
 9               componentBB.display();
10        }
11 }

编译并运行程序,输出结果如下:

为构件增加黑色边框!
为构件增加滚动条!
显示窗体!

我们可以将装饰了一次之后的 componentSB 对象注入另一个装饰类 BlackBorderDecorator 中实现第二次装饰,得到一个经过两次装饰的对象 componentBB,再调用 componentBB 的 display() 方法即可得到一个既有滚动条又有黑色边框的窗体。

如果需要在原有系统中增加一个新的具体构件类或者新的具体装饰类,无须修改现有类库代码,只需将它们分别作为抽象构件类或者抽象装饰类的子类即可。 与图所示的继承结构相比,使用装饰模式之后将大大减少了子类的个数,让系统扩展起来更加方便,而且更容易维护,是取代继承复用的有效方式之一。

 

透明装饰模式与半透明装饰模式

装饰模式虽好,但存在一个问题。如果客户端希望单独调用具体装饰类新增的方法,而不想通过抽象构件中声明的方法来调用新增方法时将遇到一些麻烦,我们通过一个实例来对这种情况加以说明:

在 Sunny 软件公司开发的 Sunny OA 系统中,采购单(PurchaseRequest)和请假条(LeaveRequest)等文件(Document)对象都具有显示功能,现在要为其增加审批、删除等功能,使用装饰模式进行设计。

我们使用装饰模式可以得到如图所示结构图:

在图中,Document充当抽象构件类,PurchaseRequest 和 LeaveRequest 充当具体构件类,Decorator 充当抽象装饰类,Approver 和 Deleter 充当具体装饰类。其中 Decorator 类和 Approver 类的示例代码如下所示:

 1 //抽象装饰类
 2 class Decorator implements  Document
 3 {
 4       private Document  document;
 5 
 6       public Decorator(Document  document)
 7       {
 8              this.  document = document;
 9       }
10 
11       public void display()
12       {
13              document.display();
14       }
15 }
16 
17 //具体装饰类
18 class Approver extends  Decorator
19 {
20       public Approver(Document document)
21       {
22              super(document);
23              System.out.println("增加审批功能!");
24       }
25 
26       public void approve()
27       {
28              System.out.println("审批文件!");
29       }
30 }

大家注意,Approver 类继承了抽象装饰类 Decorator 的 display() 方法,同时新增了业务方法 approve(),但这两个方法是独立的,没有任何调用关系。如果客户端需要分别调用这两个方法,代码片段如下所示:

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

如果newDoc也使用Document类型来定义,将导致客户端无法调用新增业务方法approve(),因为在抽象构件类Document中没有对approve()方法的声明。也就是说,在客户端无法统一对待装饰之前的具体构件对象和装饰之后的构件对象。

在实际使用过程中,由于新增行为可能需要单独调用,因此这种形式的装饰模式也经常出现,这种装饰模式被称为半透明(Semi- transparent)装饰模式,而标准的装饰模式是透明(Transparent)装饰模式。下面我们对这两种装饰模式进行较为详细的介绍:

透明装饰模式

在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型。对于客户端而言,具体构件对象和具体装饰对象没有任何区别。也就是应该使用如下代码:

1 Component  c, c1; //使用抽象构件类型定义对象
2 c = new ConcreteComponent();
3 c1 = new ConcreteDecorator (c);
而不是使用如下代码:
1 ConcreteComponent c; //使用具体构件类型定义对象
2 c = new ConcreteComponent();

1 ConcreteDecorator c1; //使用具体装饰类型定义对象
2 c1 = new ConcreteDecorator(c);

在12.3节图形界面构件库的设计方案中使用的就是透明装饰模式,在客户端中存在如下代码片段:

1 ……
2 Component component,componentSB,componentBB; //全部使用抽象构件定义
3 component = new Window();
4 componentSB = new ScrollBarDecorator(component);
5 componentBB = new BlackBorderDecorator(componentSB);
6 componentBB.display();
7 ……

使用抽象构件类型 Component 定义全部具体构件对象和具体装饰对象,客户端可以一致地使用这些对象,因此符合透明装饰模式的要求。

透明装饰模式可以让客户端透明地使用装饰之前的对象和装饰之后的对象,无须关心它们的区别,此外,还可以对一个已装饰过的对象进行多次装饰,得到更 为复杂、功能更为强大的对象。在实现透明装饰模式时,要求具体装饰类的 operation() 方法覆盖抽象装饰类的 operation() 方法,除了调用原有对象的 operation() 外还需要调用新增的 addedBehavior() 方法来增加新行为,

半透明装饰模式

透明装饰模式的设计难度较大,而且有时我们需要单独调用新增的业务方法。为了能够调用到新增方法,我们不得不用具体装饰类型来定义装饰之后的对象, 而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式,也就是说,对于客户端而言,具体构件类型无须关心,是透明的;但是具体 装饰类型必须指定,这是不透明的。如本节前面所提到的文件对象功能增加实例,为了能够调用到在 Approver 中新增方法 approve(),客户端代码片段如下所示:

1 ……
2 Document  doc; //使用抽象构件类型定义
3 doc = new PurchaseRequest();
4 Approver newDoc; //使用具体装饰类型定义
5 newDoc = new Approver(doc);
6 ……

半透明装饰模式可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便;但是其最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端 需要有区别地对待装饰之前的对象和装饰之后的对象。在实现半透明的装饰模式时,我们只需在具体装饰类中增加一个独立的 addedBehavior() 方法来封装相应的业务处理,由于客户端使用具体装饰类型来定义装饰后的对象,因此可以单独调用 addedBehavior() 方法来扩展系统功能。

思考

为什么半透明装饰模式不能实现对同一个对象的多次装饰?

装饰模式注意事项

在使用装饰模式时,通常我们需要注意以下几个问题:

(1) 尽量保持装饰类的接口与被装饰类的接口相同,这样,对于客户端而言,无论是装饰之前的对象还是装饰之后的对象都可以一致对待。这也就是说,在可能的情况下,我们应该尽量使用透明装饰模式。

(2) 尽量保持具体构件类 ConcreteComponent 是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,我们可以通过装饰类对其进行扩展。

(3) 如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类。如图所示:

装饰模式总结

装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体 装饰类。在软件开发中,装饰模式应用较为广泛,例如在 JavaIO 中的输入流和输出流的设计、javax.swing 包中一些图形界面构件功能的增强等地方都运用了装饰模式。

主要优点

装饰模式的主要优点如下:

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

(2) 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。

(3) 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。

(4) 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

主要缺点

装饰模式的主要缺点如下:

(1) 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。

(2) 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景

在以下情况下可以考虑使用装饰模式:

(1) 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的 扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如 Java 语言中的 final 类)。

练习

Sunny 软件公司欲开发了一个数据加密模块,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为 高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次 加密。试使用装饰模式设计该多重加密系统。

(本练习还可以用 策略模式 进行设计)

(Objective-C)

 下面的GamePad类是原始类(原来开始的类,我个人的说法,请不要见怪,嘻嘻),对于GamePad类我们只需要知道它内部提供的所有的方法,但是可以不用知道方法细节怎么实现,我们先创建一个装饰类GamePadDecorator类,在这个装饰类中:如下图,我们先创建一个原始类的实例对象,实例化对象放置在装饰类的初始化方法中,然后写出所有和原始类内部方法同名的方法作为装饰原始类方法,并把调用原始类方法放置在这些同名的装饰类方法中。这样就实现了一个装饰类。

 

 

 

 

 

 

 

 

 

 

 

然后拓展这个装饰类,我们就可以通过继承这个类,然后在这个继承类中添加需要拓展的方法(如下图中的cheat方法就是新拓展的方法,请看cheat方法的具体实现):
以上就是Objective-C通过装饰模式拓展一个类的方法,下面还需要不得不提到的就是Coco框架提供了一种拓展类方法的方式就是:Category的使用。

Category是Coco框架提供的装饰模式的一种方式,但是它和真正的装饰模式却有着细微的区别,这些细微的区别体现在如何给Category增加属性的时候以及Category重写被装饰对象方法的一些区别。这个区别却也是一个鲜为人知的细节。那就是如果在Category的类中你要是重写被装饰的对象的方法,即使你在使用原始类的过程中,没有引用这个Category类,你使用那个原始类中的被重写的方法,实现的结果确实Category里重写的方法所实现的结果。这个细节很可能会成为你一不小心犯下的影响整个项目的错误。

而相比较前面的装饰模式的使用来扩展类的方法,其中需要扩展的时候,是通过继承装饰类来扩展的,这样,你在子类中重写了父类的方法,然而你不引用(import)子类,使用父类的那个被重写的方法,实现的结果也不会出现子类的重写方法实现的结果。
总之,站在IOS开发的角度来总结这两种情况:
1、当不需要重写父类方法,然后却需要拓展父类方法的时候,请使用Category。
2、当需要重写父类的方法,然后又需要拓展父类方法的时候,请使用正儿八经的装饰模式。
学习来自《极客学院》
最后,关于装饰者模式,还需要继续提高的还可以观看《极客学院》的java的《设计模式之装饰者模式》
>> 留言时间:2017年2月10日
今天尝试在“柚子网”IOS项目开发中使用装饰模式来解耦一个代码耦合度比较高的模块,这个模块是“企业端发布和编辑职位”,该模块已经完成,代码行数是1700多行,很多控件,很多逻辑都写在此模块中。该模块载体是MVC模式中的Controller控制器,而IOS的控制器Controller中依赖于Cocoa框架的UIViewController,在使用装饰模式的过程中,收到了限制。
    思路:打算将该模块的Controller,采用策略模式进行解耦成两个小模块:“发布职位”和“编辑职位”。装饰模式相对于直接继承的好处是,能够将需要添加的行为或者对象作为装饰品将原来固有的基础行为或者对象进行添加和包装。当不需要这个装饰的时候,直接不包装即可。而且不同的装饰模式可以独立耦合开来。但是,在应用在Controller对象中,却受到Cocoa模块提供的UIViewController的一些已有的属性的限制,比如view,这里新的VIewController装饰对象需要拥有旧的ViewController被装饰对象的基础属性,因为其生命周期的执行并不是代码手动调用的,而是Cocoa框架自动调用的。还有原有的ViewController中的可点击控件监听事件依赖self(当前控制器对象),而装饰模式的设计就是装饰对象和被装饰对象是相互独立的对象,这就导致很多被装饰对象中的点击监听事件到了装饰对象中就无效了,只能在装饰对象中,重写监听事件的方法,这样还不如直接继承模式来的更方便。
    综上所述:
        1、在Cocoa提供的ViewController对象中,本身很多生命周期只能继承中使用,所以这里不适合对Controller进行装饰模式的使用。
        2、装饰模式是结构模式,ViewController的生命周期的逻辑是Cocoa框架固有不可改变的结构。所以在这里不适合采用装饰模式,所以,装饰模式就比较试用于自定义设计的模块并且和其他模块耦合度不高的模块。
        3、设计具有场景实用性,即使有的场景逻辑用装饰模式可以达到理想的好处,但是并不是所有的场景都能试用,要从实际出发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262785.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ArcGIS.Server.9.2.DotNet自带例子分析(三、一)

目的: 1.arcgis server9.2 ADF的AddGraphics。 准备工作: 1.用ArcGis Server Manager或者ArcCatalog发布一个叫world的Map Service,并且把这个Service启动起来。 2.找到DeveloperKit\SamplesNET\Server\Web_Applications目录下的Common_AddGraphicsCShar…

aspose将datatable导出excel 比自己拼好的多 Bug少-。.net

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Data; using Aspose.Cells; /// <summary> ///OutFileDao 的摘要说明 /// </summary> publicclass OutFileDao { public OutFileDa…

整理:Android apk 框架 布局 集锦

2019独角兽企业重金招聘Python工程师标准>>> 看到好的技术教程就想分享一下&#xff0c;不喜勿喷&#xff01;谢谢配合&#xff0c;仅供菜鸟学习研究(^o^)/~ 友情推荐《爱加密》Android apk加密保护视频教程剪辑&#xff1a;http://www.ijiami.cn/Video?v3 Andro…

IE8不兼容你的网页 怎么办? - 简单开启兼容模式

自从用了IE8 整个世界都变了形.   呵呵,问题没那么严重,如果你的网站还来不及修改以适合IE8访问的时候,咱们可以通过非常简单的方法,加几行代码就可以让访问的IE8自动调用IE7的渲染模式[/b],这样可以保证最大的兼容性,方法如下:   只需要在页面中加入如下HTTP meta-tag:  …

springboot打war包汇总

概述 第一次用maven工具打war包&#xff0c;出现各种各样的问题&#xff0c;做个问题记录方便下次查看 maven 一开始用的maven是springboot默认的&#xff0c;在.m2下&#xff0c;要打包时才发现没有mvn指令。索性自己就重新装个maven&#xff0c;去官网下载&#xff0c;我安装…

大数据初探——Hadoop历史

Hadoop是一个开源的分布式框架&#xff0c;是Apache下的一个开源项目。Hadoop运行可以在成千上万个普通机器节点组成的集群上&#xff0c;通过分布式的计算模型和存储模型来处理大数据集。Hadoop具有高容错性、工作在普通的机器节点上扩展性强等众多的优点&#xff0c;是企业选…

BMP格式图像的显示

使用多文档编程 也可以使用单文档编程 建立一个DIB图像的显示类 ImageDib 成员变量&#xff1a; 4个指针&#xff1a; LPBYTE m_lpDib; //指向DIB的指针    LPBITMAPINFOHEADER m_lpBmpInfoHead; //图像信息头指针 LPRGBQUAD m_lpColorTable; //图像颜色表指针 …

深入A*算法

一、前言 在这里我将对A*算法的实际应用进行一定的探讨&#xff0c;并且举一个有关A*算法在最短路径搜索的例子。 二、A*算法的程序编写原理 A*算法是最好优先算法的一种。只是有一些约束条件而已。我们先来看看最好优先算法是如何编写的吧。 如图有如下的状态空间&#xff1a;…

IOS中NSUserDefaults的用法

2019独角兽企业重金招聘Python工程师标准>>> IOS中NSUserDefaults的用法&#xff08;轻量级本地数据存储&#xff09; 分类&#xff1a; IOS开发 Object&#xff0d;C编程语言2012-09-09 10:58 65223人阅读 评论(13) 收藏 举报 存储iosfloatinterfaceintegerdate NS…

【Oracle 学习笔记】Day 1 常用函数整理(转换、DeCode),表的外键

select Convert(varchar,Convert(money,TaxExValue),1) from A--Result 2,794.87 58,119.66 1,367.52 对于SQL Server来说&#xff0c;进行金额的转换&#xff0c;可以按照上面的操作那样&#xff0c;会自动将金额处理为两位小数&#xff0c;并用逗号分隔小数点前面的数字。 当…

LOJ bitset+分块 大内存毒瘤题

题面 $ solution: $ 真的没有想到可以用分块。 但是可以发现一个性质&#xff0c;每个询问只关心这个点最后一次赋值操作&#xff0c;和这个赋值操作后的所有取 $ min $ 操作。这个感觉很有用&#xff0c;但是真的很难让人想到低于 $ n\times m $ 的做法。基于 $ DAG $ 的数据结…

Web开发编程实用手册

不要被这个名字吓到。这本手册&#xff0c;真的很实用。你能猜猜它有多少页么&#xff1f;只有62页&#xff0c;比起那些砖头书来&#xff0c;这本可以说是苗条得不能再苗条了。现在卓越搞活动&#xff0c;购买电子工业出版社图书&#xff0c;凡购买专题内图书满69元&#xff0…

C# 配置文件 自定義結點

1. 對於配置自定義結點&#xff0c;需要繼承ConfigurationSection類。 UrlsSection : ConfigurationSection 2. 配置文件中&#xff0c;需要如下引用&#xff1a; View Code <configSections><section name"orders" type"WebApplication4.UrlsS…

Stream流思想和常用方法

一、IO流用于读写&#xff1b;Stream流用于处理数组和集合数据&#xff1b; 1、传统集合遍历&#xff1a; 2、使用Stream流的方式过滤&#xff1a; 其中&#xff0c;链式编程&#xff08;返回值就是对象自己&#xff09;中&#xff0c;filter使用的是Predicate函数式接口&#…

Stream流方法引用

一、对象存在&#xff0c;方法也存在&#xff0c;双冒号引用 1、方法引用的概念&#xff1a; 使用实例&#xff1a; 1.1先定义i一个函数式接口&#xff1a; 1.2定义一个入参参数列表有函数式接口的方法&#xff1a; 1.3调用这个入参有函数式接口的方法&#xff1a; lambda表达式…

为什么要在定义抽象类时使用abstract关键字

本文为原创&#xff0c;如需转载&#xff0c;请注明作者和出处&#xff0c;谢谢&#xff01;众所周之&#xff0c;在任何面向对象的语言中&#xff08;包括Java、C#&#xff09;&#xff0c;在定义抽象类时必须使用abstract关键字。虽然这已经习已为常了&#xff0c;但实际上ab…

pku 3252 Round Numbers 组合数学 找规律+排列组合

http://poj.org/problem?id3252 看了discuss里面的解题报告才明白的&#xff0c;这个解题报告太强大了&#xff1a;http://poj.org/showmessage?message_id158333不多讲已经很详细了&#xff0c;不明白多看几遍肯定会明白的。 注意这里的公式c(i,j) c(i - 1,j -1) c(i - 1…

《The Coaching Booster》问与答

由Shirly Ronen-Harel和Jens R. Woinowski 编写的《The Coaching Booster》 一书探讨了不同的教练方法和实践&#xff0c;并介绍了一种教练框架&#xff0c;支持教练帮助人们达到他们的目标。\InfoQ 采访了Shirly Ronen-Harel 和 Jens R. Woinowski&#xff0c;谈论了他们的书为…

反射应用和获取Class对象的三种方式

一、写一个“框架”&#xff0c;可以创建任何对象运行任何方法 1、配置文件 2、使用类加载器ClassLoader&#xff0c;Properties集合是可以和IO流结合使用完成读取和写入数据的集合&#xff0c;方法参数列表是IO流&#xff1b; Class类的静态方法forName()创建Class对象&#x…

8 种有趣的用于 Web 品牌的动物

当 Mozilla 推出最新移动浏览器 Fennec 时&#xff0c;很多人需要借助 Wikipedia 才知道 Fennec 是什么意思&#xff0c;Web 2.0 产品以各种古怪的命名著称&#xff0c;要么非常拗口&#xff0c;象 Flickr&#xff0c;要么很 cute&#xff0c;象 Google&#xff0c;或者干脆不知…