在面向对象编程中有个重要的原则,里氏代换原则: 一个软件实体如果使用的是一个父类的话,那么一定适用其子类,而且它察觉不出父类对象与子类对象的区别。也就是说,在软件设计里面,把父类替换成它的子类,程序的行为没有变化。 简单的说,子类类型必须能替换掉它的父类类型。
就好像继承的概念,子类继承自父类,那么子类可以以父类的身份出现。有这样一个问题,在面向对象设计中,一个是鸟类,一个是企鹅类,如果鸟是可以飞得,企鹅不会飞,那么企鹅是鸟么?企鹅可以继承自鸟类么?
需要面向对象设计,那么意味着,子类拥有父类所以非private的属性和行为,鸟会飞,而企鹅不会,所以企鹅是鸟,但它不能继承自上面那个会飞的鸟类,抽象出一个更高的鸟类,然后分为会飞的鸟子类、不会飞的鸟子类,企鹅应该继承自不会飞的鸟子类。
这因为有了里氏代换原则,才使得继承复用成为可能, 只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也可以在父类的基础上增加新的行为。 正是由于子类的可替换性才使得父类类型模块在无需修改的情况下就能扩展,这是前面提到的,对扩展的开放,对修改的封闭(ocp原则)。
穿衣问题,要求写一个给人搭配不同服饰的系统,那种可以换各种各样衣服和裤子的服饰系统,如下图:
首先排除这样的结果设计,如果我需要新增加超人的服饰设计,又得更改Person类,很明显违背了开发-封闭原则(ocp,对扩展的开放,对修改的封闭)。其实把这些服饰类写成子类就好,代码结构:
如此,需要增加超人的装扮 ,只需要增加子类即可。不需要对已有的代码进行修改。但是这样还打不到最好,我们需要在控制器里面来开辟诸如"破球鞋"、“垮裤”等对象,将他们一个词一个词的显示出来,就好比是在众目睽睽下穿衣服。
对于这些,应当去优化它们。就可以用到装饰模式: 动态的给一个对象添加一些额外的职能,就增加功能来说,装饰模式比添加子类更加灵活。 无论是衣服、鞋子、裤子等,其实我们都可以把它理解为对Person的装饰,那么有下图结构:
代码:
Person类:
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject { @protected NSString *_name; } - (instancetype)initWithName:(NSString *)name; - (void)display; @end #import "ZYPerson.h" @implementation ZYPerson - (instancetype)initWithName:(NSString *)name { if (self = [super init]) { _name = name; } return self; } - (void)display { NSLog(@"装扮的%@:",_name); } @end
clothing类:
#import "ZYPerson.h" @interface ZYClothing : ZYPerson @property (nonatomic, strong) ZYPerson *decorate; - (instancetype)initWithDecorate:(ZYPerson *)decorate; @end #import "ZYClothing.h" @implementation ZYClothing - (instancetype)initWithDecorate:(ZYPerson *)decorate { if (self = [super init]) { _decorate = decorate; } return self; } - (void)display { if (self.decorate) { [self.decorate display]; } } @end
TShirts类:
#import "ZYClothing.h" @interface ZYTShirts : ZYClothing @end #import "ZYTShirts.h" @implementation ZYTShirts - (void)display { [super display]; NSLog(@"大衬衫"); } @end
Pants类:
#import "ZYClothing.h" @interface ZYPants : ZYClothing @end #import "ZYPants.h" @implementation ZYPants - (void)display { [super display]; NSLog(@"大裤衩"); } @end
Shoe类:
#import "ZYClothing.h" @interface ZYShoe : ZYClothing @end #import "ZYShoe.h" @implementation ZYShoe - (void)display { [super display]; NSLog(@"破鞋子"); } @end
viewController里面的代码:
#import "ViewController.h" #import "ZYPerson.h" #import "ZYClothing.h" #import "ZYTShirts.h" #import "ZYPants.h" #import "ZYShoe.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. ZYPerson *person = [[ZYPerson alloc] initWithName:@"晶童鞋"]; ZYClothing *clothing = [[ZYClothing alloc] initWithDecorate:person]; ZYTShirts *shirts = [[ZYTShirts alloc] init]; ZYPants *pants = [[ZYPants alloc] init]; ZYShoe *shoe = [[ZYShoe alloc] init]; //装扮过程,相当于在室内穿衣服,控制器并不知道它是怎么的顺序 shirts.decorate = clothing; pants.decorate = shirts; shoe.decorate = pants; [shoe display]; //第二次装扮 pants.decorate = clothing; shoe.decorate = pants; shirts.decorate = shoe; [shirts display]; } @end
运行效果图:
装饰模式总结:
我觉得装饰模式,是 为已有功能动态的添加更多功能的一种方法。 但是到底什么时候用它呢?
在本文的最初设计中,当系统需要添加新功能的时候,是 向旧的类中添加新的代码,这些新增的代码通常装饰了原有类的核心职能或主要行为。 这种设计方式问题在于, 他们在主类中增加了新的字段、新的方法、新的逻辑,从而增加了主类的负责度。而这些新加入的东西仅仅是为了满足一些在某种特定情况下才会执行的特殊行为的需求。
而装饰模式提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包含它所要装饰的对象,因此,当执行特殊行为时,在viewController里就可以根据需求有选择、按顺序的使用装饰功能包装对象了。
所以就有了上面的代码,我可以通过装饰,让person武装到牙齿,也可以只让他穿条内裤。
装饰模式的优点: