c++继承和重用_重用实现–继承,组合和委派的演练

c++继承和重用

If had to give a convincing reason why most developers are such peculiar creatures, I’d say that every time you call them lazy, they feel insanely proud of themselves and show off a big “full teeth” smile. While admittedly the fact itself doesn’t go any further than being merely curious and anecdotal, it suggests the term “lazy” isn’t considered offensive. Of course, being a developer myself, I’d say that we’re not that insane (or at least that our insanity is generally pretty innocuous). It’s just that we take the word “lazy” as an ad-hoc synonym for “a really DRY person who cleverly reuses things” (hence doing less work).

如果不得不给出令人信服的理由说明为什么大多数开发人员都是如此奇特的生物,我想说的是,每当您称他们为懒惰时,他们都会为自己疯狂地感到骄傲,并表现出巨大的“全齿”微笑。 尽管事实本身不仅仅只是好奇和轶事而已,但它表明“懒惰”一词不被认为具有冒犯性。 当然,作为我自己的开发人员,我会说我们并不是那么疯狂(或者至少我们的精神错乱通常是无害的)。 只是我们将“懒惰”一词作为“一个真正干的人,巧妙地重用事物”的临时同义词(因此减少了工作量)。

While there’s nothing inherently wrong with having this attitude, described as the principle of least effort, sometimes it’s nothing more than a bluff. We know that taking advantage of framework A along with the generously functionality offered by library B can be remarkably productive when it comes to dealing with tight deadlines and generating revenue, but in the end all we really want is to avoid suffering the painful effects of code duplication and enjoy the blessings of a reusable implementation. Let’s be honest… do we really know if the frameworks or libraries in question are as DRY as they claim? Worse, assuming that they are, are we actually capable of writing DRY code on top of such codebases?

尽管这种态度被描述为最省力原则,但从本质上来说并没有错,但有时不过是虚张声势而已。 我们知道,利用框架A以及库B提供的慷慨功能可以在处理紧迫的截止日期和创收方面取得显著成效,但最终,我们真正想要的是避免遭受代码的痛苦影响复制并享受可重复使用的实施带来的好处。 说实话...我们真的知道所讨论的框架或库是否像他们声称的那样干燥? 更糟糕的是,假设它们确实存在,我们是否真的有能力在此类代码库之上编写DRY代码?

The popular belief is that reusing implementation, thus producing DRYer code, boils down to exploiting the benefits that Inheritance provides, right? Well, I wish it was that easy! There are few practitioners of cargo cult programming who will also add their loud voices claiming that Composition is actually the king of code reuse and that Inheritance should be thrown out. Or, why don’t we just stop cranking and get rid of both by picking up plain Delegation instead? It is an itching dilemma indeed, but ultimately pointless.

普遍的看法是,重用实现,从而生成DRYer代码,归结为利用继承提供的好处,对吗? 好吧,我希望那是那么容易! 很少有从事货物崇拜编程的从业人员,他们还会大声疾呼,声称组合实际上是代码重用的王者,应该放弃继承。 或者,为什么我们不停止发呆,而是通过选择简单的委派来摆脱两者呢? 这确实是一个发痒的难题,但最终毫无意义。

As one might expect, each approach has its own comfort zone, and which one fits the bill best depends on the type of problem being tackled along with its surrounding context. Inheritance has been an overrated beast for a long time, especially in the world of PHP, where central OOP concepts like the Liskov Substitution Principle (LSP) were viewed pretty much as tangled, obscure paradigms with little or no applicable value in the real world. On the flip side, Composition and Delegation have found quite a prolific niche in the language, reinforced by the momentum gained by Dependency Injection in the last few years. It’s as if opposing forces are constantly pushing against each other in order to maintain a purely fictional balance.

