S.O.L.I.D是Robert C. Martin提出的前五个面向对象设计(OOD)原则的首字母缩写,他更为人所熟知的名字是Uncle Bob。
 
将这些原理结合在一起,可使程序员轻松开发易于维护和扩展的软件。它们还使开发人员可以轻松避免代码异味,轻松重构代码,并且是敏捷或自适应软件开发的一部分。
 
注意这只是一篇简单的“欢迎使用_ S.O.L.I.D ”,它只是阐明了S.O.L.I.D是什么.。
 
更多学习内容可以访问从码农成为架构师的修炼之路

S.O.L.I.D代表:

首字母缩略词在扩展时可能看起来很复杂,但是却很容易掌握。
  • S - 单一责任原则
  • O - 开闭原理
  • L - Liskov替代原理
  • I - 接口隔离原理
  • D - 依赖倒置原则
让我们分别看一下每个原理,了解一下S.O.L.I.D为什么可以帮助使我们成为更好的开发人员。

单一责任原则

SRP的简称-此原则指出:
一个类有且只能有一个因素使其改变,意思是一个类只应该有单一职责.
例如,假设我们有一些形状,我们想对形状的所有区域求和。好吧,这很简单对吧?
class Circle {
public $radius; public function construct($radius) {
$this->radius = $radius;
}
} class Square {
public $length; public function construct($length) {
$this->length = $length;
}
}
首先,我们创建形状类,并让构造函数设置所需的参数。接下来,我们继续创建AreaCalculator类,然后编写逻辑以总结所有提供的形状的面积。
class AreaCalculator {

    protected $shapes;

    public function __construct($shapes = array()) {
$this->shapes = $shapes;
} public function sum() {
// logic to sum the areas
} public function output() {
return implode('', array(
"",
"Sum of the areas of provided shapes: ",
$this->sum(),
""
));
}
}
要使用AreaCalculator类,我们只需实例化该类并传递形状数组,然后在页面底部显示输出。
$shapes = array(
new Circle(2),
new Square(5),
new Square(6)
); $areas = new AreaCalculator($shapes); echo $areas->output();
输出方法的问题在于AreaCalculator处理逻辑以输出数据。因此,如果用户希望将数据输出为json或其他内容怎么办呢?
 
所有这些逻辑都将由AreaCalculator类处理,这是SRP所反对的。在AreaCalculator类应该只提供总结形状的区域,它不应该关心用户是否希望JSON或HTML。
 
因此,要解决此问题,你可以创建一个SumCalculatorOutputter类,并使用它来处理处理所有提供的形状的总面积如何显示所需的任何逻辑。
 
该SumCalculatorOutputter类会的工作是这样的:
$shapes = array(
new Circle(2),
new Square(5),
new Square(6)
); $areas = new AreaCalculator($shapes);
$output = new SumCalculatorOutputter($areas); echo $output->JSON();
echo $output->HAML();
echo $output->HTML();
echo $output->JADE();
现在,SumCalculatorOutputter类现在可以处理将数据输出到用户所需的任何逻辑。

开闭原理

对象和实体应该对扩展开放,但是对修改关闭。
这只是意味着一个类应该易于扩展,而无需修改类本身。让我们看一下AreaCalculator类,尤其是sum方法。
public function sum() {
foreach($this->shapes as $shape) {
if(is_a($shape, 'Square')) {
$area[] = pow($shape->length, 2);
} else if(is_a($shape, 'Circle')) {
$area[] = pi() * pow($shape->radius, 2);
}
} return array_sum($area);
}
如果我们希望sum方法能够对更多形状的区域求和,则必须添加更多if / else块,这违背了Open-closed原理。
 
