数据模型-主题域_空对象模式-域模型中的多态

数据模型-主题域

While certainly a far cry from being canonical, it can be said that Orthogonality is the quintessence of software systems that rest on the foundations of “good design”, where the constituent modules are neatly decoupled from one another, making the system less vulnerable to rigidity and fragility issues. Of course, it’s easier to chatter on about the benefits that orthogonal systems than getting an actual system up and running in production; the process is often a pursuit of utopia.

虽然肯定与规范相差甚远,但是可以说, 正交性是建立在“良好设计”基础上的软件系统的精髓所在,在这些系统中,组成模块之间相互巧妙地分离,从而使系统不易受到刚性的影响。和脆弱性问题。 当然,谈论正交系统的好处比在生产环境中启动和运行实际系统要容易得多。 这个过程通常是对乌托邦的追求。

Even though, the implementation of highly-decoupled components in a system is everything but a utopian concept. The use of several programming concepts, such as Polymorphism, permit one to design flexible programs whose parts can be switched over at runtime and whose dependencies can be expressed in the form of abstractions rather than concrete implementations. I’d dare say the old “Programming to Interfaces” mantra has gained ubiquitous adoption over time, regardless of if we’re talking about implementing infrastructure or application logic.

即使在系统中实现高度解耦的组件也不是乌托邦式的概念。 使用诸如Polymorphism之类的几种编程概念可以使人们设计出灵活的程序,其部分可以在运行时进行切换,并且其依赖关系可以以抽象的形式而不是具体的实现方式来表达。 我敢说,随着时间的流逝,旧的“编程到接口”的口号无处不在,无论我们是在谈论实现基础架构还是应用程序逻辑。

Things, however, are radically different when stepping on the terrain of Domain Models. And to be frank, this is a predictable scenario. After all, why should a web of interconnected objects, whose data and behavior are constrained to well-defined business rules, be polymorphic? It doesn’t make much sense per se. There are a few exceptions to this rule, though, that might be eventually applicable to this case in particular.

但是,在踏入领域模型领域时,情况就大不相同了。 坦率地说,这是可以预见的。 毕竟,为什么互连对象的网络(其数据和行为仅限于定义良好的业务规则)为何是多态的? 它本身没有多大意义。 但是,此规则有一些例外,最终可能会特别适用于此情况。

The first exception is the use of Virtual Proxies, which effectively share the same interface as the one implemented by the actual domain objects. The other exception is the so-called “Null Case”, a special situation where an operation might end up assigning or returning a null value instead of a nicely populated entity. In a traditional, non-polymorphic approach, the consumer of the model must check for these “evil” nulls and handle the condition gracefully, thus generating an explosion of conditionals throughout your code.

第一个例外是使用虚拟代理 ,该代理实际上共享与实际域对象实现的接口相同的接口。 另一个例外是所谓的“空情况”,这是一种特殊情况,其中操作可能最终分配或返回空值而不是填充好的实体。 在传统的非多态方法中,模型的使用者必须检查这些“邪恶”的空值并优雅地处理条件,从而在整个代码中产生大量的条件。

Fortunately, this seemingly-tangled situation can be sorted out simply by creating a polymorphic implementation of the domain object, which would implement the same interface as the one of the object in question, only that its methods wouldn’t do anything, therefore offloading client code from doing repetitive checks for ugly null values when the operation is executed. Not surprisingly, this approach is a design pattern called Null Object, which takes the advantages of Polymorphism to extremes.

幸运的是,可以通过简单地通过创建域对象的多态实现来解决这种看似纠结的情况,该实现将实现与所讨论对象的接口相同的接口,只是其方法不会做任何事情,因此减轻了客户端负担代码在执行操作时重复检查丑陋的null值。 毫不奇怪,这种方法是一种称为Null Object的设计模式,它将多态性的优势发挥到了极致。

In this article I’ll demonstrate the pattern’s benefits in a few situations and show you how sweet they cling to a polymorphic approach.

在本文中,我将演示在某些情况下该模式的好处,并向您展示它们对多态方法的坚持。

处理非多态条件 (Handling Non-Polymorphic Conditions)

As one might expect, there are several paths to tread when showcasing the niceties of the Null Object pattern. One that I find particularly straightforward is the implementation of a data mapper which may eventually return a null value from a generic finder. Let’s say that we’ve managed to create a skeletal domain model made up of just one single user entity. The interface, along with its class, look like this:

