编写扩展性更好的程序——里氏替换原则
2016-01-24 14:19
169 查看
什么是里氏替换原则
里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述如下:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义:
里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
在使用里氏代换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。
里氏替换原则的核心原理是抽象,抽象又依赖于继承这个特性,在OOP当中,继承的优缺点相当明显。
优点有一下几点:
(1) 代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性;
(2) 子类与父类基本相似,但又与父类有所区别;
(3) 提高代码的高扩展性。
继承的缺点:
(1) 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;
(2) 可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的属性和方法。
事物总是具有两面性,如何权衡利弊都是需要根据具体的情况来做出选择并加以处理的。里氏替换原则指导我们构建扩展性更好的软件系统。说了这么多我们还是通过代码来进一步来体会一下里氏替换原则的精髓吧。
Android源码中的里氏替换原则
我们知道Android的ViewGroup是一个容器,它提供了addView(View view)方法,这个方法需要一个View类型的参数,调用这个方法会向ViewGroup中添加一个View。那么这个View可以是TextView、Button等等,因为这些View的都是View的子类或间接子类。根据里氏替换原则,所有父类出现的地方都可以直接替换成子类。类似的设计在Android中随处可见。那么我们在看看我们平时的代码中是否也会用到里氏替换原则呢?其实这个在我们调用Android api的时候已经解除到很多了。例如上面的的ViewGroup的addView方法,我们结合实际的代码来看一下我们平时是怎么用的。
被你忽略的里氏替换原则
现在有这样一个需求,那就是一个页面中会请求服务器,服务器会返回给我们不定个数的图片地址,然后我们要在页面中将这些图片显示出来。首先我们分析一下,因为图片的个数是不固定的,所以这里我们动态的向页面中添加ImageView是一个比较好的选择。我们通过一个简单的小例子来演示一下:
[code] /** * 模拟解析网络请求数据,返回一个图片地址集合 * @return */ private List<String> getImageUrl() { List<String> urls = new ArrayList<>(); for(int i = 0; i < 10; i++) { urls.add("imageUrl" + i + ".png"); } return urls; } /** * 向页面中动态添加ImageView */ private void addImageView() { // 创建容器 LinearLayout container = new LinearLayout(this); // 根据请求到的URL个数来动态创建ImageView for(int i = 0; i < getImageUrl().size(); i++) { ImageView imageView = new ImageView(this); imageLoader(getImageUrl().get(i), imageView); // 向容器中添加ImageView container.addView(imageView); } } /** * 下载图片、并设置给ImageView * @param url * @param imageView */ private void imageLoader(String url, ImageView imageView) { // 网络请求图片 }
上面的代码模拟了我们的需求实现,如上述我们分析的ViewGroup的addView需要一个View类型的参数,又因为ImageView是View的实现类,所以根据里氏替换原则,这里我们可以将ImageView作为参数传给addView方法,这样Android系统就会将ImageView添加到LinearLayout容器当中,通过一些列的内部处理从而展现在页面上了。
对于里氏替换原则我们就分析到这里了,里氏替换原则是开闭原则的实现方式之一,可能大家感觉它们非常相似,切记一定要领会到它们的定义和针对的问题领域、这样才能在实际的开发当中融会贯通、灵活运用。
上述如有描述不当欢迎大家留言、拍砖。
相关文章推荐
- POJ 1064 (二分)
- 1.22 Java基础总结 最常用冒泡排序
- centos6.5修改硬盘分区
- IOS高级控件(一)
- QLPreviewItem
- 异常检测
- 虚拟机下安装UEFI+GPT+win7x64
- ubuntu下cmake3.4的安装(有GUI界面)
- js如何判断一组数字是否连续,得到一个临时数组[[3,4],[13,14,15],[17],[20],[22]];
- JavaSE-匿名类_匿名内部类的使用
- Linux CentOs 7.0 mysql 5.7.10 开机自动启动方法
- LeetCode264. Ugly Number II
- [Leetcode]105. Construct Binary Tree from Preorder and Inorder Traversal@python
- 信号量与锁的差别
- Android项目结构之模块内结构优化
- linux用户管理笔记
- (九)Android权限系统
- python脚本检查网页能否打开
- 我的2015年度总结
- [从头学数学] 第61节 除数是两位数的除法