企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### 简介 别担心,里氏替换原则名字起的高大上,但是其实很简单。该原则可以描述为:一个抽象的任意实现都可以在声明该抽象的地方替换它。读起来有点绕口,通俗点说就是:如果一个类使用了某个接口的实现,那么一定可以通过该接口的其它实现来替换它,不用做出任何修改。 > 里氏替换原则规定对象可以被其子类的实例所替换,并且不会影响到程序的正确性。 ### 实战 为了说明该原则,我们继续使用前面编写的 `OrderProcessor` 类作为示例。请看下面的方法: ```php public function process(Order $order) { // Validate order... $this->orders->logOrder($order); } ``` 注意,当 `Order` 通过验证后,我们就会通过 `OrderRepositoryInterface` 的实现类实例将其记录下来。假设订单处理业务刚起步时,我们将所有订单都存储到了 CSV 格式的文件系统中。对应的,我们的 `OrderRepositoryInterface` 的实现类就应该是`CsvOrderRepository`。现在,随着订单增多,我们想用一个关系数据库来存储订单。下面我们就来看看新的订单资料库类该怎么编写吧: ```php class DatabaseOrderRepository implements OrderRepositoryInterface { protected $connection; public function connect($username, $password) { $this->connection = new DatabaseConnection($username, $password); } public function logOrder(Order $order) { $this->connection->run('insert into orders values (?, ?)', array( $order->id, $order->amount )); } } ``` 现在,我们来研究如何使用这个实现类: ```php public function process(Order $order) { // Validate order... if($this->repository instanceof DatabaseOrderRepository) { $this->repository->connect('root', 'password'); } $this->repository->logOrder($order); } ``` 注意在这段代码中,我们不得不在调用的地方检查 `OrderRepositoryInterface` 接口是否是通过数据库实现的。如果是的话,则必须连接到数据库。在很小的应用中,这可能看起来没什么问题,但如果`OrderRepositoryInterface` 在很多类中被调用呢?我们可能就要把这段「启动」代码在每一个调用的地方重复实现。这让人非常头疼,不仅难以维护,而且非常容易出错误,并且一旦我们忘了将所有调用的地方进行同步修改,那程序恐怕就会出问题。 很明显,上面的例子违背了里氏替换原则。因为我们不能在不修改调用方代码的情况下注入接口的实现。所以,既然已经定位到问题所在,接下来就要修复它。下面就是新的 `DatabaseOrderRepository` 实现: ```php class DatabaseOrderRepository implements OrderRepositoryInterface { protected $connector; public function __construct(DatabaseConnector $connector) { $this->connector = $connector; } public function connect() { return $this->connector->bootConnection(); } public function logOrder(Order $order) { $connection = $this->connect(); $connection->run('insert into orders values (?, ?)', array( $order->id, $order->amount )); } } ``` 现在 `DatabaseOrderRepository` 自己接管了数据库连接,这样我们就可以把数据库「启动」代码从 `OrderProcessor` 中移除了: ```php public function process(Order $order) { // Validate order... $this->repository->logOrder($order); } ``` 这样一改,我们就可以在 `CsvOrderRepository` 和 `DatabaseOrderRepository` 实现之间进行切换了,不用对 `OrderProcessor` 做任何修改。我们的代码终于实现了里氏替换原则!需要注意的是,我们讨论过的许多架构概念都和「知识」相关。具体来说,一个类所具备的「周边」知识,例如外围代码和依赖,会帮助这个类完成它的工作。当你想要构建一个健壮的大型应用时,限制类的知识会是一个反复出现、非常重要的主题。 还要注意如果不遵守里氏替换原则,那么可能会影响到我们之前已经讨论过的其他原则。不遵守里氏替换原则,那么开放封闭原则一定也会被打破。因为,如果调用者必须检查实例属于哪个子类,则一旦有了新的子类,调用者就得做出改变。 > 你可能已经注意到这个原则和前面提到的「泄露抽象实现细节」密切相关。数据库仓库类的实现细节泄露就是里氏替换原则被破坏的第一迹象。所以要时刻留意那些泄露!