PHP 设计模式学习 设计模式的七大原则(三)

268 阅读1分钟

开闭原则

开闭原则是编程中最基础,最重要的设计原则

<?php
/**
 * IBook为书籍接口,NovelBook为小说书籍类(实现书籍接口)
 */


interface IBook
{
    public function getName();

    public function getPrice();

    public function getAuthor();
}


class NovelBook implements IBook
{
    private $name;
    private $price;
    private $author;

    public function __construct($name, $price, $author)
    {
        $this->name = $name;
        $this->price = $price;
        $this->author = $author;
    }


    public function getAuthor()
    {
        return $this->author;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getPrice()
    {
        return $this->price;
    }
}

问题

项目投入使用后,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢?

我们有下面三种方法可以解决此问题:

  • 修改接口 在IBook接口中,增加一个方法getOffPrice(),专门用于进行打折处理,所有的实现类实现此方法。但是对于这样的一个修改方式,首先,作为接口,IBook应该稳定且可靠,不应该经常发生改变,否则接口作为契约的作用就失去了。其次,并不是所有的书籍都需要打折销售,仅仅因为NovelBook打折销售就修改接口使所有书都必须实现打折销售的逻辑,显然与实际业务不符。因此,此方案否定。

  • 修改实现类 修改NovelBook类的方法,直接在getPrice()方法中实现打折处理。此方法是有问题的,例如我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?这不是有问题吗?当然我们也可以再增加getOffPrice()方法,这也是可以实现其需求,但是这就有二个读取价格的方法,因此,该方案也不是一个最优方案。

  • 通过扩展实现变化 我们可以增加一个子类OffNovelBook(继承自NovelBook),覆写getPrice()方法。此方法修改少,对现有的代码没有影响,风险少,是最好的办法,同时也符合开闭原则。

优化方案

class  OffNovelBook extends NovelBook
{
    public function getPrice()
    {
        if ($this->price > 40) {
            return $this->price * 0.8;
        } else {
            return $this->price * 0.9;
        }
    }
}

总结

  • 一个软件实体如类,模块,和函数应该对扩展开放(提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
  • 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则

迪米特原则(最少知道原则)

一个对象应该对其他对象保持最少了解

<?php

/** 
 * 体育老师上课前要体育委员确认一下全班女生到了多少位,也就是体育委员清点女生的人数。类图如下:
 */


class Teacher
{
    //老师对体育委员发一个命令,让其清点女生人数
    public function command(GroupLeader $groupLeader)
    {
        $listGirls = Array();
        //初始化女生
        for ($i = 0; $i < 20; $i++) {
            $listGirls[] = (new Girl());
        }
        //告诉体育委员开始清点女生人数
        $groupLeader->countGirls($listGirls);
    }
}

class GroupLeader
{
    //清点女生数量
    public function countGirls($listGirls)
    {
        echo "女生人数是:" . count($listGirls);
    }
}

class Girl
{

}

问题

我们再回头看Teacher类,Teacher类只有一个朋友类GroupLeader,Girl类不是朋友类, 但是Teacher与Girl类通信了,这就破坏了Teacher类的健壮性, Teacher类的方法竟然与一个不是自己的朋友类Girl类通信,这是不允许的,严重违反了迪米特原则。

优化方案

<?php

/**
 * 对程序修改,把Teacher中对Girl群体的初始化移动到场景类中,同时在GroupLeader中增加对Girl的注入,
 * 避开了Teacher类对陌生类Girl的访问,降低了系统间的耦合,提高了系统的健壮性。
 */


class Teacher
{
    //老师对体育委员发一个命令,让其清点女生人数
    public function command(GroupLeader $groupLeader)
    {
        $groupLeader->countGirls();
    }
}

class GroupLeader
{

    protected $listGirls;

    public function __construct($listGirls)
    {
        $this->listGirls = $listGirls;
    }

    //清点女生数量
    public function countGirls()
    {
        echo "女生人数是:" . count($this->listGirls);
    }
}

class Girl
{

}

$listGirls = Array();
//初始化女生
for ($i = 0; $i < 20; $i++) {
    $listGirls[] = (new Girl());
}

$teacher = new Teacher();
//老师给体育委员发清点女生人数的命令
$teacher->command(new GroupLeader($listGirls));

总结

  • 类与类之间关系越密切耦合越大
  • 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法,不对外泄露其他任何信息
  • 迪米特法则还有个更简单的定义:只与直接朋友通讯 直接朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象是朋友关系,耦合的方式有很多,依赖,关联,组合,聚合等。其中,我们称出现的成员变量,方法参数,方法返回值为直接朋友。也就是陌生的类最好不要以局部变量的形式出现在类的内部

注意事项

  • 迪米特法则的核心是降低类之间的耦合
  • 但是注意:由于每一个类都减少了不必要的依赖,因此迪米特只是降低了类之间的耦合关系,并不是要求完全没有依赖关系

合成复用原则

原则是尽量使用合成/聚合的方式,而不是使用继承

<?php

abstract class DBConnection
{
    public abstract function getConnection();
}

class MySQLConnection extends DBConnection
{

    public function getConnection()
    {
        return "MySQL数据库连接";
    }
}

class PostgreSQLConnection extends DBConnection
{

    public function getConnection()
    {
        return "PostgreSQL数据库连接";
    }
}

class ProductDao
{

    private $dbConnection;

    public function setDbConnection(DBConnection $dbConnection)
    {
        $this->dbConnection = $dbConnection;
    }

    public function addProduct()
    {
        $conn = $this->dbConnection->getConnection();
        echo "使用" . $conn . "增加产品";
    }

}

$productDao = new ProductDao();
$productDao->setDbConnection(new PostgreSQLConnection());
$productDao->addProduct();

设计模式核心思想

  1. 找出应用中可能需要变化之处,把他独立出来,不要和那些不需要变化的代码混淆在一起
  2. 针对接口编程,而不是针对实现编程
  3. 为了交互对象之间的松耦合设计而努力