设计模式原则之六大原则

一.单一职责原则(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 方法,不对泄漏任何信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值