From Apprentice To Artisan 翻译 18

Stella981
• 阅读 534

上一篇

Liskov Substitution Principle 里氏替换原则

Introduction 介绍

Don't worry, the Liskov Substitution Principle is a lot easier to understand than it sounds. This principle states that you should be able to use any implementation of an abstraction in any place that accepts that abstraction. But, let's make this a little simpler. In plain English, the principle states that if a class uses an implementation of an interface, it must be able to use any implementation of that interface without requiring any modifications.

别担心,里氏替换原则读起来吓人学起来简单。该原则要求:一个抽象的任意一个实现,可以被用在任何需要该抽象的地方。读起来绕口,用普通人的话来解释一下。该原则规定:如果某处代码使用了一个接口的一个实现类,那么在这里也可以直接使用该接口的任何其他实现类,不用做出任何修改。

Liskov Substitution Principle 里氏替换原则

This principle states that objects should be replaceable with instances of their sub-types without altering the correctness of that program.

该原则规定对象应该可以被该对象子类的实例所替换,并且不会影响到程序的正确性。

In Action 实践

To illustrate this principle, let's continue to use our OrderProcessor example from the previous chapter. Take a look at this method:

为了说明该原则,我们继续编写上一章节的OrderProcessor。看下面的方法:

<!-- lang:php -->
public function process(Order $order)
{
    // Validate order...
    $this->orders->logOrder($order);
}

Note that after the Order is validated, we log the order using the OrderReporsitoryInterface implementation. Let's assume that when our order processing business was young, we stored all of our orders in CSV format on the file system. Our only OrderRepositoryInterface implementation was a CsvOrderRepository. Now, as our order rate grows, we want to use a relational database to store them. So, let's look at a possible implementation of our new repository:

注意当我们的Order通过了验证,就被OrderRepositoryInterface的实现对象存储起来了。假设当我们的业务刚起步时,我们将订单存储在CSV格式的文件系统中。我们的OrderRepositoryInterface的实现类是CsvOrderRepository。现在,随着我们订单增多,我们想用一个关系数据库来存储订单。那么我们来看看新的订单资料库类该怎么编写吧:

<!-- lang: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
        ));
    }
}

Now, let's examine how we would have to consume this implementation:

现在我们来研究如何使用这个实现类:

<!-- lang:php -->
public function process(Order $order)
{
    // Validate order...

    if($this->repository instanceof DatabaseOrderRepository)
    {
        $this->repository->connect('root', 'password');
    }
    $this->repository->logOrder($order);
}

Notice that we are forced to check if our OrderRepositoryInterface is a database implementation from within our consuming processor class. If it is, we must connect to the database. This may not seem like a problem in a very small application, but what if the OrderRepositoryInterface is consumed by dozens of other classes? We would be forced to implement this "bootstrap" code in every consumer. This will be a headache to maintain and is prone to bugs, and if we forgot to update a single consumer our application will break.

注意在这段代码中,我们必须在资料库外部检查OrderRepositoryInterface的实例对象是不是用数据库实现的。如果是的话,则必须先连接数据库。在很小的应用中这可能不算什么问题,但如果OrderRepositoryInterface被几十个类调用呢?我们可能就要把这段“启动”代码在每一个调用的地方复制一遍又一遍。这让人非常头疼难以维护,非常容易出错误。一旦我们忘了将所有调用的地方进行同步修改,那程序恐怕就会出问题。

The example above clearly breaks the Liskov Substitution Principle. We were unable to inject an implementation of our interface without changing the consumer to call the connect method. So, now that we have identified the problem, let's fix it. Here is our new DatabaseOrderRepository implementation:

很明显,上面的例子没有遵循里氏替换原则。如果不附加“启动”代码来调用connect方法,则这段代码就没法用。好了,我们已经找到问题所在,咱们修好他。下面就是新的DatabaseOrderRepository

<!-- lang: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
        ));
    }
}

Now our DatabaseOrderRepository is managing the connection to the database, and we can remove our "bootstrap" code from the consuming OrderProcessor:

现在DatabaseOrderRepository掌管了数据库连接,我们可以把“启动”代码从OrderProcessor移除了:

<!-- lang:php -->
public function process(Order $order)
{
    // Validate order...
    
    $this->repository->logOrder($order);
}

With this modification, we can now use our CsvOrderRepository or DatabaseOrderRepository without modifying the OrderProcessor consumer. Our code adheres to the Liskov Substitution Principle! Take note that many of the architecture concepts we have discussed are related to knowledge. Specifically, the knowledge a class has of its "surrounding", such as the peripheral code and dependencies that help a class do its job. As you work towards a robust application architecture, limiting class knowledge will be a recurring and important theme.

这样一改,我们就可以想用CsvOrderRepository也行,想用DatabaseOrderRepository也行,不用改OrderProcessor一行代码。我们的代码终于实现了里氏替换原则!要注意,我们讨论过的许多架构概念都和_知识_相关。具体讲,知识就是一个类和它所具有的_周边领域_,比如用来帮助类完成任务的外围代码和依赖。当你要制作一个容错性强大的应用架构时,限制类的_知识_是一种常用且重要的手段。

Also note the consequence of violating the Liskov Substitution principle with regards to the other principle we have covered. By breaking this principle, the Open Closed principle must also be broken, as, if the consumer must check for instances of various child classes, this consumer must be changed each time there is a new child class.

还要注意如果不遵守里氏替换原则,那后果可能会影响到我们之前已经讨论过的其他原则。不遵守里氏替换原则,那么开放封闭原则一定也会被打破。因为,如果调用者必须检查实例属于哪个子类的,那一旦有个新的子类,调用者就得做出改变。(译者注:这就违背了对修改封闭的原则。)

Watch For Leaks 小心遗漏

You have probably noticed that this principle is closely related to the avoidance of "leaky abstractions", which were discussed in the previous chapter. Our database repository's leaky abstraction was our first clue that the Liskov Substitution Principle was being broken. Keep an eye out for those leaks!

你可能注意到这个原则和上一章节提到的“抽象的漏洞”密切相关。我们的数据库资料库的抽象漏洞就是没有遵守里氏替换原则的第一迹象。要留意那些漏洞!

下一篇

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这