正如人们可能期望的那样,每种方法都有其自己的舒适区,最适合该方案的方法取决于要解决的问题类型及其周围环境。 长期以来,继承一直是被高估的野兽,尤其是在PHP世界中,诸如Liskov替换原理 (LSP)之类的OOP中心概念在很大程度上被视为纠结,晦涩的范例,在现实世界中几乎没有或没有适用价值。 另一方面,组合和委托在该语言中找到了一个多产的利基市场,最近几年依赖注入的发展势头进一步加强了这种优势。 仿佛敌对力量不断在相互抵制,以维持纯粹的虚构平衡。

If you don’t know what path to travel when it comes to reusing implementation, in this article I’ll be doing a humble walk-through on the Inheritance/Composition/Delegation trio in an attempt to showcase, side by side, some of their most appealing virtues and clunky drawbacks.

如果您不知道重用实现时要走的路,在本文中,我将对Inheritance / Composition / Delegation三人进行一次简短的演练,以尝试并排展示一些它们最吸引人的优点和笨拙的缺点。

继承继承的路径–简单性与基本类型/子类型问题 (Treading Inheritance’s Path – Simplicity vs Basetype/Subtype Issues)

It shouldn’t be breaking news that the first code reuse approach I plan to put in the spotlight first is the most overused and misused of all, Inheritance. But I don’t want to be a sadist and bore you to tears (and myself, of course) explaining how to get a nice Dog subclass up and running by inheriting from Animal (ouch!). Rather, I’ll be a little more realistic and we’ll sink our teeth into a naïve, yet functional, PDO adapter. Here we go:

我计划首先关注的第一个代码重用方法是继承,这是最被过度使用和滥用的方法,这并不是什么新闻。 但是我不想成为一个虐待狂,不让您流泪(当然,还有我自己),他解释了如何通过继承Animal(哎呀!)来建立并运行一个不错的Dog子类。 相反,我会更加现实一些,我们将全神贯注于幼稚但实用的状态, PDO适配器。 开始了:

<?php
namespace LibraryDatabase;

interface DatabaseAdapterInterface
{
    public function executeQuery($sql, array $parameters = array());
}
<?php
namespace LibraryDatabase;

class PdoAdapter extends PDO implements DatabaseAdapterInterface
{
    protected $statement;
    
    public function __construct($dsn, $username = null, $password = null, array $options = array()) 
    {
        // fail early if the PDO extension is not loaded
        if (!extension_loaded("pdo")) {
            throw new InvalidArgumentException(
                "This adapter needs the PDO extension to be loaded.");
        }
        // check if a valid DSN has been passed in
        if (!is_string($dsn) || empty($dsn)) {
            throw new InvalidArgumentException(
                "The DSN must be a non-empty string.");
        }
        // try to create a valid PDO object
        try {
            parent::__construct($dsn, $username, $password,
                $options);
            $this->setAttribute(PDO::ATTR_ERRMODE,
                PDO::ERRMODE_EXCEPTION);
            $this->setAttribute(PDO::ATTR_EMULATE_PREPARES,
                false);
        
        }
        catch (PDOException $e) {
            throw new RunTimeException($e->getMessage());
        }
    }
    
    public function executeQuery($sql, array $parameters = array())
    {
        try {
           $this->statement = $this->prepare($sql);
           $this->statement->execute($parameters);
           return $this->statement->fetchAll(PDO::FETCH_CLASS,
               "stdClass"); 
        
        }
        catch (PDOException $e) {
            throw new RunTimeException($e->getMessage());
        }
    }
}

When it comes to creating a quick and dirty adapter that just adds some extra functionality to the one provided by the PDO class, Inheritance is indeed a pretty tempting path. In this case, the adapter performs some basic validation on the inputted database arguments and its executeQuery() method allows us to run parameterized queries via an easily consumable API. The following snippet demonstrates how to drop the adapter into client code to pull in a few users from the database:

当创建一个快速而肮脏的适配器时,它只是向PDO类提供的适配器添加了一些额外的功能,继承确实是一条很诱人的路径。 在这种情况下,适配器对输入的数据库参数执行一些基本的验证,并且其executeQuery()方法允许我们通过易于使用的API运行参数化查询。 以下代码片段演示了如何将适配器放入客户端代码中,以从数据库中引入一些用户:

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