我们可以使这种求和方法更好的一种方法是从求和方法中删除用于计算每个形状的面积的逻辑,并将其附加到形状的类中。
class Square {
public $length; public function __construct($length) {
$this->length = $length;
} public function area() {
return pow($this->length, 2);
}
}
对Circle类应该做同样的事情,应该添加一个area方法。现在,要计算提供的任何形状的总和应该很简单:
public function sum() {
foreach($this->shapes as $shape) {
$area[] = $shape->area();
} return array_sum($area);
}
现在,我们可以创建另一个形状类,并在计算总和时传递它,而不会破坏我们的代码。但是,现在又出现了另一个问题,我们如何知道传递到AreaCalculator中的对象实际上是一个形状,或者该形状是否具有名为area的方法?
 
编码接口是S.O.L.I.D不可或缺的一部分,一个简单的示例是我们创建一个接口,每种形状都可以实现:
interface ShapeInterface {
public function area();
} class Circle implements ShapeInterface {
public $radius; public function __construct($radius) {
$this->radius = $radius;
} public function area() {
return pi() * pow($this->radius, 2);
}
}
在我们的AreaCalculatorsum方法中,我们可以检查所提供的形状是否实际上是ShapeInterface的实例,否则我们抛出异常:
public function sum() {
foreach($this->shapes as $shape) {
if(is_a($shape, 'ShapeInterface')) {
$area[] = $shape->area();
continue;
} throw new AreaCalculatorInvalidShapeException;
} return array_sum($area);
}

Liskov替代原则

如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
所有这一切都说明,每个子类/派生类都可以替代其基类/父类。
 
仍然使用OutAreaCalculator类,例如我们有一个VolumeCalculator类,它扩展了AreaCalculator类:
class VolumeCalculator extends AreaCalulator {
public function construct($shapes = array()) {
parent::construct($shapes);
} public function sum() {
// logic to calculate the volumes and then return and array of output
return array($summedData);
}
}
在SumCalculatorOutputter类中:
class SumCalculatorOutputter {
protected $calculator; public function __constructor(AreaCalculator $calculator) {
$this->calculator = $calculator;
} public function JSON() {
$data = array(
'sum' => $this->calculator->sum();
); return json_encode($data);
} public function HTML() {
return implode('', array(
'',
'Sum of the areas of provided shapes: ',
$this->calculator->sum(),
''
));
}
}
如果我们尝试运行这样的一个例子:
$areas = new AreaCalculator($shapes);
$volumes = new AreaCalculator($solidShapes); $output = new SumCalculatorOutputter($areas);
$output2 = new SumCalculatorOutputter($volumes);
该程序不会出问题,但是当我们在$ output2对象上调用HTML方法时,会收到E _ NOTICE错误,通知我们数组转换为字符串。
 
若要解决此问题,而不是从VolumeCalculator类的sum方法返回数组,你应该简单地:
public function sum() {
// logic to calculate the volumes and then return and array of output
return $summedData;
}
求和后的数据为浮点,双精度或整数。

接口隔离原理

使用方(client)不应该依赖强制实现不使用的接口,或不应该依赖不使用的方法。
仍然使用形状示例,我们知道我们也有实体形状,因此由于我们还想计算形状的体积,因此可以向ShapeInterface添加另一个协定:
interface ShapeInterface {
public function area();
public function volume();
}
我们创建的任何形状都必须实现volume方法,但是我们知道正方形是扁平形状并且它们没有体积,因此此接口将强制Square类实现一种不使用的方法。
 
