SRP | The Single Responsibility Principle | 单一责任原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
SOLID--固体、稳定的
solid是5个原则的缩写,这样的设计具有稳定、可靠的特点。
单一职责SRP
一个类有且只有一个职责引起类改变的原因,只能有一个
//接口、方法 更容易实现此原则
任意小的改变都将导致这个单一类的变化。如果遵守 SRP,类将变得简洁和灵活。
核心是把整个问题分为小部分,并且每个小部分都将通过一个单独的类负责。
public class Tank
{//坦克类,业务逻辑和界面混合在一起
public void Move()
{
//left
//up
//right
//down
}
public void Show()
{
//windows show
}
}
//新需求:linux
面对新的需求,如何复用逻辑代码?
因此,应该将职责分离,降低耦合。
开放封闭原则
一个类应该对扩展开放,对修改关闭。
一个类已经开始使用,就不应该再修改。因为如果改变它,很可能你的改变会引发系统的崩溃。如果需要一些额外功能,应该扩展这个类而不是修改它。
使用这种方式,现有系统不会看到任何新变化的影响。同时,只需要测试新创建的类。
public class Calculator
{ //加减计算器
public double Calculate(double firstNumber, double secondNumber, string operate)
{
double result = 0d;
switch (operate)
{
case “+":
result= firstNumber+ secondNumber;
break;
case “-":
result= firstNumber- secondNumber;
break;
}
return result;
}
}
新需求:加入乘除功能
修改原来的类?影响到原有的功能怎么办?
更好的设计:
public class Operation
{ //抽象父类,拥有基本属性
public double NumberA
{
}
public double NumberB
{
}
public virtual double GetResult()
{
double result = 0;
return result;
}
}
class OperationMul : Operation
{ //拓展的乘法子类,增加新功能时,直接增加一个类,而不是修改原来的代码
public override double GetResult()
{
double result = 0;
result = NumberA * NumberB;
return result;
}
}
也就是封装变化,把频繁改动的地方抽象
LSP里式替换
父类出现的地方皆可以换成子类
上层模块不应关心底层模块的是如何工作的;同样的接口模块之间,可以在不知道底层模块代码的情况下,进行替换。即接口或父类出现的地方,实现接口的类或子类可以代入。
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();//新来一只牛(猫,狗,羊),要修改整个方法吗?
animal.Show(); //或者拓展了Animal类,上层客户端的代码都要改动吗
animal.Eat();
animal.Run();
}
}
public class Cat:Animal
{ //子类继承Animal,是对父类的拓展
}
//Animal animal = new Cat();//使用继承&&多态,尽可能修改少的代码
正因为子类型的替换,才使得父类无须修改就可以扩展,
父类真正被服用,子类在此基础上增加新的行为
注意:父类和子类须严格遵循is-a关系
接口隔离原则(ISP)
类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的。
使用多个专门的接口比使用单一的总接口要好
不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性
public interface Animal
{
public void eat(); // 吃
public void sleep(); // 睡
public void crawl(); // 爬
public void run(); // 跑
}
public class Bird : Animal
{
public void eat()
{
}
public void sleep()
{
}
public void crawl ()
{应单独抽取出接口!
//鸟是动物,但不会爬
}
public void run()
{应单独抽取出接口!
//鸟也不会跑
}
}
依赖注入或倒置原则(DIP)
1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
2. 抽象不应该依赖于细节,细节应该依赖于抽象
public class Manager
{
public Notify notify = new Notify();//老式的调用
}
public class Notify
{
// Notify something
}
第一,当需要追加提供一种新的Notify时,对Manager层进行改动,增加了额外的工作。
第二,这种改动可能会影响到Manager ,带来风险。
第三,改动后, Manager层必须重新再做测试。
public interface INotify
{
void Notify();
}
public class Manager:INotify
{
//do something
public void Notify()
{//只需要维护一个面向底层的接口,改动变小,尽可能遵守开闭原则
//亦可以使用反射或上层传参的方式完全封闭这个类
public INotify notify = new EmailNotify();
}
//do somgthing
}
public class EmailNotify:INotify
{
//EmailNotify
}
public class PhoneNotify:INotify
{
//PhoneNotify
}
总结
一个对象只承担一种责任,所有服务接口只通过它来执行这种任务。
向扩展行为开放,向修改行为关闭。
子类应该可以用来替代它所继承的类。
一个类对另一个类的依赖应该限制在最小化的接口上。
依赖抽象层(接口、抽象类),而不是具体类。
好处:
高内聚低耦合
简洁明了
可读性高
可维护性高
良好的设计、尽可能避免问题的出现
问题出现时,尽早的解决、低风险
通常,符合这样的设计规范的代码就是简短、灵活、易懂,也就是,好的代码!