zoukankan      html  css  js  c++  java
  • 连载:面向对象葵花宝典:思想、技巧与实践(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原则


  • 相关阅读:
    November 07th, 2017 Week 45th Tuesday
    November 06th, 2017 Week 45th Monday
    November 05th, 2017 Week 45th Sunday
    November 04th, 2017 Week 44th Saturday
    November 03rd, 2017 Week 44th Friday
    Asp.net core 学习笔记 ( Area and Feature folder structure 文件结构 )
    图片方向 image orientation Exif
    Asp.net core 学习笔记 ( Router 路由 )
    Asp.net core 学习笔记 ( Configuration 配置 )
    qrcode render 二维码扫描读取
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/3790640.html
Copyright © 2011-2022 走看看