ISP 原则不允许这么去做,所以我们应该创建另外一个拥有 volume 方法的 SolidShapeInterface 接口去代替这种方式,这样类似立方体的实心体就可以实现这个接口了:
interface ShapeInterface {
public function area();
} interface SolidShapeInterface {
public function volume();
} class Cuboid implements ShapeInterface, SolidShapeInterface {
public function area() {
//计算长方体的表面积
} public function volume() {
// 计算长方体的体积
}
}
这是一种更好的方法,但要注意的是在类型提示这些接口时要注意,而不是使用ShapeInterface或SolidShapeInterface。
您可以创建另一个接口,也许是ManageShapeInterface,并在平面和实体形状上都实现它,这样您就可以轻松地看到它具有用于管理形状的单个API。例如:
interface ManageShapeInterface {
public function calculate();
} class Square implements ShapeInterface, ManageShapeInterface {
public function area() { /Do stuff here/ } public function calculate() {
return $this->area();
}
} class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {
public function area() { /Do stuff here/ }
public function volume() { /Do stuff here/ } public function calculate() {
return $this->area() + $this->volume();
}
}
现在在 AreaCalculator 类中,我们可以很容易地用 calculate 替换对 area 方法的调用,并检查对象是否是 ManageShapeInterface 的实例,而不是 ShapeInterface 。

依赖倒置原则

最后但并非最不重要的一点是:
实体必须依赖于抽象而不依赖于具体。它指出高级模块一定不能依赖于低级模块,而应该依赖于抽象。
这也许听起来让人头大,但确实很容易理解。该原理允许去耦,这个例子似乎是解释该原理的最佳方法:
class PasswordReminder {
private $dbConnection; public function __construct(MySQLConnection $dbConnection) {
$this->dbConnection = $dbConnection;
}
}
首先,MySQLConnection是低等级模块,然而PasswordReminder是高等级模块,但是根据 S.O.L.I.D. 中 D 的解释:依赖于抽象而不依赖与实现, 上面的代码段违背了这一原则,因为PasswordReminder类被强制依赖于MySQLConnection类。
 
以后,如果您要更改数据库引擎,则还必须编辑PasswordReminder类,从而违反了Open-close原理。
 
该PasswordReminder类不应该关心什么数据库应用程序使用,以解决这个问题,我们再次“代码的接口”,因为高层次和低层次的模块应该依赖于抽象,我们可以创建一个界面:
interface DBConnectionInterface {
public function connect();
}
该接口具有一个connect方法,而MySQLConnection类实现了此接口,而且也没有直接在PasswordReminder的构造函数中直接提示MySQLConnection类,而是改为提示该接口,无论您的应用程序使用哪种数据库类型,PasswordReminder类可以轻松连接到数据库而不会出现任何问题,并且不会违反OCP。
class MySQLConnection implements DBConnectionInterface {
public function connect() {
return "Database connection";
}
} class PasswordReminder {
private $dbConnection; public function __construct(DBConnectionInterface $dbConnection) {
$this->dbConnection = $dbConnection;
}
}
根据上面的小片段,您现在可以看到高级模块和低级模块都依赖于抽象。

结论

老实说,乍一看S.O.L.I.D似乎很少,但是通过不断使用和遵守其准则,它成为你和你的代码的一部分,可以轻松地对其进行扩展,修改,测试和重构,而不会出现任何问题。

interface ManageShapeInterface {    public function calculate();}
class Square implements ShapeInterface, ManageShapeInterface {    public function area() { /Do stuff here/ }
    public function calculate() {        return $this->area();    }}
class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {    public function area() { /Do stuff here/ }    public function volume() { /Do stuff here/ }
    public function calculate() {        return $this->area() + $this->volume();    }}

