PHP面向对象设计的五个基准原则是什么

PHP面向对象设计的五个基准原则是什么

S.O.L.I.D面向对象设计(OOD)的 5 个准则的首字母缩写 ,这些准则是由 Robert C. Martin 提出的, 他更为人所熟知的名字是 Uncle Bob。

这些准则使得开发出易扩展、可维护的软件变得更容易。也使得代码更精简、易于重构。同样也是敏捷开发和自适应软件开发的一部分。

S.O.L.I.D 意思是:

扩展出来的首字母缩略词看起来可能很复杂,实际上它们很容易理解。

  • S – 单一功能原则

  • O – 开闭原则

  • L – 里氏替换原则

  • I – 接口隔离原则

  • D – 依赖反转原则

接下来让我们看看每个原则,来了解为什么 S.O.L.I.D 可以帮助我们成为更好的开发人员。

单一职责原则

缩写是 S.R.P ,该原则内容是:

一个类有且只能有一个因素使其改变,意思是一个类只应该有单一职责.

例如,假设我们有一些图形,并且想要计算这些图形的总面积.是的,这很简单对不对?

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 '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 blocks ,然而这违背了开闭原则.

让这个 sum 方法变得更好的方式是将计算每个形状面积的代码逻辑移出 sum 方法,将其放进各个形状类中:

class Square {    public $length;    public function __construct($length) {        $this->length = $length;    }    public function area() {        return pow($this->length, 2);    }}

相同的操作应该被用来处理 Circle 类, 在类中添加一个 area 方法。 现在,计算任何形状面积之和应该像下边这样简单:

public function sum() {    $area = [];    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);    }}

在我们的 AreaCalculator 的 sum 方法中,我们可以检查提供的形状类的实例是否是 ShapeInterface 的实现,否则我们就抛出一个异常:

public function sum() {    $area = [];    foreach($this->shapes as $shape) {        if(is_a($shape, 'ShapeInterface')) {            $area[] = $shape->area();            continue;        }        throw new AreaCalculatorInvalidShapeException;    }    return array_sum($area);}

里氏替换原则

如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

这句定义的意思是说:每个子类或者衍生类可以毫无问题地替代基类/父类。

依然使用 AreaCalculator 类, 假设我们有一个 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 'Sum of the areas of provided shapes: ' . $this->calculator->sum();    }}

如果我们运行像这样一个例子:

$areas = new AreaCalulator($shapes);$volumes = new VolumeCalculator($solidShapes);$output = new SumCalculatorOutputter($areas);$output2 = new SumCalculatorOutputter($volumes);

程序不会出问题, 但当我们使用$output2 对象调用 HTML 方法时 ,我们接收到一个 E_NOTICE 错误,提示我们 数组被当做字符串使用的错误。

为了修复这个问题,只需:

