OOD设计原则之OCP、LSP

来源:互联网 发布:linux已安装软件位置 编辑:程序博客网 时间:2024/06/05 19:59

一直谈软件设计,却不能准确的描述。结合最近看《黑客与画家》,这才对设计的六大原则有了一点浅显的体会。首先说一下一个项目的路径:开发、重构、测试、投产、运维。其中重构的好处就是希望对原有设计和代码进行修改(注意:重构的应该分两个方向:设计上的修改和代码上的修改),而运维则是希望尽量减少对原有代码的修改,保持历史代码的纯净,提高系统稳定性。

原则一:开闭原则(OCP)

 软件应该保持对扩展开放,对修改关闭。开发的时候要允许在已有软件模块的基础上进行拓展功能,并尽可能不去修改已有的功能模块。换句话说就是一个软件实体应该通过扩展来实现变化,而不是通过修改已有代码来实现变化。

那么如何使用这个原则呢?个人认为核心是找到或者预见未来可能发生变化代码块。在具体使用的时候可以考虑:

1、通过使用接口或者抽象类约束扩展,并对扩展的边界进行限定,保证出现在接口或抽象类中方法都是有用的方法。

2、参数类型尽可能是接口或者抽象类,而不是某一个实现类。

3、合理的设计抽象类和接口,也就是尽可能的保持抽象层的稳定,一经确定不允许修改。

4、尽可能的通过元数据来控制模块的行为。简单点说通过配置参数,来控制模块的模块的功能。参数既可以写在普通文件中,也可以保存在数据库中。提到这一点就不得不说Spring容器,Spring就是一个典型的通过元数据来控制模块行为。仅仅通过元数据控制模块是不够的,用到极致的是IOC,也就是控制反转。

5、统一项目约束,团队中的所有人员必须遵守项目约束,比如说命名规则等。(团队合作中非常重要)

6、封装变化,将相同的变化封装到同一个接口或者抽象类中,不同的变化封装到不同 的接口或者抽象类中。也就是多种变化不能共存。

值得注意的是,OCP并不意味着完全不修改已有代码。通常而言,底层模块的变化,必须服务于高层模块,也就是耦合。在具体使用中需要根据情况考虑。


原则二:里氏替换原则(LSP)

开发时,基类型(basetype)能够被子类型(subtype)替代,充分考虑多态性。换句话说,一个软件实体如果使用的是一个基类,那么一定适用于其子类,而且无法差别到底是基类对象还是子类对象。比如说有两个类,一个是Base类,另一个Sub类继承了Base子类。那么如果一个方法可以接收一个基类对象b:method(Base b),那么他必然可以接收一个子类对象s,也就是method(s)。这样一看,LSP其不是很简单么?当然不是!LSP本质上应该是说在 继承结构构建的过程中,要合理,而不是滥用!!也就是说不是所有的继承都是合理的。这里我们也来说说经典的问题:”正方形不是矩形“。在现实世界中,正方形是矩形。在OO中,正方形和矩形的也应该是IS-A的关系,这关系正好是OO中的继承关系的依据嘛。因此,在设计类的时候,正方形Square类应该继承矩形Rectangle类。

package com.ldd.lsp;public class Rectangle {private int width;private int height;public int getWidth() {return width;}public void setWidth(int width) {this.width = width;}public int getHeight() {return height;}public void setHeight(int height) {this.height = height;}}
package com.ldd.lsp;public class Square extends Rectangle {private int width;private int height;public void setWidth(int width) {this.width=width;}public void setHeight(int height) {this.height=height;}}
package com.ldd.lsp;public class Client {public Rectangle addHeight(Rectangle b){while(b.getHeight()<b.getWidth()){int height = b.getHeight();b.setHeight(height++);}return b;}}
package com.ldd.lsp;public class Test {public static void main(String[] args) {Client client=new Client();Rectangle rectangle=new Rectangle();rectangle.setHeight(5);rectangle.setWidth(9);//LSP原则Rectangle square=new Square();square.setHeight(5);square.setWidth(5);client.addHeight(rectangle);//正方形到底是不是矩形呢?client.addHeight(square);}}

如果给addHeight传一个Rectangle(长宽不一样)对象的话,没问题;但是如果传一个Square对象的话,我们且不管结果如何,明眼人一眼看出,这addHeight方法不适用Square对象,说白了违反了LSP原则:对于Square必须同时修改width和Height,而Rectangle可以单独修改width和Height。这个问题反映了现实世界概念和OO概念的区别,虽然OO吉利于描述现实世界,但并不是完全等于。
那么如何做到LSP呢?
1、从抽象类继承,而不是实体类继承。也就是抽象类做父类,之所以这么考虑是因为实体类中确定了和特定实体相关的方法,而这些方法在子类中也许无用。
2、使用契约式编程方法(DBC)。简单点理解DBC就是,父类中定义子类需要实现的功能,相当于用父类来约束子类的功能。
3、从客户角度出发,派生类的行为必须和客户程序对其基类所期望行为的保持一样。

现在我们来重新考虑一起正方形和长方形问题:无论正方形还是矩形,都是图形。因此创建抽象类Shape,里边定义了对图形的基本操作,而Rectangle类和Square类继承Shape类。
通过以上,再来看一下LSP就很明了:如何合理的使用继承才是关键。

0 0