正如人们可能期望的那样,展示Null Object模式的精妙之处时要走几条路。 我发现特别简单的一种方法是数据映射器的实现,该映射器最终可能会从通用查找器返回空值。 假设我们设法创建了一个仅由一个用户实体组成的骨架域模型。 该接口及其类如下所示:

<?php
namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();
     
    public function setName($name);
    public function getName();
    
    public function setEmail($email);
    public function getEmail();
}
<?php
namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }
    
    public function getName() {
        return $this->name;
    }
    
    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }
    
    public function getEmail() {
        return $this->email;
    }
}

The User class is a reactive structure which implements some mutators/accessors in order to define the data and behavior of a few users.

User类是一种React式结构,它实现一些更改器/访问器,以便定义一些用户的数据和行为。

With this contrived domain class in place, now we can go a step further and define a basic Data Mapper which will keep our domain model and the data access layer isolated from each other.

有了这个人为设计的领域类,现在我们可以更进一步,定义一个基本的Data Mapper ,它将使我们的领域模型和数据访问层彼此隔离。

<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;
    
    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }
    
    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }
    
    private function createUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}

The first thing that should have popped out is that the mapper’s fetchById() method is the naughty boy on the block as it effectively returns a null if no user in the database matches the given ID. For obvious reasons, this clunky condition makes the client code go through the hassle of checking for the null value every time the mapper’s finder is called.

应该弹出的第一件事是,映射程序的fetchById()方法是块上的顽皮男孩,因为如果数据库中没有用户匹配给定ID,它实际上将返回null。 出于明显的原因,这种笨拙的条件使每次调用映射器的查找器时,客户端代码都要经历检查空值的麻烦。

<?php
use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperUserMapper;
    
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");

$userMapper = new UserMapper($adapter);

$user = $userMapper->fetchById(1);

if ($user !== null) {
    echo $user->getName() . " " . $user->getEmail();
}

At first glance that wouldn’t be much of an issue, so long as the check is done in a single place. But wouldn’t you hit your head against a brick wall if the same lines appear across multiple page controllers or inside a service layer? Before you know it, the seemingly-innocent null returned by the mapper produces a plague of repetitive conditions, an ominous sign of bad design.

乍一看,只要检查在一个地方完成,就不会有太大的问题。 但是,如果跨多个页面控制器或在服务层内部出现相同的行,您是否会撞墙撞头? 在不知不觉中,映射器返回的看似纯真的null会造成重复条件的困扰,这是不良设计的不祥之兆。

从客户端代码中删除条件 (Removing Conditionals from Client Code)

There’s no need for anguish, however, as this is exactly the kind of case where the Null Object pattern shows why Polymorphism is a godsend. If we want to get rid of those pesky conditionals once and for all, we can implement a polymorphic version of the previous User class.

但是,不需要痛苦,因为这正是Null Object模式显示为什么多态是天赐之物的情况。 如果我们想要一劳永逸地摆脱那些讨厌的条件,我们可以实现先前User类的多态版本。

<?php
namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() { }
    
    public function setName($name) { }
    public function getName() { }
    
    public function setEmail($email) { }
    public function getEmail() { }
}

If you were expecting a full-fledged entity class packaging all sort of bells and whistles, I’m afraid you’ll mighty disappointed. The “null” version of the entity conforms to the corresponding interface, but the methods are empty wrappers with no actual implementation.

如果您期望一个成熟的实体类包装各种花哨的内容,恐怕您会感到失望。 实体的“空”版本符合相应的接口,但是方法是空包装,没有实际实现。

While the existence of the NullUser class apparently doesn’t buy us anything useful to praise, it’s a neat creature that allows us to throw all the previous conditionals into the trash. Want to see how?

尽管NullUser类的存在显然并没有给我们带来任何值得赞扬的东西,但它是一个精巧的生物,它使我们可以将以前的所有条件扔进垃圾桶。 想看看如何?

First and foremost, we should do some up front work and refactor the data mapper so its finder returns a null user object instead of the null value.

首先,我们应该做一些前期工作并重构数据映射器,以便其查找器返回空用户对象而不是空值。

<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser,
    ModelNullUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;
    
    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }
    
    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        return $this->createUser($this->adapter->fetch());
    }
    
    private function createUser($row) {
        if (!$row) {
            return new NullUser;
        }
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user; 
    }
}

The mapper’s createUser() method hides a tiny conditional as now it’s responsible for creating a null user whenever the ID passed into the finder doesn’t return a valid user. Even so, this subtle penalty not only saves client code from doing a lot of repetitive checks, but turns it into a permissive consumer which doesn’t complain when it has to deal with a null user.