$adapter = new PdoAdapter("mysql:dbname=mydatabase",
    "myfancyusername", "myhardtoguesspassword");

$guests = $adapter->executeQuery("SELECT * FROM users WHERE role = :role", array(":role" => "Guest"));

foreach($guests as $guest) {
    echo $guest->name . " " . $guest->email . "<br>";
}

There are no smelly signs of any “incidental” breakages of the LSP, meaning that any instance of PdoAdapter could be safely swapped out at runtime by a base PDO object without complaints from the client code, and the adapter has been blessed with all the legacy functionality encapsulated by its parent. Can we dare to ask for a better deal?

没有任何LSP意外破坏的臭味,这意味着基本的PDO对象可以在运行时安全地换出PdoAdapter任何实例,而不会受到客户端代码的抱怨,并且该适配器拥有所有的传统其父封装的功能。 我们可以敢要求更好的交易吗?

Admittedly, there’s a catch. While not explicit, PdoAdapter is actually exposing to the outside world the whole verbose PDO API, plus the one implemented on it’s own. There might be occasions where we want to avoid this, even if the “IS-A” relation between PdoAdapter and PDO is neatly maintained.

诚然,有一个陷阱。 虽然不是很明确,但PdoAdapter实际上是向外界公开了整个冗长的PDO API,以及一个自己实现的API。 即使在PdoAdapterPDO之间的“ IS-A”关系得到了PdoAdapter维护,有时也可能要避免这种情况。

We could appeal to the wisdom of the mantra “favor Composition over Inheritance” in such situations and effectively shift into the seas of Composition. In doing so, we would be effectively reusing PDO’s implementation, but without having to deal with its entire API. Better yet, since there wouldn’t be a “IS-A” relationship, but instead a “HAS-A” one between the corresponding adapter and the PDO class, we’d be also satisfying our purist OOP instincts. Encapsulation would keep its pristine shell intact.

在这种情况下,我们可以呼吁“偏爱组成重于继承”这一口头禅的智慧,并有效地将其带入组成之海。 这样,我们将有效地重用PDO的实现,而不必处理其整个API。 更好的是,由于在相应的适配器和PDO类之间没有“ IS-A”关系,而是“ HAS-A”关系,因此我们也将满足我们纯粹的OOP本能。 封装将使其原始外壳保持完整。

Of course, one nice way to understand the inner workings of this approach is by setting up a concrete example. Let’s tweak the previous PdoAdapter class to honor the commandments of Composition.

当然,了解此方法的内部运作方式的一种不错的方法是建立一个具体的示例。 让我们调整以前的PdoAdapter类,以遵守Composition的诫命。

继承之上的组成–适配器,适配器和其他有趣的事实 (Composition over Inheritance – of Adapters, Adaptees, and Other Funny Facts)

Contrary to popular opinion, it’s ridiculously easy to implement a PDO adapter that rests on the pillars of Composition. Furthermore, the whole refactoring would be limited to just creating a simple wrapper which would inject a native PDO object into the constructor. Once again, a hands-on example is the best teacher when it comes to understanding the driving logic of the process:

与流行观点相反,实现基于CompositionStruts的PDO适配器非常容易。 此外,整个重构将仅限于创建一个简单的包装程序,该包装程序会将本机PDO对象注入到构造函数中。 再次,一个动手的例子是理解过程驱动逻辑的最佳老师:

<?php
namespace LibraryDatabase;

interface DatabaseAdapterInterface
{
    public function executeQuery($sql, array $parameters = array());
}
<?php
namespace LibraryDatabase;

class PdoAdapter implements DatabaseAdapterInterface
{
    protected $pdo;
    protected $statement;
    
