dao层通用封装
Being one of the cornerstones of OOP, Inheritance is pretty much like a doubled-sided door which dangerously swings in both ways. When it opens to one side, it exposes a powerful mechanism that allows us to reuse implementations in a snap without having to resort to Composition. Whenever it opens to the other side, though, we see its pleasant nature quickly vanish into thin air, replaced with that of an evil and twisted beast capable of creating all sorts of rotted hierarchies, where subtypes behave so wildly different from their base types that saying there’s a “IS-A” relationship between each of them is blasphemous!
作为OOP的基石之一,继承几乎就像是一扇双面门,以两种方式危险地摆动。 当它向一侧敞开时,它暴露了一种强大的机制,该机制使我们可以在不借助Composition的情况下Swift重用实现。 无论何时,只要它向另一侧敞开,我们就会看到它令人愉悦的本性Swift消失在稀薄的空气中,取而代之的是一种邪恶而扭曲的野兽,这种野兽能够创造出各种腐烂的等级制度,其中子类型的行为与基本类型大相径庭,说他们每个人之间都是“ IS-A”关系,这是亵渎神明!
Despite all of the pitfalls and oddities rooted to Inheritance, most of which can be mitigated through rational and moderate use, its charming influence is hard to resist. Code reuse is, after all, the reason why Inheritance lives and breaths in the first place, and it can be a real killer when it comes to adding boilerplate implementations inside the abstractions of a multi-tiered system.
尽管存在继承的所有陷阱和奇怪之处,但可以通过合理和适度的使用来缓解大多数缺陷和奇怪之处,但其迷人的影响力却难以抗拒。 毕竟,代码重用是继承首先活下来的原因,当在多层系统的抽象内部添加样板实现时,它可能是真正的杀手。
Inheritance offers a straightforward way to easily spawn a large number of objects that are semantically related to each other without having duplicate code. The concept is ridiculously simple – yet powerful: you first drop as much logic as possible within the boundaries of a base type (usually an abstract class, but it could be a concrete one), and then start deriving refined subtypes according to more specific requirements. The process is typically conducted in a “per-layer” basis, thus providing each layer with its own set of super types whose core functionality is distilled and extended in turn by the corresponding subtypes.
继承提供了一种简单的方法,可以轻松生成大量彼此语义相关的对象,而无需重复代码。 这个概念非常简单,但是功能强大:首先在基本类型(通常是抽象类,但是可能是一个具体的类)的边界内放置尽可能多的逻辑,然后根据更具体的要求开始推导精炼的子类型。 。 该过程通常以“每层”为基础进行,因此为每一层提供了自己的一组超级类型,这些超级类型的核心功能经过相应的子类型提炼和扩展。
Not surprisingly, this repetitive encapsulation/derivation cycle lays down on the formalities of a design pattern known as Layer Supertype, (yes, although somewhat naïve, it does have a real academic name), and in the upcoming lines I’ll be taking an in-depth look at its inner workings and you’ll be able to see just how easy it is to hook up its functionality to a Domain Model.
毫不奇怪,这个重复的封装/派生循环取决于称为Layer Supertype的设计模式的形式(是的,尽管有些天真,它确实有一个学术名称),在接下来的几行中,我将介绍深入了解它的内部工作原理,您将能够看到将其功能连接到Domain Model多么容易。
对层超类型的需求–定义膨胀域模型 (The Need for a Layer Supertype – Defining a Bloated Domain Model)
It could be said that a Layer Supertype is the natural and selective evolution of “common” base types, only that the latter live and breathe inside the confines of a specific layer. This has a prolific niche in multi-tiered designs, where exploiting the functionality of a supertype is mostly an imperative need, not just a flippant decision.
可以说,层超类型是“通用”基本类型的自然而选择性的演变,只是后者在特定层的范围内生活和呼吸。 在多层设计中,这是一个多产的利基市场,在其中利用超类型的功能主要是当务之急,而不仅仅是轻率的决定。
As usual, the most effective way to understand the pragmatism behind the pattern is through a few hands-on examples. So, say we need to build a simple Domain Model from scratch, responsible for defining a few basic interactions between some blog posts and their corresponding comments.
与往常一样,了解模式背后的实用主义的最有效方法是通过一些动手实例。 因此,说我们需要从头开始构建一个简单的域模型,负责定义一些博客文章及其相应评论之间的一些基本交互。
In a cursory fashion, the model could be easily outlined as an anemic layer containing just a couple of skeletal classes modeling the posts and comments.
以粗略的方式,可以轻松地将该模型概述为一个贫血层,该贫血层仅包含几个对帖子和评论进行建模的骨架类。
The first domain class, along with its contract, could look like this:
第一个域类及其合同可能如下所示:
<?php
namespace Model;
interface PostInterface
{
public function setId($id);
public function getId();
public function setTitle($title);
public function getTitle();
public function setContent($content);
public function getContent();
public function setComment(CommentInterface $comment);
public function setComments(array $comments);
public function getComments();
}
<?php
namespace Model;
class Post implements PostInterface
{
protected $id;
protected $title;
protected $content;
protected $comments = array();
public function __construct($title, $content, array $comments = array()) {
$this->setTitle($title);
$this->setContent($content);
if (!empty($comments)) {
$this->setComments($comments);
}
}
public function setId($id) {
if ($this->id !== null) {
throw new BadMethodCallException(
"The ID for this post has been set already.");
}
if (!is_int($id) || $id < 1) {
throw new InvalidArgumentException(
"The post ID is invalid.");
}
$this->id = $id;
return $this;
}
public function getId() {
return $this->id;
}
public function setTitle($title) {
if (!is_string($title)
|| strlen($title) < 2
|| strlen($title) > 100) {
throw new InvalidArgumentException(
"The post title is invalid.");
}
$this->title = htmlspecialchars(trim($title),
ENT_QUOTES);
return $this;
}
public function getTitle() {
return $this->title;
}
public function setContent($content) {
if (!is_string($content) || strlen($content) < 2) {
throw new InvalidArgumentException(
"The post content is invalid.");
}
$this->content = htmlspecialchars(trim($content),
ENT_QUOTES);
return $this;
}
public function getContent() {
return $this->content;
}
public function setComment(CommentInterface $comment) {
$this->comments[] = $comment;
return $this;
}
public function setComments(array $comments) {
foreach ($comments as $comment) {
$this->setComment($comment);
}
return $this;
}
public function getComments() {
return $this->comments;
}
}
Driving the Post
class is banal logic that boils down to defining the data and behavior of a few basic post entries. It should be pretty simple to understand.
推动Post
类的发展是平庸的逻辑,归结为定义一些基本帖子条目的数据和行为。 它应该很容易理解。
Now let’s make the model a little fatter by adding to it a class that spawns the comments associated with a particular blog entry. It’s contract and implementation look like this:
现在,通过向其添加一个类来生成与特定博客条目关联的注释的类,使模型更胖一些。 它的契约和实现如下所示:
<?php
namespace Model;
interface CommentInterface
{
public function setId($id);
public function getId();
public function setContent($content);
public function getContent();
public function setAuthor($author);
public function getAuthor();
}
<?php
namespace Model;
class Comment implements CommentInterface
{
protected $id;
protected $content;
protected $author;
public function __construct($content, $author) {
$this->setContent($content);
$this->setAuthor($author);
}
public function setId($id) {
if ($this->id !== null) {
throw new BadMethodCallException(
"The ID for this comment has been set already.");
}
if (!is_int($id) || $id < 1) {
throw new InvalidArgumentException(
"The comment ID is invalid.");
}
$this->id = $id;
return $this;
}
public function getId() {
return $this->id;
}
public function setContent($content) {
if (!is_string($content) || strlen($content) < 2) {
throw new InvalidArgumentException(
"The content of the comment is invalid.");
}
$this->content = htmlspecialchars(trim($content),
ENT_QUOTES);
return $this;
}
public function getContent() {
return $this->content;
}
public function setAuthor($author) {
if (!is_string($author) || strlen($author) < 2) {
throw new InvalidArgumentException(
"The author is invalid.");
}
$this->author = $author;
return $this;
}
public function getAuthor() {
return $this->author;
}
}
Like Post
, the Comment
class is pretty naïve as well. But now with both classes in place, we can put the model to use. For example:
像Post
一样, Comment
类也很幼稚。 但是现在有了这两个类,我们就可以使用模型了。 例如:
<?php
use LibraryLoaderAutoloader,
ModelPost,
ModelComment;
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();
$post = new Post(
"A sample post.",
"This is the content of the post."
);
$post->setComments(array(
new Comment(
"One banal comment for the previous post.",
"A fictional commenter"),
new Comment(
"Yet another banal comment for the previous post.",
"A fictional commenter")
));
echo $post->getTitle() . " " . $post->getContent() . "<br>";
foreach ($post->getComments() as $comment) {
echo $comment->getContent() . " " . $comment->getAuthor() .
"<br>";
}
That worked like a charm indeed! Consuming the model is a fairly straightforward process, requiring that you first create a few Post
objects and then hydrating them with the related comments. Yes, life is sweet and good. Well, so far it is, but things could definitely be much better!
确实确实像魅力! 消耗模型是一个相当简单的过程,需要首先创建一些Post
对象,然后将它们与相关注释结合在一起。 是的,生活是美好而美好的。 好吧,到目前为止,但是情况肯定会好得多!
Not that I want to spoil the magic of such a beautiful moment, but I must confess a slight shiver run down my spine each time I look at the implementations of the Post
and Comment
classes. While it isn’t a serious issue per se, some methods, such as setId()
and setContent()
, exhibit the typical symptoms of code duplication.
并不是说我想破坏如此美好时刻的魔力,而是每次当我查看Post
和Comment
类的实现时,我都必须承认我的脊椎有些发抖。 尽管这本身并不是一个严重的问题,但是某些方法(例如setId()
和setContent()
表现出代码重复的典型症状。
Solving this problem without sloppy isn’t so intuitive as it might seem at first glance because of a few logical concerns. First, although they share a semantic relationship with each other, each class effectively models different types of objects. Second, they implement disparate interfaces which means it’s somewhat hard to abstract away the logic without ending up with a clumsy hierarchy where the “IS-A” condition never holds true.
解决问题的过程并不那么草率,因为一些逻辑上的顾虑,乍一看似乎并不直观。 首先,尽管它们彼此共享语义关系,但是每个类都有效地对不同类型的对象建模。 其次,它们实现了完全不同的接口,这意味着在不以“ IS-A”条件永远不成立的笨拙层次结构结束的情况下,很难抽象出逻辑。
In this case in particular, we could just take a more liberal approach and think of Post and Comment as subtypes of a highly-generic AbstractEntity
supertype. In doing so, it would be pretty simple to put shared implementation inside the boundaries of an abstract class, hence making the definitions of the subtypes a lot thinner. Since the whole abstraction process would be only conducted in the domain layer, the hypothetical AbstractEntity
would be considered… yes, you guessed right, a Layer Supertype. Simple but nice, huh?
特别是在这种情况下,我们可以采用一种更为自由的方法,并将Post和Comment视为高度通用的AbstractEntity
超类型的子类型。 这样,将共享实现放在抽象类的边界内将非常简单,从而使子类型的定义更薄。 由于整个抽象过程仅在域层中进行,因此将考虑假设的AbstractEntity
…是的,您猜对了,Layer Supertype。 简单但很好,是吗?
将共享实现移至单个域类–创建层超类型 (Moving Shared Implementation to a Single Domain Class – Creating a Layer Supertype)
The process of creating a model supertype, at least in this case, can be viewed as a mixture of a few atomic and granular refactoring techniques, such as Extract SuperClass, Pull Up Field, and Pull Up Method all covered in depth by Martin Fowler in his book Refactoring: Improving the Design of Existing Code (if you don’t own a copy already, make sure to grab one ASAP).
至少在这种情况下,创建模型超类型的过程可以看作是几种原子和粒度重构技术的混合物,例如Extract SuperClass,Pull Up Field和Pull Up Method,这些都在Martin Fowler在他的书《 重构:改善现有代码的设计》 (如果您尚未拥有副本,请确保尽快获取一个副本)。
Let’s take the big plunge and refactor our clunky domain model by dropping into it the aforementioned AbstractEntity
class. Since in this specific situation the bulk of duplicated code appears through the methods of the domain classes that handle IDs and perform some basic validation on string-based fields, it’d be useful to delegate these responsibilities to the supertype and let the subtypes be focused on doing fewer, more narrowed tasks.
让我们大跌一下,通过将前面提到的AbstractEntity
类放入重构笨拙的域模型。 由于在这种特定情况下,大量重复的代码通过处理ID并在基于字符串的字段上执行一些基本验证的域类的方法出现,因此将这些职责委托给超类型并让子类型集中是很有用的做更少,更狭窄的任务。
Based on this simplistic concept, a “fat” implementation of the aforementioned model supertype might look like this:
基于这个简单的概念,上述模型超类型的“胖”实现可能看起来像这样:
<?php
namespace Model;
class AbstractEntity
{
protected $id;
// Map calls to protected/private fields to mutators when
// defined. Otherwise, map them to the fields.
public function __set($field, $value) {
$this->checkField($field);
$mutator = "set" . ucfirst(strtolower($field));
method_exists($this, $mutator) && is_callable(array($this, $mutator))
? $this->$mutator($value)
: $this->$field = $value;
return $this;
}
// Map calls to protected/private fields to accessors when
// defined. Otherwise, map them to the fields.
public function __get($field) {
$this->checkField($field);
$accessor = "get" . ucfirst(strtolower($field));
return method_exists($this, $accessor) && is_callable(array($this, $accessor))
? $this->$accessor()
: $this->$field;
}
// Map calls to undefined mutators/accessors to the corresponding
// fields
public function __call($method, $arguments) {
if (strlen($method) < 3) {
throw new BadMethodCallException(
"The mutator or accessor '$method' is not valid for this entity.");
}
$field = lcfirst(substr($method, 3));
$this->checkField($field);
if (strpos($method, "set") === 0) {
$this->$field = array_shift($arguments);
return $this;
}
if (strpos($method, "get") === 0) {
return $this->$field;
}
}
// Make sure IDs are positive integers and assigned only once
public function setId($id) {
if ($this->id !== null) {
throw new BadMethodCallException(
"The ID for this entity has been set already.");
}
if (!is_int($id) || $id < 1) {
throw new InvalidArgumentException(
"The ID for this entity is invalid.");
}
$this->id = $id;
return $this;
}
public function getId() {
return $this->id;
}
// Get the entity fields as an array
public function toArray() {
return get_object_vars($this);
}
// Check if the given field exists in the entity
protected function checkField($field) {
if (!property_exists($this, $field)) {
throw new InvalidArgumentException(
"Setting or getting the field '$field' is not valid for this entity.");
}
}
// Validate and sanitize a string
protected function sanitizeString($value, $min = 2, $max = null) {
if (!is_string($value) || empty($value)) {
throw new InvalidArgumentException(
"The value of the current field must be a non-empty string.");
}
if (strlen($value) < (integer) $min || $max ? strlen($value) > (integer) $max : false) {
throw new InvalidArgumentException(
"Trying to assign an invalid string to the current field.");
}
return htmlspecialchars(trim($value), ENT_QUOTES);
}
}
Contrary to popular belief, bringing to life an abstract supertype which neatly encapsulates most of the logic shared by the model’ subtypes under its hood is actually an easy process that can be accomplished relatively quickly. In this case, I admit I became a bit overzealous as the supertype not only is capable of handling IDs and validating/sanitizing strings, but is also sprinkles in some PHP magic behind the scenes to map calls to private/protected properties to their corresponding mutators/accessors whenever possible. Providing the supertype with extra functionality like this right out of the box is entirely optional, so feel free include it or omit it in your own AbstractEntity
class as you see fit.
与普遍的看法相反,使抽象超型栩栩如生地将模型子类型所共享的大多数逻辑巧妙地封装起来,实际上是一个容易完成的过程,可以相对快速地完成。 在这种情况下,我承认我有点过分热衷,因为超类型不仅能够处理ID和验证/清理字符串,而且还散布着一些PHP魔术,可以将对私有/受保护属性的调用映射到其对应的变体/ accessors尽可能。 开箱即用地为超类型提供此类额外功能完全是可选的,因此您可以随意在自己的AbstractEntity
类中包含或忽略它。
The next step is to refactor the implementations of the Post
and Comment
classes so they can accommodate the goodies their abstract parent now offers. Below are the refactored versions:
下一步是重构Post
和Comment
类的实现,以便它们可以容纳其抽象父级现在提供的东西。 下面是重构的版本:
<?php
namespace Model;
class Post extends AbstractEntity implements PostInterface
{
protected $title;
protected $content;
protected $comments = array();
public function __construct($title, $content, array $comments = array()) {
$this->setTitle($title);
$this->setContent($content);
if (!empty($comments)) {
$this->setComments($comments);
}
}
public function setTitle($title) {
try {
$this->title = $this->sanitizeString($title);
return $this;
}
catch (InvalidArgumentException $e) {
throw new $e("Error setting the post title: " .
$e->getMessage());
}
}
public function getTitle() {
return $this->title;
}
public function setContent($content) {
try {
$this->content = $this->sanitizeString($content);
return $this;
}
catch (InvalidArgumentException $e) {
throw new $e("Error setting the post content: " .
$e->getMessage());
}
}
public function getContent() {
return $this->content;
}
public function setComment(CommentInterface $comment) {
$this->comments[] = $comment;
return $this;
}
public function setComments(array $comments) {
foreach ($comments as $comment) {
$this->setComment($comment);
}
return $this;
}
public function getComments() {
return $this->comments;
}
}
<?php
namespace Model;
class Comment extends AbstractEntity implements CommentInterface
{
protected $content;
protected $author;
public function __construct($content, $author) {
$this->setContent($content);
$this->setAuthor($author);
}
public function setContent($content) {
try {
$this->content = $this->sanitizeString($content);
return $this;
}
catch (InvalidArgumentException $e) {
throw new $e("Error setting the comment: " .
$e->getMessage());
}
}
public function getContent() {
return $this->content;
}
public function setAuthor($author) {
try {
$this->author = $this->sanitizeString($author);
return $this;
}
catch (InvalidArgumentException $e) {
throw new $e("Error setting the author : " .
$e->getMessage());
}
}
public function getAuthor() {
return $this->author;
}
}
The model’s subtypes are now a lot slimmer creatures, as most of the duplicated code has been placed within the dominant supertype. What’s more, because of the implementation under the surface of the __set()
/__get()
duet, consuming the model is even an easier task, reduced to just coding the following snippet:
该模型的子类型现在是许多苗条的生物,因为大多数重复的代码已放置在显性超类中。 而且,由于在__set()
/ __get()
二重奏的__get()
,使用模型甚至是一件容易的事,减少为仅对以下代码段进行编码:
<?php
$post = new Post("A sample post.", "This is the content of the post.");
$post->setComments(array(
new Comment(
"One banal comment for the previous post.",
"A fictional commenter"),
new Comment(
"Yet another banal comment for the previous post.",
"A fictional commenter")
));
echo $post->title . " " . $post->content . "<br>";
foreach ($post->comments as $comment) {
echo $comment->content . " " . $comment->author . "<br>";
}
The example might be contrived, but it does highlight how to get the model finally up and running, this time using the trimmed versions of the Post
and Comment
classes. The juicy stuff actually happens behind the scenes rather than on the flashy front line, since the definition of an abstract supertype entity allows us to remove large chunks of duplicated implementations without much worry during the whole refactoring process.
该示例可能是人为设计的,但是它确实突出了如何使用Post
和Comment
类的修剪版本最终使模型最终启动并运行。 多汁的东西实际上发生在幕后,而不是浮华的前线,因为抽象超类型实体的定义使我们可以删除大量重复的实现,而在整个重构过程中不必担心。
Moreover, the sole existence of a few subtypes is, on its own, a valid reason for going through the hassle of implementing a Layer Supertype. For obvious reasons, the pattern’s most appealing facet is exposed when dealing with multiple subtypes scattered through several application layers.
而且,仅靠几个子类型的存在本身就是经历实现层超类型的麻烦的正当理由。 出于明显的原因,当处理分散在几个应用程序层中的多个子类型时,该模式最吸引人的方面暴露了出来。
结束语 (Closing Remarks)
Although its commonly viewed as an overrated and abused beast, I hope now few will disagree that Inheritance is a powerful mechanism which, when cleverly employed in multi-tiered systems, can be an effective repellent of code duplication. The use of a simplistic pattern such as Layer Supertype is an example of the wealth of engaging virtues that Inheritance provides right out of the box when it comes to creating subtypes that share extensive segments of boilerplate implementation with each other.
尽管它通常被认为是一种被高估和滥用的野兽,但我希望现在很少有人会不同意继承是一种强大的机制,当将其巧妙地应用于多层系统中时,它可以有效地阻止代码重复。 使用简单的模式(例如Layer Supertype)便是继承优势的众多示例,当涉及到创建彼此共享大量样板实现细分的子类型时,继承即开箱即用。
Image via Fotolia
图片来自Fotolia
dao层通用封装