映射器的createUser()方法隐藏了一个很小的条件,因为现在,只要传递给查找器的ID未返回有效用户,它便负责创建一个空用户。 即便如此,这种微妙的代价不仅可以节省客户端代码进行大量重复检查的时间,而且可以将其变成允许的使用者,当它必须处理空用户时,它不会抱怨。

<?php
$user = $userMapper->fetchById("This ID is invalid...");

echo $user->getName() . " " . $user->getEmail();

The major pitfall with this polymorphic approach is that any application consuming it would become too permissive, as it’d never crash when working with invalid entities. In the worst case, the user interface would display just a few blank lines here and there, but nothing really noisy to make us cringe. This is particularly evident when scanning the current implementation of the earlier NullUser class.

这种多态方法的主要缺陷在于,任何使用它的应用程序都将变得过于宽松,因为使用无效实体时,它永远不会崩溃。 在最坏的情况下,用户界面到处只会显示一些空白行,但是没有什么让我们感到恐惧。 当扫描较早的NullUser类的当前实现时,这一点尤其明显。

Even though, it’s feasible, not to say recommended, to encapsulate logic in null objects while keeping its polymorphic nature untouched. I’d even say that null objects are great candidates for encapsulating default data and behavior that should be only exposed to client code in a few special cases.

即使,将逻辑封装在空对象中,同时保持其多态性是可行的,而不是建议这样做。 我什至要说,空对象是封装默认数据和行为的最佳选择,在某些特殊情况下,这些默认数据和行为仅应公开给客户端代码。

If you were ambitious enough and wanted to give this concept a try using naïve null user objects, the current NullUser class could be refactored in the following way:

如果您有足够的野心并且想尝试使用朴素的空用户对象来尝试使用此概念,则可以通过以下方式重构当前的NullUser类:

<?php
namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() {
        return "The requested ID does not correspond to a valid user.";
    }
    
    public function setName($name) { }
    public function getName() {
        return "The requested name does not correspond to a valid user.";
    }
    
    public function setEmail($email) { }
    public function getEmail() {
        return "The requested email does not correspond to a valid user.";
    }
}

The enhanced version of NullUser is slightly more expressive than its quiet predecessor now that its getters provide some basic implementation to return a few default messages when an invalid user is requested.

增强版本的NullUser比其安静的前身更具表达力,因为它的getter提供了一些基本的实现,以在请求无效用户时返回一些默认消息。

Although trivial, this change has a positive impact in the manner that client code processes null users, as this time the consumers have at least a clear idea that something went wrong when they attempted to pull in a non-existent user from storage. That’s a nice breakthrough, which shows not only how to implement null objects that actually aren’t null at all, but also how easy it is to move logic inside the objects in question according to specific requirements.

尽管微不足道,但此更改对客户端代码处理空用户的方式产生了积极影响,因为这一次,消费者至少清楚地知道,当他们尝试从存储中拉出不存在的用户时出了问题。 这是一个不错的突破,它不仅显示了如何实现实际上根本不为空的空对象,而且还展示了根据特定要求在所讨论的对象内部移动逻辑的难易程度。

结束语 (Closing Remarks)

Some might say that going through the hassle of implementing null objects is overkill, especially in PHP where central OOP concepts, such as Polymorphism, are blatantly underrated. They’d be right to some extent.

有人可能会说过解决实现空对象的麻烦是过大的,特别是在PHP中,公然低估了诸如Obg多态性这样的中心OOP概念。 他们在某种程度上是对的。

Even though, the progressive adoption of well-trusted programming principles and design patterns, along with the level of maturity currently reached by the language’s object model, provides all of the groundwork necessary for moving forward with firm steps and starting to use a few little “luxuries” which were considered tangled, impractical concepts not so long ago. The Null Object pattern falls under this category, but its implementation is so straightforward and elegant that it’s hard not to find it appealing when it comes to cleaning up client code from repetitive null value checks.

即使逐渐采用了值得信赖的编程原则和设计模式,以及该语言的对象模型当前达到的成熟程度,也为迈出坚实的一步并开始使用一些“奢侈品”不久前被认为是纠结的,不切实际的概念。 Null Object模式属于此类,但是其实现是如此简单明了,以至于从重复的null值检查中清除客户端代码时,很难发现它很吸引人。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/the-null-object-pattern-polymorphism-in-domain-models/

数据模型-主题域

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值