开闭原则
开闭原则是编程中最基础,最重要的设计原则
<?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();
设计模式核心思想
- 找出应用中可能需要变化之处,把他独立出来,不要和那些不需要变化的代码混淆在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力