SOLID:面向对象设计的前五项原则的更多相关文章

  1. SOLID (面向对象设计) 基本原则

      SOLID (面向对象设计) 基本原则    在 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特•C•马丁在21世纪早期[1] 引入的记忆术首字母缩略 ...

  2. zz SOLID (面向对象设计)

    SOLID (面向对象设计) 维基百科,自由的百科全书 跳到导航 跳到搜索 在程序设计领域, SOLID(单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期[1] ...

  3. 面向对象设计(OOD)七大原则

    这篇文章我会不停的维护它,它将会越来越长,但它是关于我在面向对象中的一些学习的思考心得.希望对自己对各位都能实用处.     开篇前,说明一下写这篇文章的原因.原因是由于设计模式.由于设计模式里的各种 ...

  4. 面向对象设计之SRP(单一职责)原则

    SRP设计原则面向对象类设计的第一个原则,最优先考虑的因素 一个类应该有且仅有一个职责.所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因 引起该类变化,其 ...

  5. Java成长第五集--面向对象设计的五大原则

    S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写.以下图说明: 下面就个人的理解来说说这五大原则的含义到 ...

  6. S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则

    注:以下图片均来自<如何向妻子解释OOD>译文链接:http://www.cnblogs.com/niyw/archive/2011/01/25/1940603.html      < ...

  7. 【OOAD】面向对象设计原则概述

    软件的可维护性和可复用性 知名软件大师Robert C.Martin认为一个可维护性(Maintainability) 较低的软件设计,通常由于如下4个原因造成: 过于僵硬(Rigidity)  ...

  8. 【设计模式系列】之OO面向对象设计七大原则

    1  概述 本章叙述面向向对象设计的七大原则,七大原则分为:单一职责原则.开闭原则.里氏替换原则.依赖倒置原则.接口隔离原则.合成/聚合复用原则.迪米特法则. 2  七大OO面向对象设计 2.1 单一 ...

  9. SOLID面向对象的五个设计原则,留空待学习。

     SOLID面向对象的五个设计原则对于开发人员非常重要,其身影在任何大中型软件项目中随处可见,建议必须掌握并灵活应用.此五原则分别为:     单一职责原则(Single Resposibility ...

随机推荐

  1. Vue组件篇——Vue3.0中使用高德地图

    VUE-CLI 3.0 中配置高德地图 在项目开发中,地图组件 1.首先,需要注册高德开放平台的账号,并在[应用管理]页面[创建新应用],为应用添加Key值 高德开放平台:https://lbs.am ...

  2. 2020/6/10 JavaScript高级程序设计 BOM

    BOM(浏览器对象模型):提供用于访问浏览器的对象. 8.1 window对象 window是BOM的核心对象,表示浏览器的一个实例. JavaScript访问浏览器窗口的接口 ECMAScript规 ...

  3. 入门大数据---Storm搭建与应用

    1.Storm在Linux环境配置 主机名 tuge1 tuge2 tuge3 部署环境 Zookeeper/Nimbus Zookeeper/Supervisor Zookeeper/Supervi ...

  4. 数据库连接池 --Druid 连接工具类创建_JDBCUtils

    package com.itheima.jdbc_druid; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.s ...

  5. Oracle Solaris 10图文安装

    文章目录 1. 虚拟机软件 2. solaris 10镜像 3. 安装OS 4. 允许远程使用root用户登录SSH 5. bash配置 5.1. 修改bash 5.2. 修改提示符 6. CRT连接 ...

  6. CenterOS的安装配置(配图解)

    CenterOS的安装配置 一.    配置虚拟机 打开Virtual Machine(虚拟机),点击create new virtual machine 进入新建虚拟机向导页面,选择[Custom( ...

  7. 每日一题 - 剑指 Offer 44. 数字序列中某一位的数字

    题目信息 时间: 2019-07-01 题目链接:Leetcode tag: 规律 难易程度:中等 题目描述: 数字以0123456789101112131415-的格式序列化到一个字符序列中.在这个 ...

  8. elasticsearch集群配置 (Tobe Continue)

    elasticsearch集群配置 (Tobe Continue)   准备 首先需要在每个节点有可以正常启动的单节点elasticsearch   elasticsearch集群配置仅需要在elas ...

  9. web前端知识点(JavaScript篇)

    call,apply,bind call,apply,bind这三者的区别,及内部实现原理,点这里 promise promise函数的内部实现原理,点这里 闭包 闭包就是能够读取其他函数内部变量的函 ...

  10. 「单调队列优化DP」P2034 选择数字

    「单调队列优化DP」P2034 选择数字 题面描述: 给定一行n个非负整数a[1]..a[n].现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大. 输入格 ...