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. Java 数组最佳指南,快收藏让它吃灰

    两年前,我甚至写过一篇文章,吐槽数组在 Java 中挺鸡肋的,因为有 List 谁用数组啊,现在想想那时候的自己好幼稚,好可笑.因为我只看到了表面现象,实际上呢,List 的内部仍然是通过数组实现的, ...

  2. android studio gradle的坑

    国产模拟器别国外的好用.非常傻瓜.有人推荐夜神. 之前用geom,下载Android都得半天.     每次做开发前,配置环境都要搞半天.尤其是想快速学习一个技术的话,光装环境都让人放弃了.最近想看一 ...

  3. P2034 选择数字——线性dp(单调队列优化)

    选择数字 题目描述 给定一行 \(n\) 个非负整数 \(a[1]...a[n]\) .现在你可以选择其中若干个数,但不能有超过 \(k\) 个连续的数字被选择.你的任务是使得选出的数字的和最大. 输 ...

  4. 线性DP之免费馅饼

    题目 思路 线性DP,思路很容易就能想到,f[i][k]数组定义为第i秒在k位置时从上一位置j转移过来的最优解,易得f[i][k]=max(f[i][k],f[i-1][j]+search(i,k)) ...

  5. 返回报文变成xml格式了!

    首先,google chrome之前有安装jsonview插件: 然后,自己弄springCloud项目,搭建eureka后,访问url发现返回报文变成xml格式了,一通摸索及查找,现整理如下: 1. ...

  6. 浅析Python垃圾回收机制!

    Python垃圾回收机制 目录 Python垃圾回收机制 1. 内存泄露 2. Python什么时候启动垃圾回收机制? 2.1 计数引用 2.2 循环引用 问题:引用计数是0是启动垃圾回收的充要条件吗 ...

  7. flask 源码专题(一):app.run()的背后

    当我们用Flask写好一个app后, 运行app.run()表示监听指定的端口, 对收到的request运行app生成response并返回. 现在分析一下, 运行app.run()后具体发生了什么事 ...

  8. java 数据结构(七):Collection接口

    1.单列集合框架结构|----Collection接口:单列集合,用来存储一个一个的对象* |----List接口:存储序的.可重复的数据. -->“动态”数组* |----ArrayList. ...

  9. CMDB04 /流程梳理、cmdb总结

    CMDB04 /流程梳理.cmdb总结 目录 CMDB04 /流程梳理.cmdb总结 1. 流程梳理 1.1 环境 1.2 远程连接服务器 1.3 向服务器上传文件 1.4 运维管理服务器 2. cm ...

  10. Django之重写用户模型

    django——重写用户模型 Django内建的User模型可能不适合某些类型的项目.例如,在某些网站上使用邮件地址而不是用户名作为身份的标识可能更合理. 1.修改配置文件,覆盖默认的User模型 D ...