iOS 面向对象六大设计原则(三)里式替换原则

吾所成之事,不可逆也。

iOS 面向对象设计原则全集

前言

接上篇 iOS 面向对象六大设计原则(二)开闭原则

正文

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述如下:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义:

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

里氏替换原则是实现开闭原则的重要方式之一。由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

在使用里氏代换原则时需要注意如下几个问题:

  • 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。

  • 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

  • Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。OC语言好像不会检查,OC有动态语言的特性,其动态类型就是在运行的时候才去判断具体的是哪个类。


小结

说了那么多,通俗点讲:

所有可以使用基类的地方,一定可以使用子类且不会引入错误。

嗯~~还是抽象的描述,那整点具体的。

举例

在IM通信中,会有消息实体,有的消息是纯文本,有的是带有图片的,有的是带有链接的等。那么把纯文本的消息实体Message作为基类,一切从简,如下:

  • .h
@interface Message : NSObject

@property (nonatomic, copy) NSString *text;

@end
  • m
@implementation Message

@end

这时候有个带图片的实体,产品要求,图片消息里面的文本不能有空格(别问我问什么),那么可以整个ImageMessage类,如下

  • .h
@interface ImageMessage : Message

@property (nonatomic, strong) UIImage *image;

@end
  • .m
@implementation ImageMessage

- (void)setText:(NSString *)text {
    text = [text stringByReplacingOccurrencesOfString:@" " withString:@""];
    super.text = text;
}

@end

就功能而言,这种写法是满足产品需求的,但是如果在Message出现的地方,你用ImageMessage替换的话,就会导致用户不能发带有空格的文本了,这就违反了里式替换原则:所有可以使用基类的地方,一定可以使用子类且不会引入错误。

可做以下简单修改:

  • .h
@interface LSPImageMessage : Message

@property (nonatomic, strong) UIImage *image;

/** 去除text内空格 */
- (void)checkText;

@end
  • .m
@implementation LSPImageMessage

- (void)checkText {
    self.text = [self.text stringByReplacingOccurrencesOfString:@" " withString:@""];
}

@end

Demo传送门

总结

  • 里式替换原则:子类必须完好的(没有重写使之功能改变)拥有父类所有方法。

里式替换原则是实现开闭原则的必要条件。要想实现开闭原则,必须遵循里式替换原则,但是实现了开闭原则不一定遵循里式替换原则。


以下是修改部分,感谢 灬Blue


  • 里氏替换原则是实现开闭原则的重要方式之一。

  • 它避免了继承中重写父类造成的可复用性变差的缺点。

  • 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

补充

如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。

感谢 面向对象设计原则之里氏代换原则

你可能感兴趣的:(iOS 面向对象六大设计原则(三)里式替换原则)