设计模式中的SOLID原则,分别是单一原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则,遵循前辈们总结出来的五大原则可以使程序解决紧耦合,更加健壮。来看个表单:

简拼 全拼 汉字 简单介绍
SRP The Single Responsibility Principle  单一责任原则 对象应该仅具有单一的功能
OCP The Open Closed Principle  开放封闭原则 软件体应该对扩展是开放的,但对修改封闭的
LSP The Liskov Substitution Principle 里氏替换原则 程序中的对象应该是可以在不改变程序正确性的前提下
被它的子类对象所替换的
DIP The Dependency Inversion Principle 依赖倒置原则 多个特定客户端接口要好于一个宽泛用途的接口
ISP The Interface Segregation Principle 接口分离原则 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;
抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口

我们要注意的是,原则并不是规则,更不是教条,对智者来说是指导,对愚者来说是遵从。

咱们先来看单一责任原则,它指的是一个类或者一个方法只做一件事。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化就可能抑制或者削弱这个类完成其他职责的能力。例如餐厅服务员负责把订单给厨师去做,而不是服务员又要订单又要炒菜。

在这里有一句特别有名的话,那就是,THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE,意思就是,当需要修改某个类的时候原因有且只有一个。

换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。 类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题,非常耗时耗力。

咱来结合写代码时候的思路来具体解释下。

当我们新建一个Rectangle类,这个类包含两个方法,一个用于把矩形绘制在屏幕上,一个方法用于计算矩形的面积。我们来结合这个SRP原则来分析下,很明显这个类违反了SRP原则,因为Rectangle类具有两个职责,如果其中一个改变,会影响到两个应用程序的变化。其实,一个好的设计是把两个职责分离出来放在两个不同的类中,这样任何一个变化都不会影响到其他的应用程序。

完事来看开放封闭原则,很简单的一句话,对扩展开放,对修改关闭。意思就是一个类独立之后就不应该去修改它,而是以扩展的方式适应新需求。例如一开始做了普通计算器程序,突然添加新需求,要再做一个程序员计算器,这时不应该修改普通计算器内部,应该使用面向接口编程,组合实现扩展,来看张图片:

PHP设计模式之SOLID原则了解下

再来看里氏替换原则,意思就是,所有基类出现的地方都可以用派生类替换而不会程序产生错误。子类可以扩展父类的功能,但不能改变父类原有的功能。例如机动车必须有轮胎和发动机,子类宝马和奔驰不应该改写没轮胎或者没发动机,来看张图片:

PHP设计模式之SOLID原则了解下

也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。在基本的面向对象原则里,"继承"通常是"is a"的关系,如果"Developer" 是一个"SoftwareProfessional",那么"Developer"类应当继承"SoftwareProfessional"类,在类设计中"Is a"关系非常重要,但它容易冲昏头脑,导致使用错误的继承造成错误设计。

通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计。

来看一个经典的例子:

PHP设计模式之SOLID原则了解下

上图就是遵循Liskov替换原则的类结构图,我们可以理解为,KingFisher(翠鸟)类扩展了Bird基类,并继承了Fly()方法。

来看下里氏替换原则的重要原因:

  • 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
  • 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;

好啦,之后我们来看下接口隔离原则,意思就是类不应该依赖不需要的接口,知道越少越好,例如电话接口只约束接电话和挂电话,不需要让依赖者知道还有通讯录。我们不能强迫用户去依赖那些他们不使用的接口,换句话说就是,使用多个专门的接口比使用单一的总接口总要好。

反正,我们最终不要把什么功能都放在一个大的接口里,因为这些功能并不是每个继承该接口的类都所必须的。来看一张图片:

PHP设计模式之SOLID原则了解下

上图IBird接口包含很多鸟类的行为,包括Fly()行为。现在如果一个Bird类(如Ostrich也就是鸵鸟)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich鸵鸟不会飞),因此,这个"胖接口"应该拆分成两个不同的接口,IBird和IFlyingBird, 而IFlyingBird继承自IBird,如下图:

PHP设计模式之SOLID原则了解下

由此可知,如果我们想要获得可重用的方案,就应当遵循接口分离原则,把接口定义成仅包含必要的部分,以便在任何需要该接口功能的地方复用这个接口。

最后我们来看依赖倒置原则,它指的是高级模块不应该依赖低级模块,而是依赖抽象,并且抽象不能依赖细节,细节要依赖抽象。比如类A内有类B对象,称为类A依赖类B,但是不应该这样做,而是选择类A去依赖抽象。例如垃圾收集器不管垃圾是什么类型,要是垃圾就行,如下图:

PHP设计模式之SOLID原则了解下

这个原则细分下来,可以化为五点,如下:

   (1).高层模块不要依赖低层模块;
   (2).高层和低层模块都要依赖于抽象;
   (3).抽象不要依赖于具体实现;
   (4).具体实现要依赖于抽象;
   (5).抽象和接口使模块之间的依赖分离。

来举个实例感受下,比如,我们的汽车是由很多如引擎,车轮,空调和其它等部件组成,如下:

PHP设计模式之SOLID原则了解下

咱们这里的 Car 就是高层模块,它依赖于抽象接口IToyotaEngine 和 IEighteenInchWheel,然而,具体的引擎FifteenHundredCCEngine 属于底层模块,也依赖于抽象接口IToyotaEngine,还有就是,具体的车轮 EighteenInchWheelWithAlloy同样属于底层模块,也依赖于抽象接口IEighteenInchWheel。

咱们上面Car类有两个属性(引擎和车轮列表),它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。

对于上例,我们可以做出如下总结:

  • 一个对象只承担一种责任,所有服务接口只通过它来执行这种任务。

  • 程序实体,比如类和对象,向扩展行为开放,向修改行为关闭。

  • 子类应该可以用来替代它所继承的类。

  • 一个类对另一个类的依赖应该限制在最小化的接口上。

  • 依赖抽象层(接口),而不是具体类。

除了上述的SOLID五个原则之外,PHP它还有很多原则,如下:

  1. "组合替代继承":这是说相对于继承,要更倾向于使用组合;
  2. "笛米特法则":这是说"你的类对其它类知道的越少越好";
  3. "共同封闭原则":这是说"相关类应该打包在一起";
  4. "稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";

不过我们要知道,这些原则并不是孤立存在的,而是紧密联系的,遵循一个原则的同时也就遵循了另外一个或多个原则;反之,违反了其中一个原则也很可能同时就违反了另外一个或多个原则。 设计模式是这些原则在一些特定场景的应用结果。因此,可以把设计模式看作"框架",把OOD原则看作"规范"。 在搞设计模式的过程中,我们要经常性的反思,这个设计模式体现了面向对象设计原则中的哪个或哪一些原则。

最后我要表达的就是,没人写一款程序能完全遵守SOLID原则,甚至有些设计模式是违反SOLID原则,至于如何权衡,就要看利是否大于弊或者是你是否需要了。

好啦,本次记录就到这里了。

如果感觉不错的话,请多多点赞支持哦。。。

相关文章: