• 连载:面向对象葵花宝典:思想、技巧与实践(32)


    LSP是唯一一个以人名命名的设计原则,并且作者还是一个“女博士” 大笑

    =============================================================


    LSP,Liskov substitution principle,中文翻译为“里氏替换原则”。

     

    这是面向对象原则中唯一一个以人名命名的原则,尽管Liskov在中国的知名度没有UNIX的几位巨匠(Kenneth Thompson、Dennis Ritchie)、GOF四人帮那么响亮,但查一下资料,你会发现事实上Liskov也是非常牛的:2008年图灵奖获得者,历史上第一个女性计算机博士学位获得者。其具体资料能够在维基百科上查阅:http://en.wikipedia.org/wiki/Barbara_Liskov 

     

    言归正传,我们来看看LSP原则究竟是怎么一回事。

    LSP最原始的解释当然来源于Liskov女士了,她在1987年的OOPSLA大会上提出了LSP原则,1988年,她将文章发表在ACM的SIGPLAN Notices杂志上,当中详解了LSP原则:

    A type hierarchy is composed of subtypes and supertypes. The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype) plus something extra.What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

     

    英文比較长,看起来比較累,我们简单的翻译并归纳一下:

    1) 子类的对象提供了父类的全部行为,且加上子类额外的一些东西(能够是功能,也能够是属性);

    2) 当程序基于父类实现时,假设将子类替换父类而程序不须要改动,则说明符合LSP原则

     

    尽管我们略微翻译和整理了一下,但实际上还是非常拗口和难以理解。

    幸好还有Martin大师也认为这个不怎么通俗易懂,Robert Martin在1996年为《C++ Reporter》写了一篇题为《The The Liskov Substitution Principle》的文章,解释例如以下:

    Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

    翻译一下就是:函数使用指向父类的指针或者引用时,必须能够在不知道子类类型的情况下使用子类的对象

     

    Martin大师解释了一下,相对easy理解多了。但Martin大师还不满足,在2002年,Martin在他出版的《Agile   Software   Development   Principles   Patterns   and   Practices》一书中,又进一步简化为:

    Subtypes   must   be   substitutable   for   their   base   types。

    翻译一下就是:子类必须能替换成它们的父类

     

    经过Martin大师的两次翻译,我相信LSP原则本身已经解释得比較easy理解了,但问题的关键是:怎样满足LSP原则?或者更通俗的讲:什么情况下子类才干替换父类?

     

    我们知道,对于调用者来说(Liskov解释中提到的P),和父类交互无非就是两部分:调用父类的方法、得到父类方法的输出,中间的处理过程,P是无法知道的。

     

    也就是说,调用者和父类之间的联系体如今双方面:函数输入,函数输出。具体例如以下图:

     

    有了这个图之后,怎样做到LSP原则就清晰了:

    1) 子类必须实现或者继承父类全部的公有函数,否则调用者调用了一个父类中有的函数,而子类中没有,执行时就会出错;

    2) 子类每一个函数的输入參数必须和父类一样,否则调用父类的代码不能调用子类;

    3) 子类每一个函数的输出(返回值、改动全局变量、插入数据库、发送网络数据等)必须不比父类少,否则基于父类的输出做的处理就没法完毕。

     

    有了这三条原则后,就能够非常方便的推断类设计是否符合LSP原则了。须要注意的是第3条的关键是“不比父类少”,也就是说能够比父类多,即:父类的输出是子类输出的子集

     

    有的朋友看到这三条原则可能有点纳闷:这三条原则一出,那子类还有什么差别哦,岂不都是一样的实现了,那还会有不同的子类么?

     

    事实上假设细致研究这三条原则,就会发现当中仅仅是约定了输入输出,而并没有约束中间的处理过程。比如:相同一个写数据库的输出,A类能够是读取XML数据然后写入数据库,B类能够是从其他数据库读取数据然后本地的数据库,C类能够是通过分析业务日志得到数据然后写入数据库。这3个类的处理过程都不一样,但最后都写入数据到数据库了。

     

    LSP原则最经典的样例就是“长方形和正方形”这个样例。从数学的角度来看,正方形是一种特殊的长方形,但从面向对象的角度来观察,正方形并不能作为长方形的一个子类。原因在于对于长方形来说,设定了宽高后,面积 = 宽 * 高;但对于正方形来说,设定高同一时候就设定了宽,设定宽就同一时候设定了高,最后的面积并非等于我们设定的 宽 * 高,而是等于最后一次设定的宽或者高的平方。

    具体代码样比例如以下:

    Rectangle.java

    package com.oo.java.principles.lsp;
    
    /**
     * 长方形
     */
    public class Rectangle {
    
        protected int _width;
        protected int _height;
        
        /**
         * 设定宽
         * @param width
         */
        public void setWidth(int width){
            this._width = width;
        }
        
        /**
         * 设定高
         * @param height
         */
        public void setHeight(int height){
            this._height = height;
        }
        
        /**
         * 获取面积
         * @return
         */
        public int getArea(){
            return this._width * this._height;
        }
    }
    

    Square.java

    package com.oo.java.principles.lsp;
    
    /**
     * 正方形
     */
    public class Square extends Rectangle {
        
        /**
         * 设定“宽”,与长方形不同的是:设定了正方形的宽,同一时候就设定了正方形的高
         */
        public void setWidth(int width){
            this._width = width;
            this._height = width;
        }
        
        /**
         * 设定“高”,与长方形不同的是:设定了正方形的高,同一时候就设定了正方形的宽
         */
        public void setHeight(int height){
            this._width = height;
            this._height = height;
        }
    
    }
    

    UnitTester.java

    package com.oo.java.principles.lsp;
    
    public class UnitTester {
    
        public static void main(String[] args){
            Rectangle rectangle = new Rectangle();
            rectangle.setWidth(4);
            rectangle.setHeight(5);
            
            //例如以下assert推断为true
            assert( rectangle.getArea() == 20);
            
            rectangle = new Square();
            rectangle.setWidth(4);
            rectangle.setHeight(5);
            
            //例如以下assert推断为false,断言失败,抛出java.lang.AssertionError
            assert( rectangle.getArea() == 20);
        }
    }
    

    上面这个样例同一时候也给出了一个推断子类是否符合LSP的取巧的方法,即:针对父类的单元測试用例,传入子类是否也能够測试通过。假设測试能够通过,则说明符合LSP原则,否则就说明不符合LSP原则


  • 相关阅读:
    C#几种截取字符串的方法小结
    KinSlideshow参数设置说明
    WinForm程序中两份mdf文件问题的解决
    全国省市数据库
    ASP.NET项目中使用CKEditor +CKFinder 实现上传图片
    mht文件无法打开的解决办法
    Non-parametric tests
    Plot transpant lines in Matleb 在Matlab中绘制透明线条
    Which HRV method to use: FFT or Autoregressive?
    SPM How-tos SPM预处理及统计分析指南
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/3778484.html
Copyright © 2020-2023  润新知