    public function __construct(PDO $pdo) 
    {
        $this->pdo = $pdo;
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE,
            PDO::ERRMODE_EXCEPTION);
        $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,
            false);
    }
    
    public function executeQuery($sql, array $parameters = array())
    {
        try {
           $this->statement = $this->pdo->prepare($sql);
           $this->statement->execute($parameters);
           return $this->statement->fetchAll(PDO::FETCH_CLASS,
               "stdClass"); 
        }
        catch (PDOException $e) {
            throw new RunTimeException($e->getMessage());
        }
    }
}

While the refactored version of the adapter follows in general the formal definition of the Adapter pattern, the adaptee passed along into the adapter’s internals is, in this case, a concrete implementation rather than an abstraction. Considering that we’re attempting to design a slimmed down API for a PDO object, it doesn’t hurt to be pragmatic and inject the object directly in question without having to go through the hassle of defining an extra interface. If you feel a little more comfortable picking up the interface-based approach, just go ahead and stick to it all the way.

尽管适配器的重构版本通常遵循Adapter模式的正式定义,但是在这种情况下,传递到适配器内部的适配器是一种具体的实现,而不是抽象的实现。 考虑到我们正在尝试为PDO对象设计一个精简的API,务实地将其直接注入有问题的对象,而不必经历定义额外接口的麻烦,这无害。 如果您对采用基于接口的方法感到更自在,请继续并始终坚持下去。

Leaving out of the picture those subtle implementation details, by far the most relevant thing to point out is the PdoAdapter class is now is a less verbose creature. Using Composition, it hides the whole PDO API from the outside world by exposing only its executeQuery() method to client code. Although naïve, the example raises a few points worth noting. First, the burden of dealing with potentially dangerous class hierarchies, where subtypes might behave wildly different from their base types blowing away application flow, has quietly vanished in thin air. Second, not only now is the adapter’s API less bloated, it declares an explicit dependency on a PDO implementation, which makes it easier to see from the outside the collaborator it needs to do its thing.

忽略了那些细微的实现细节,到目前为止最需要指出的是PdoAdapter类现在是一个不太冗长的生物。 通过使用Composition,它通过仅将其executeQuery()方法暴露给客户端代码来将整个PDO API从外界隐藏起来。 尽管很幼稚,但该示例提出了一些值得注意的观点。 首先,处理潜在危险的类层次结构的负担已悄然消失,在这些层次结构中,子类型的行为可能与其基本类型完全不同,从而浪费了应用程序流。 其次,不仅适配器的API现在不那么肿,它还声明了对PDO实现的显式依赖,这使得从外部更容易看到协作者需要做的事情。

Yet this slew of benefits come with a not-so-hidden cost: in more realistic situations, it might be harder to create “composed” adapters than it is to create “inherited” ones. The correct path to tread will vary from case to case; be rational and choose the one that fits your requirements the best.

然而,这一系列的好处却没有那么隐性的成本:在更现实的情况下,创建“组合”适配器比创建“继承”适配器更难。 正确的踩踏路径因情况而异; 保持理性并选择最适合您需求的产品。

If you’re wondering how to get things rolling with the refactored PdoAdapter class, the following example should be instructive:

如果您想知道如何使用重构的PdoAdapter类使事情PdoAdapter ,那么以下示例应该具有指导意义:

<?php
$pdo = new PDO("mysql:dbname=mydatabase",
    "myfancyusername", "myhardtoguesspassword");
$adapter = new PdoAdapter($pdo);

$guests = $adapter->executeQuery("SELECT * FROM users WHERE role = :role", array(":role" => "Guest"));

foreach($guests as $guest) {
    echo $guest->name . " " . $guest->email . "<br>";
}

By now we’ve seen at a very broad level two common approaches that can be employed for reusing implementation. So, what comes next? Well, I promised to dig deeper into the niceties of Delegation, too. While admittedly Composition is an implicit form of Delegation, in the earlier example the delegation link was set by declaring a dependency on a PDO implementation in the constructor.

到目前为止,我们已经在非常广泛的层次上看到了可以用于重用实现的两种常用方法。 那么,接下来会发生什么呢? 好吧,我保证也会深入研究代表团的好处。 尽管公认组合是委派的隐式形式,但在前面的示例中,通过声明对构造函数中PDO实现的依赖来设置委派链接。

