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. Azure Monitor(二)Log Analytics

    一,引言( 前情回顾) Azure Monitor 包括 Log Analytics 和 Application Insights,其提供的高级工具适用于收集和分析遥测数据,以便最大程度地提高云和本地 ...

  2. 恕我直言你可能真的不会java第9篇-Stream元素的匹配与查找

    在我们对数组或者集合类进行操作的时候,经常会遇到这样的需求,比如: 是否包含某一个"匹配规则"的元素 是否所有的元素都符合某一个"匹配规则" 是否所有元素都不符 ...

  3. List集合-03.Vector

    3.Vector 3.1 UML图 3.2 Vector的特点 Object的数组存储元素 默认初始大小为10 public Vector() { this(10); } 线程安全,可以看到所有的数据 ...

  4. centos7安装Mysql爬坑记录

    centos7安装Mysql爬坑记录   查看是否已安装 使用下列命令查看是否已经安装过mysql/mariadb/PostgreSQL 如果未安装,不返回任何结果(ECS的centos镜像默认未安装 ...

  5. unity vscode 断点问题

    困扰了很久的vscode老莫名其妙的断到网络通信那里. 后来发现是因为起来了一个线程并且调用的unity API 导致. unity 线程中是禁止调用unity API 的. 删掉用 DateTime ...

  6. Yii2源码分析(一):入口

    写在前面,写这些随笔是记录下自己看Yii2源码的过程,可能会有些流水账,大部分解析放在注释里说明,由于个人水平有限,有不正确的地方还望斧正. web入口文件Index.php // 定义全局的常量,Y ...

  7. Qt-文件系统

    1  简介 参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=45 参考文档:<Qt教程.docx> 说明:本文主要介绍Qt的文件系统. ...

  8. GAN网络从入门教程(三)之DCGAN原理

    目录 DCGAN简介 DCGAN的特点 几个重要概念 下采样(subsampled) 上采样(upsampling) 反卷积(Deconvolution) 批标准化(Batch Normalizati ...

  9. 数据可视化之powerBI技巧(二十四)Power BI初学者刚见的错误,帮你轻松处理

    在学习PowerBI的过程中,尤其是刚接触的时候,不可避免的会遇到各种各样的错误,有时自己怎么检查都没法消除,不解决这个错误又没法进行下一步的工作,经常会搞的自己烦闷无比,不过最后通过自己的苦苦摸索. ...

  10. 机器学习实战基础(十五):sklearn中的数据预处理和特征工程(八)特征选择 之 Filter过滤法(二) 相关性过滤

    相关性过滤 方差挑选完毕之后,我们就要考虑下一个问题:相关性了. 我们希望选出与标签相关且有意义的特征,因为这样的特征能够为我们提供大量信息.如果特征与标签无关,那只会白白浪费我们的计算内存,可能还会 ...