文章目录
一.单一职责原则(Single Responsibility Principle):
定义: 就一个类而言,应该仅有一个引起他变化的原因,简单来说,就是一个类只负责一项职责.
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则
单一职责好处:
- 代码的粒度降低了,类的复杂度降低了。
- 可读性提高了,每个类的职责都很明确,可读性自然更好。
- 可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
- 改动代码所消耗的资源降低了,更改的风险也降低了。
二.开闭原则(Open Close Principle):
定义: 一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应该尽量在不修改原代码的情况下进行扩展。
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层次模块的变化,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
在业务规则改变的情况下高层模块必须有部分改变以适应新业务,改变要尽量地少,放置变化风险的扩散
—秦小波《设计模式之禅》
单一职责好处:
- 良好的可扩展性,可维护性
举个简单的例子:
这里有个生产电脑的公司,根据输入的类型,生产出不同的电脑,代码如下:
interface Computer { }
class Macbook : Computer { }
class Surface : Computer { }
class Factory {
public Computer produceComputer (String type) {
Computer c = null;
if (type.Equals ("macbook")) {
c = new Macbook ();
} else if (type.Equals ("surface")) {
c = new Surface ();
}
return c;
}
}
显然上面的代码违背了开放 - 关闭原则,如果需要添加新的电脑产品,那么就要修改 produceComputer 原本已有的方法,正确的方式如下:
interface Computer { }
class Macbook : Computer { }
class Surface : Computer { }
interface Factory {
Computer produceComputer ();
}
class AppleFactory : Factory {
public Computer produceComputer () {
return new Macbook ();
}
}
class MSFactory : Factory {
public Computer produceComputer () {
return new Surface ();
}
}
正确的方式应该是将 Factory 抽象成接口,让具体的工厂(如苹果工厂,微软工厂)去实现它,生产它们公司相应的产品,这样写有利于扩展,如果这是需要新增加戴尔工厂生产戴尔电脑,我们仅仅需要创建新的电脑类和新的工厂类,而不需要去修改已经写好的代码。
三.里式替换原则(Liskov Substitution Principle):
定义: 所有引用基类的地方必须能透明地使用其子类的对象。
通俗点讲:只要父类能出现的地方子类就可以出现,而且替换为子类也不产生任何异常错误,反之则不然。这主要体现在,我们经常使用抽象类/基类做为方法参数,具体使用哪个子类作为参数传入进去,由调用者决定。
这条原则包含以下几个方面:
- 子类必须完全实现父类的方法
- 子类可以有自己的个性外观(属性)和行为(方法)
- 覆盖或者实现父类方法时,参数可以被放大。即父类的某个方法参数为HashMap时,子类参数可以是HashMap,也可以是Map或者更大
- 覆盖或者实现父类的方法时,返回结果可以被缩小。即父类的某个方法返回类型是Map,子类可以是Map,也可以是HashMap或者更小。
LSP 原则最重要的一点就是:避免子类重写父类中已经实现的方法。这就是 LSP 原则的本质
里氏替换原则的重点在不影响原功能,而不是不覆盖原方法。
举个简单的例子:
我们需要完成一个两数相减的功能:
class A{
public int func1(int a, int b){
return a-b;
}
}
后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:
- 两数相减
- 两数相加,然后再加100
由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:
class B : A{
public int func1(int a, int b){
return a+b;
}
public int func2(int a, int b){
return func1(a,b)+100;
}
}
```csharp
我们发现原来原本运行正常的相减功能发生了错误,原因就是类 B 在给方法起名时无意中重写了父类的方法,造成了所有运行相减功能的代码全部调用了类 B 重写后的方法,造成原来运行正常的功能出现了错误。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是这样往往也增加了重写父类方法所带来的风险。
**里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。**
### 四.依赖倒置原则(Dependence Inversion Principle):
**定义**: 高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象。
- 底层模块:不可分割的原子逻辑就是低层模块
- 高层模块:低层模块的组装合成后就是高层模块
该原则主要有下面几点要求:
- 模块间的依赖应该通过抽象发生,具体实现类之间不应该建立依赖关系
- 接口或者抽象类不依赖于实现类,否则就失去了抽象的意义
- 实现类依赖于接口或者抽象类
- 每个类都尽量要有接口或抽象类,或者两者都有
- 任何类都不应该从具体类中派生
**依赖倒转原则的核心思想就是面向接口编程**
**例子:**
母亲给孩子讲故事,只要给她一本书,她就可照着书给孩子讲故事了。代码如下:
```csharp
class Program {
static void Main (string[] args) {
Mother mother = new Mother ();
mother.say (new Book ());
}
}
class Book {
public String getContent () {
return "这是一个有趣的故事";
}
}
class Mother {
public void say (Book book) {
Console.WriteLine ("妈妈开始讲故事");
Console.WriteLine (book.getContent ());
}
}
假如有一天,给的是一份报纸,而不是一本书,让这个母亲讲下报纸上的故事,报纸的代码如下:
class Newspaper{
public String getContent(){
return "这个一则重要的新闻";
}
}
然而这个母亲却办不到,应该她只会读书,这太不可思议,只是将书换成报纸,居然需要修改 Mother 类才能读,假如以后需要换成了杂志呢?原因是 Mother 和 Book 之间的耦合度太高了,必须降低他们的耦合度才行。
我们可以引入一个抽象接口 IReader 读物,让书和报纸去实现这个接口,那么无论提供什么样的读物,该母亲都能读。代码如下:
class Program {
static void Main (string[] args) {
Mother mother = new Mother ();
mother.say (new Book ());
mother.say (new Newspaper ());
}
}
interface IReader {
String getContent ();
}
class Newspaper : IReader {
public String getContent () {
return "这个一则重要的新闻";
}
}
class Book : IReader {
public String getContent () {
return "这是一个有趣的故事";
}
}
class Mother {
public void say (IReader reader) {
Console.WriteLine ("妈妈开始讲故事");
Console.WriteLine (reader.getContent ());
}
}
五.接口隔离原则(Interface-Segregation Principle):
定义: 客户端不应该依赖它不需要的接口;类间的依赖应该建立在最小的接口上.
通俗点讲:使用接口时应该建立单一接口,不要建立臃肿庞大的接口,尽量给调用者提供专门的接口,而非多功能接口。
- 接口隔离原则的思想在于建立单一接口,尽可能地去细化接口,接口中的方法尽可能少
- 但是凡事都要有个度,如果接口设计过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
六.迪米特法则(Low of Demeter):
定义:又称最少知识原则,一个对象应该对其他对象有最少的了解。
通俗来说就是,只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外提供 public 方法,不对泄漏任何信息。