public function sum() {    // logic to calculate the volumes and then return and array of output    return $summedData;}

而不是让VolumeCalculator 类的 sum 方法返回数组。

$summedData 是一个浮点数、双精度浮点数或者整型。

接口隔离原则

使用方(client)不应该依赖强制实现不使用的接口,或不应该依赖不使用的方法。

继续使用上面的 shapes 例子,已知拥有一个实心块,如果我们需要计算形状的体积,我们可以在 ShapeInterface 中添加一个方法:

interface ShapeInterface {    public function area();    public function volume();}

任何形状创建的时候必须实现 volume 方法,但是【平面】是没有体积的,实现这个接口会强制的让【平面】类去实现一个自己用不到的方法。

ISP 原则不允许这么去做,所以我们应该创建另外一个拥有 volume 方法的SolidShapeInterface 接口去代替这种方式,这样类似立方体的实心体就可以实现这个接口了:

interface ShapeInterface {    public function area();}interface SolidShapeInterface {    public function volume();}class Cuboid implements ShapeInterface, SolidShapeInterface {    public function area() {        //计算长方体的表面积    }    public function volume() {        // 计算长方体的体积    }}

这是一个更好的方式,但是要注意提示类型时不要仅仅提示一个 ShapeInterfaceSolidShapeInterface
你能创建其它的接口,比如 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 principle

PasswordReminder 类不应该关注你的应用使用了什么数据库,为了进一步解决这个问题,我们「面向接口写代码」,由于高等级和低等级模块都应该依赖于抽象,我们可以创建一个接口:

interface DBConnectionInterface {    public function connect();}

这个接口有一个连接数据库的方法,MySQLConnection 类实现该接口,在 PasswordReminder 的构造方法中不要直接将类型约束设置为 MySQLConnection 类,而是设置为接口类,这样无论你的应用使用什么类型的数据库,PasswordReminder 类都能毫无问题地连接数据库,且不违背 开闭原则

class MySQLConnection implements DBConnectionInterface {    public function connect() {        return "Database connection";    }}class PasswordReminder {    private $dbConnection;    public function __construct(DBConnectionInterface $dbConnection) {        $this->dbConnection = $dbConnection;    }}

从上面一小段代码,你现在能看出高等级和低等级模块都依赖于抽象了。

关于“PHP面向对象设计的五个基准原则是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

文章标题:PHP面向对象设计的五个基准原则是什么,发布者:亿速云,转载请注明出处:https://worktile.com/kb/p/24024

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
亿速云的头像亿速云认证作者
上一篇 2022年9月8日 下午10:53
下一篇 2022年9月8日 下午10:54

相关推荐

  • java常见log日志如何使用

    前言 log日志可以debug错误或者在关键位置输出想要的结果 java日志使用一般有原生logger、log4j、Slf4j等 一般的日志级别都有如下(不同日志不一样的方法参数,注意甄别) 参数 描述 OFF、ON 不输出或者输出所有级别信息,通常使用在setLevel方法中 FATAL 致命错误…

    2022年9月18日
    79200
  • mysql hint的概念是什么

    在mysql中,hint指的是“查询优化提示”,会提示优化器按照一定的方式来生成执行计划进行优化,让用户的sql语句更具灵活性;Hint可基于表的连接顺序、方法、访问路径、并行度等规则对DML(数据操纵语言,Data Manipulation Language)语句产生作用。 本教程操作环境:win…

    2022年9月21日
    1.4K00
  • coreldraw如何编辑文字

    coreldraw编辑文字的方法 1、首先找到你要修改的cdr文件。 2、打开软件,点击文件,打开。 3、在软件的左侧找到文字工具。 4、鼠标放到需要修改的文字上面,进行修改。 5、再次点击上方菜单栏的文件,保存即可。 感谢各位的阅读,以上就是“coreldraw如何编辑文字”的内容了,经过本文的学…

    2022年9月26日
    1.1K00
  • word字体放大如何弄

    word字体放大的方法 1、先选中需要放大的字体,然后点击开始菜单中的增大字号。 2、选中字体之后在悬浮框中点击字体大小就可以设置了。 3、选中需要放大的字体之后,点击上方菜单栏中的输入框,直接在里面输入你要的字号。 以上就是“word字体放大如何弄”这篇文章的所有内容,感谢各位的阅读!相信大家阅读…

    2022年9月10日
    50800
  • 如何用rank函数排名不重复

    用rank函数排名不重复的方法: 1、首先打开表格,进入你的表格。 2、然后在单元格中输入公式:=RANK(H2,H2:H47,0)可以看到名列前茅行43的排行。 3、然后将书本放在单元格右下角,然后向下拉。 4、最后就可以看到很多的重复排名了,但是没有第二和第三。 5、如果需要不重复就需要在ran…

    2022年8月30日
    4.0K00
  • 怎么搭建配置Docker私有仓库

    ⛳️ 1.Docker容器三要素 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之…

    2022年9月18日
    87500
  • ai如何转曲文件

    ai转曲文件的方法: 1、首先双击桌面的ai软件,然后打开。 2、之后去点击任务选项栏中的“选择”。 3、点击弹出菜单中的“全部”。 4、之后点击任务选项中的“文字”。 5、点击文字下面的“创建轮廓”即可。 以上就是“ai如何转曲文件”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很…

    2022年9月1日
    1.9K00
  • word怎么统一调整图片大小

    word图片统一调整大小的方法: 1、首先你需要打开word文档界面,在word插入图片,并选中一张图片。 2、选中图片后,用鼠标右击,这时会弹出下拉菜单,点击“大小和位置”选项。 3、此时会出现“布局”设置窗口,点击大小设置模块。 4、把设置界面中的“锁定纵横比”取消,然后重新设置图片的高度和宽度…

    2022年9月20日
    5.2K00
  • cad字体库怎么看

    cad字体库查看方法 1、右键CAD的桌面图标,找到属性一栏点击。 2、在打开的页面中,切换到快捷方式这一个栏目。 3、接着打开文件所在的位置。 4、找到Fonts文件夹。 5、Fonts文件夹就是CAD的字体库了。 以上就是“cad字体库怎么看”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这…

    2022年9月16日
    79000
  • MySQL外键约束知识点有哪些

    一、MySQL外键约束作用 外键约束(Foreign Key)即数据库中两个数据表之间的某个列建立的一种联系。这种联系通常是以实际场景中含义完全相同的字段所造成的。MySQL通过外键约束的引入,可以使得数据表中的数据完整性更强,也更符合显示情况。下面,我举一个例子来说明MySQL外键约束的作用。 假…

    2022年9月15日
    1.3K00
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部