面向对象编程中的6大设计原则
面相对象编程中,参考以下编码原则,可以帮助提升程序的可维护性、可扩展性、可复用性。这些原则也是设计模式的基础。
单一职责原则
There should never be more than one reason for a class to change
定义:应该有且仅有一个原因引起类的变化。
该原则适用于类、接口,同时也适用于方法,一个方法要尽可能的只做一件事。
这个原则的要点是每个类在理想情况下,应该围绕一个核心任务构建。本质上不是简化,而是通过暴露数量非常有限的责任,使这个类与系统的交集更小,这样一来,当需求改变的时候,需要编辑这个类的可能性就会更小了。
有大佬建议在实际项目中,接口一定要做到单一职责原则,类的设计尽量做到只有一个原因引起变化。
开放/封闭原则
该原则认为模块应该对扩展开放,但对修改封闭。
对扩展开放基本上意味着现有的类应该是可扩展的。可以用作构建其他相关功能的基础,但在实现其他相关功能时,你不应该修改现有代码,因而是对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
该原则鼓励使用组合、接口、和泛型等编程机制生成在不修改源代码的情况下,可以扩展的类。
里氏替换原则
定义:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常。
里氏替换原则(LSP)为良好的继承定义了一个规范,包含以下几个含义:
- 子类必须完全实现父类的方法。(父类是抽象类或接口,父类的方法是指父类中未显式实现的方法)
另外注意,在类中调用其他类时,务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则 - 重写(override)或实现父类方法时,输入参数可以被放大。(输入参数范围大于父类该方法中的参数范围,这样才能LSP,这很明显)
- 重写(override)或实现父类方法时,返回值即输出结果可以被缩小。
里氏替换原则的本质是派生类不能限制基类执行的条件。简而言之,派生类需要的不能比父类多,提供的不能比父类少。
依赖倒置原则
定义:该原则认为高级模块不应该依赖于低层模块,二者都应该依赖于抽象。
在项目中,大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
具体处理依赖的模式有两种:依赖注入、服务定位器。
依赖注入通常有构造函数注入、属性注入、方法注入这三种,我们一般通过构造函数注入,因为这样可以从一开始就清楚表明一个类有什么依赖。依赖注入通常需要特定的生产力工具,也被称为ioc容器。
服务定位器模式:
在使用形式上类似下面这样:
void Copy() { Byte byte; var reader = ServiceLocator.GetService<IReader>(); var writer = ServiceLocator.GetService<IWriter>(); while (byte=reader.Read()) { writer.Write(byte); } }
通常的做法是把服务定位器创建成一个类,暴露少数静态工厂方法,类似下面这样:
public class ServiceLocator { public object GetService(Type typeToResolve) { ……} public T GetService<T>( ) { ……} public object GetService(string typeNickName) { ……} }
这个模式的缺点是需要你深入代码才能弄清如何处理依赖。另外最适合他的场景是,当你要为很难通过其他方式重新设计的某个大型遗留代码添加扩展时。在多数情况下,服务定位器被看做反模式。理由是你的代码最终会遍布服务定位器类的引用。更糟糕的情况是直到运行时,你才会发现错误。
接口隔离原则
该原则认为不应该强制客户(调用方)依赖于他们不用的接口。
正确实施这个原则,意味着把接口分解成多组函数。以便每个客户更容易匹配他真正感兴趣的那组函数。
未能充分遵守接口分立原则会导致实现很复杂以及很多方法根本没有实现。此外,客户被迫依赖于他们不用的接口,而且这些客户还受制于这种接口的改变。
迪米特法则
又称最少知道原则,是指:一个实体应当尽量少地与其他实体之间发生相互作用,以使得系统功能模块相对独立。
更新于:2923.5.27