OOD设计原则之OCP、LSP

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

原则一:开闭原则(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类。

</pre><pre class="java" name="code">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就很明了:如何合理的使用继承才是关键。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结