重要性
SOLID原则就是设计模式的基础,看不懂设计模式那应该就是这五个原则没学好。
耦合:
我们可以把它定义为一个类、方法或者任何一个实体直接与另一个实体连接的度。这个耦合的度也可以被看作依赖的度。
当一个Class中使用了另一个Class实例化出来的对象,就代表产生了依赖。
如果在一个类A中使用了另一个类B的实例,不巧类B出了bug,此时不管类A有多正确都无法正常工作。程序不好调试,也会影响团队工作:负责类B的程序员将bug修复好之前,类A程序员必须等待,整个开发节奏都取决于速度最慢的那一个人。
一个项目中有20个方法调用良好,但是要修改了其中一个,另外的19个都要进行修改,这就是高耦合!独立性太差!
内聚:
一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即‘高内聚’ !
高内聚,低耦合
内部之间严格依赖,到外部就各扫门前雪,每个模块都要做的像大清闭关锁国一样,程序里要做到全世界都是大清一样的国家(模块)。
如何实现? 引入接口!
约束一组功能,调用者只能调用接口包含的功能,而且调用者可以只管调用,不必关心是谁提供的。
S :Single Responsibility Pinciple SRP 单一职责原则
一个类应该只能有一个职责并且只完成为它设计的功能任务
如果我们的类承担的职责多余一个,那么我们的代码就会出现高耦合度,导致任何改变都很脆弱。
O: Open Closed Principle OCP 开闭原则
软件实体(类,模块,方法等)应该对扩展开放,对修改封闭。
抽象和多态可以帮助应用这个原则
比如刘铁锰老师的那个例子中,run方法是很多子类都需要的,所以可以用virtual去修饰父类中的run()方法,子类中用override去重写,这就是“开”。而stop方法和fill方法是不需要更替的,而且很少会对他们做出修改(除了修bug),就不应该重写他们,所以就不用virtual修饰,以防将他们暴露出来
L:Liskov substitution Principle 里氏替换原则
程序里的对象都应该可以被它的子类实例替换而不用更改程序.
I ISP 接口隔离原则
接口隔离原则:保证调用者不会多要(提供者给出的都会实现),提供者不会少给(调用者不会拿到自己用不到的部分)
也就是说:过个专用的接口比一个通用接口要好
有这样一个场景:一个女生开车出门遇到了一点小交通事故,打电话给男朋友,她男朋友也很高情商啊,就说:没事,下回给你买一辆坦克,这样上街就不怕被撞了。
下面就来实现这个过程:
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new LightTank());
driver.drive();
}
}
public class Driver
{
IVhicle _vhicle;
public Driver(IVhicle vhicle)
{
this._vhicle = vhicle;
}
public void drive()
{
this._vhicle.run();
}
}
public interface IVhicle
{
void run();
}
public class Car : IVhicle
{
public void run()
{
Console.WriteLine("car is running");
}
}
public class Truck : IVhicle
{
public void run()
{
Console.WriteLine("Truck is running");
}
}
//public interface IWeapon
//{
// void fire();
//}
//public interface ITank: IWeapon,IVhicle
//{
//}
public interface ITank
{
void run();
void fire();
}
public class LightTank : ITank
{
public void fire()
{
Console.WriteLine("Light Tank is fire");
}
public void run()
{
Console.WriteLine("Ligth Tank is Running");
}
}
public class HeavyTank : ITank
{
public void fire()
{
Console.WriteLine("Heavy Tank is fire");
}
public void run()
{
Console.WriteLine("Heavy Tank is Running");
}
}
}
这段代码在new Driver(new LightTank());时是报错的,也就是说驾驶员开不了坦克,因为Driver要求传入的是IVhicle类型
一种解决方案是将Driver类中的IVhicle都改成ITank的,这样做确实没问题,但是也是只能做到二选一。
问题就在于目前是把一个“胖的”接口传了进来,里面包含了不会被用到的fire功能,违反了接口隔离原则。
违反隔离原则时的解决方法:将胖接口分解为多个更专用的接口
public interface IWeapon
{
void fire();
}
public interface ITank : IWeapon,IVehicle
{
}
这里将ITank的两个功能分配给IVehicle和IWeapon:
这样做Driver就可以保持使用IVehicle来达到Car和Tank都能开的效果。
C#一个特有的能力,显式接口实现,更好的接口隔离
不但能实现接口隔离,甚至能把一些隔离出来的接口隐藏起来,直到显式的使用这些接口类型的变量去引用一个实现了这个接口的具体类的实例的时候,才能被你使用。
举例:这个杀手不太冷中的男主接,让.雷诺。一半暖男,一半杀手。
WarmKiller类实现IGentleman,IKiller接口,代表让.雷诺是一个一半暖男一半杀手的人,拥有Lover和Killer两种方法。但是平常Killer的方法不能随意拿来示人,所以我们将其使用显式实现。这样做之后只有用IKiller类型引用此对象时才能看见Kill方法。
using System;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
WarmKiller wm = new WarmKiller();
}
}
interface IGentleman
{
void Love();
}
interface IKiller
{
void Kill();
}
class WarmKiller : IGentleman, IKiller
{
public void Love()
{
throw new NotImplementedException();
}
void IKiller.Kill() //可以看到此处和Love实现不同,是显式实现,方法名前面加了IKiller
{ //也就是说只有用IKiller类型引用此对象时才能看见这个Kill方法
throw new NotImplementedException();
}
}
}
D:Dependency Inversion Principle DIP 依赖反转原则
解耦在代码中的表现就是依赖反转,单元测试就是依赖反转在开发当中的直接受益者
高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象**。
抽象不应该依赖于细节。细节应该依赖于抽象。
依赖倒转原则的意思是一个特定的类不应该直接依赖于另外一个类,但是可以依赖于这个类的抽象(接口)。
当我们应用这个原则的时候我们能减少对特定实现的依赖性,让我们的代码复用性更高。**
紧耦合的情况下,类与类之间会产生如下自顶向下,逐步求精的依赖结构,这也是人类解决问题的一种思想:
被依赖的放在下面,调用者在上面。
依赖反转原则就是用来平衡这种思维方式
下面来看反转过程:
先是紧耦合,而且此时还没有使用接口来形成多态。Racer开不了Truck
下一步就是使用接口来实现多态,注意 一个类类实现一个接口的时候,类与接口之间依旧是紧耦合。
再演化,当多个服务的使用者和多个服务的提供者都遵循一个接口时,他们就可以进行多种配对。
再进一步就是设计模式了。