It’s feasible, however, to get a decent delegation mechanism up and running by using one or multiple factory methods, even though this misses much of the advantages of decoupling interface from implementation. Regardless, it’s worth looking at this approach as it comes in helpful for building PDO adapters capable of lazy connections to the database.

但是,通过使用一种或多种工厂方法来启动并运行一种体面的委托机制是可行的,即使这错过了将接口与实现分离的许多优点。 无论如何,值得一看的是这种方法,因为它有助于构建能够延迟连接数据库的PDO适配器。

通过委派推迟数据库旅行 (Deferring Database Trips through Delegation)

Can anything be more delightful than delaying an expensive database trip to the last minute? Avoiding it completely would certainly be better, which is the prime reason why cache system live and breath. But unfortunately, we just can’t have it all sometimes and we need to settle ourselves by designing PDO adapters that encapsulate this nifty ability inside the boundaries of a factory method.

有什么比将昂贵的数据库旅行延迟到最后一分钟更令人愉悦的了吗? 完全避免使用它肯定会更好,这就是缓存系统能够正常运行的主要原因。 但是不幸的是,有时候我们无法拥有所有这些,我们需要通过设计PDO适配器来解决自己,这些适配器将这种精巧的功能封装在工厂方法的范围内。

Delegation is a simple yet powerful pattern that allows to implement this feature without much hassle. If we would turn our attention to the previous PdoAdapter class and make it exploit the benefits of Delegation, it would look as follows:

委托是一种简单但功能强大的模式,可以轻松实现此功能。 如果我们将注意力转移到先前的PdoAdapter类上,并使其利用委派的好处,它将看起来如下:

<?php
namespace LibraryDatabase;

interface DatabaseAdapterInterface
{
    public function connect();

    public function disconnect();

    public function executeQuery($sql, array $parameters = array());
}
<?php
namespace LibraryDatabase;

class PdoAdapter implements DatabaseAdapterInterface
{
    protected $config = array();
    protected $connection;
    protected $statement;
    
    public function __construct($dsn, $username = null, $password = null, array $options = array())
    {
        // fail early if the PDO extension is not loaded
        if (!extension_loaded("pdo")) {
            throw new InvalidArgumentException(
                "This adapter needs the PDO extension to be loaded.");
        }
        // check if a valid DSN has been passed in
        if (!is_string($dsn) || empty($dsn)) {
            throw new InvalidArgumentException(
                "The DSN must be a non-empty string.");
        }
        $this->config = compact("dsn", "username",
            "password", "options");
    }
    
    public function connect()
    {
        if ($this->connection) {
            return;
        }
        try {
            $this->connection = new PDO(
                $this->config["dsn"], 
                $this->config["username"], 
                $this->config["password"], 
                $this->config["options"]
            );
            $this->connection->setAttribute(PDO::ATTR_ERRMODE,
                PDO::ERRMODE_EXCEPTION);
            $this->connection->setAttribute(
                PDO::ATTR_EMULATE_PREPARES, false);
        }
        catch (PDOException $e) {
            throw new RunTimeException($e->getMessage());
        }
    }
    
    public function disconnect()
    {
        $this->connection = null;
    }
    
    public function executeQuery($sql, array $parameters = array())
    {
        $this->connect();
        try {
           $this->statement = $this->connection->prepare($sql);
           $this->statement->execute($parameters);
           return $this->statement->fetchAll(PDO::FETCH_CLASS,
               "stdClass"); 
        }
        catch (PDOException $e) {
            throw new RunTimeException($e->getMessage());
        }
    }
}

If you’ve ever had the chance to craft your own PDO masterpiece from scratch, or if you’ve just reused one of the heap available in the wild, the underlying logic of the above adapter should be pretty easy to grasp. Unquestionably, its most engaging facet is the implementation of the connect() method, as its responsibility is to create a PDO object on request which is then used in turn to dispatch queries against the database.

