SOLID原则
- 单一责任原则(SRP)
- 开放/封闭原则(OCP)
- 里氏代换原则(LSP)
- 接口分离原则(ISP)
- 依赖反转原则(DIP)
对象建模并不容易,而且也不是精确科学。原则的存在大多数情况下告诉你做事的方式.一一给予指导,可能为你指明正确的方向。建模的整个要点是找到正确的原则组合。它们之中的一些甚至可能是冲突的。某些SOLID原则(特别是SRP和OCP)可能真的很难在大型代码库里全面实施。它们可能会引导你持续重构,直到你发现超过某个临界值之后工作继续量增加而好处却开始减少为止。盲目实施原则(特别是和OCP)可能会使你不能集中于主要目标,即产生可工作可维护的代码。
单一责任原则
一个类有且只有一个改变理由。
这里的重点本质上不是简化,而是通过暴露数量非常有限的责任使这个类与系统的交集更小,这样一来,当需求
改变时,需要编辑这个类的可能性就会更小了。
SRP经常被盲目地用于衡量代码的质量。在极端情况下,你会面临产生很多贫血类(只有属性
和少量甚至没有行为)的严重风险。每个类都倾向于承担最小量的责任,但你仍需要一些地方放置
系统所需的所有编排逻辑。因此出来的模型可能没有那么理想。
这就是为什么我们把SRP称为向量,在写代码时,记住类应该尽可能简单,专注于一个主要的
核心任务。但那不应该变成宗教教义或者衡量性能和质量的方式。
开放/封闭原则
开放/封闭原则使开发者可以创建能从改变幸存下来的软件实体(不管是类、模块还是函数)。这个原则认为:
模块应该对扩展开放,但对修改封闭。
对扩展开放基本上意味着现有的类应该是可扩展的,可以用作构建其他相关功能的基础。但在实现其他相关功能时,你不应该修改现有代码,因而对修改封闭。
OCP鼓励使用组合、接口和泛型等编程机制生成在不修改源代码的情况下可以扩展的类。
比如说,如果你创建一个类,使用泛型日志记录接口记录这个类的活动,这个类就能使用任何实现这个接口的日志记录器了。如果你使用组合,你可以在不触及现有组件的情况下在它们之上构建新的功能。
里民代换原则
子类应该可以替换它们的基类。
每当使用继承时,需要注意里民代换原则。继承是一个棘手的东西,组合肯定是使用类的一种更安全的方式。
里氏代换原则的本质是派生类不能限制基类执行的条件。类似地,派生类不能避免产生一些父类保证要有的结果。简而言之,派生类需要的不能比父类多,提供的不能比父类少。
里氏代换原则也适用于接口,而不仅仅是派生类。
常见代码问题
-
在一个派生类里,你可以做一些事情妨碍这个派生类在任何一个接受父类的地方使用。面向对象的这种微妙的特点通常会给开发者带来负面影响。
-
另一个可以追溯到里氏代换原则的常见代码问题是使用虚方法。每当你定义一个虚方法时,你应该确保你没有从它里面调用任何私有成员。这样做会限制实现派生类的人重写这个访问父类上下文的方法。
错误示例
public class User
{
public virtual void DoSomething(int number)
{
return;
}
}
public class RegisteredUser : User
{
public override void DoSomething(int number)
{
Contract.Requires<ArgumentException>(number > 0);
}
}
如果在方法调用时,参数类型为User,实际传递的类型为RegisteredUser,由于派生类的方法DoSomething中有判断number > 0,所以很有可能会传入负数导致抛出异常。
代码辅助工具
Code Contracts
接口分离原则
接口分离原则解决接口臃肿的问题,建议接口保持最低限度的函数。这里的重点不是你应该使用瘦接口,而是你不应该使用胖接口。这个原则认为:
不应该强制客户依赖于它们不用的接口。
仅当功能分割时候,接口分离才有意义。如下。
public interface IDoor
{
void Lock();
void UnLock();
Boolean IsDoorOpen { get; }
Int32 OpenTimeout { get; set; }
event EventHandler DoorOpenForTooLong;
}
分离后
public interface IDoor
{
void Lock();
void UnLock();
Boolean IsDoorOpen { get; }
}
public interface ITimedDoor
{
Int32 OpenTimeout { get; set; }
event EventHandler DoorOpenForTooLong;
}
未能充分遵守接口分离原则会导致实现很复杂以及很多方法根本没有实现。此外,客户被迫依赖于它们不用的接口,而且这些客户还受制于这种接口的改变。
依赖反转原则
DIP是一个极其重要的原则,它也是依赖注入和服务定位器等常见模式的理论基础,
高级模块不应该依赖于低层模块。二者都应该依赖于抽象。
依赖反转是表达“对接口而不是实现编程”背后的概念的正式方式。当你写一个方法并且需要调用外部组件时,作为一名开发者,你应该思考你要调用的这个函数是这个类私有的还是一个外部的依赖。如果是一个外部的依赖,你就把它抽象成一个接口,然后对这个接口继续编码。剩下的问题就是如何把这个接口变成某些具体的可调用的实例。这就是依赖注入等模式的事情了。