如果您曾经有机会从头开始制作自己的PDO杰作,或者您刚刚重用了野外可用的一个堆,那么上面适配器的基本逻辑应该很容易理解。 毫无疑问,它最吸引人的方面是connect()方法的实现,因为它的责任是根据请求创建一个PDO对象,然后将其反过来用于对数据库调度查询。

While admittedly this is pretty dull and boring, it’s helpful for showing how to use a sort of “hard-coded” Delegation in the implementation of adapters which are clever enough for triggering lazy connections to the database. Furthermore, the example below demonstrates how to put the adapter to use:

尽管公认这是相当乏味和无聊的,但它有助于说明如何在适配器的实现中使用一种“硬编码”委托,该适配器足够聪明,可以触发到数据库的惰性连接。 此外,下面的示例演示了如何使用适配器:

<?php
$adapter = new PdoAdapter("mysql:dbname=mydatabase",
    "myfancyusername", "myhardtoguespassword");

$guests = $adapter->executeQuery("SELECT * FROM users WHERE role = :role", array(":role" => "Guest"));

foreach($guests as $guest) {
    echo $guest->name . " " . $guest->email . "<br>";
}

Call me an unbearable pedantic if you want to, but consuming the adapter is actually a simple process, especially when analyzed from the client code’s standpoint. The major pitfall is that the adapter hides a dependency. This introduces a nasty coupling, hence making the whole implementation a little bit dirty. On the flip side, the adapter’s most appealing virtue rests with its capability for calling the database only when the situation warrants. Naturally, this sort of trade off can be worked out pretty easily by injecting a factory in the adapter, which would be charged with creating a PDO object when needed.

如果愿意,可以称呼我为难以忍受的学徒,但是使用适配器实际上是一个简单的过程,尤其是从客户端代码的角度进行分析时。 主要的陷阱是适配器隐藏了依赖性。 这引入了讨厌的耦合,因此使整个实现变得有点脏。 另一方面,适配器最吸引人的优点在于它仅在情况允许时调用数据库的功能。 自然地,可以通过在适配器中注入工厂来轻松解决这种折衷,工厂将在需要时负责创建PDO对象。

Even though this approach effectively is the best of both worlds, since it actively promotes the use of Dependency Injection while still employing Delegation, in my view it is somewhat overkill, at least in this case in particular.

尽管这种方法实际上是两全其美的方法,但由于它在仍然雇用委派的情况下积极地促进了依赖注入的使用,我认为这有些过头了,至少在这种情况下尤其如此。

总结思想 (Closing Thoughts)

I have to admit that it’s rather challenging to come to a judgement on the flaws and niceties exposed by Inheritance, Composition and Delegation without falling into a shallow, shy analysis. Even so, taking the plunge is definitively worthwhile considering each approach is a central pillar of the OOP paradigm.

我必须承认,要对继承,组合和委派所暴露的缺陷和优点进行判断,而不要进行浅浅,害羞的分析,这是具有挑战性的。 即便如此,考虑到每种方法都是OOP范式的中心Struts,绝对值得尝试。

Moreover, while in this case I intentionally did an isolated “per approach” evaluation, something that hopefully was instructive in the end, it’s possible to wire up all the approaches together to implement more efficient code reuse strategies. There’s no mutual exclusion here.

此外,尽管在这种情况下,我有意进行了隔离的“每种方法”评估,但最终还是很有帮助的,但可以将所有方法结合起来以实施更有效的代码重用策略。 这里没有互斥。

Needless to say, it doesn’t make much sense to pile them up blindly just because you can. In all cases, be conscious and make sure to narrow their usage to the context you’re dealing with.

不用说,仅仅因为可以就将它们盲目地堆积起来没有多大意义。 在所有情况下,请保持意识,并确保将它们的使用范围缩小到您要处理的上下文。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/reusing-implementation-a-walk-through-of-inheritance-composition-and-delegation/

c++继承和重用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值