模板方法模式-覆盖策略逻辑

The first time I looked at the Strategy pattern it was love at first sight. The pattern’s logic seemed to be a mythical panacea come true, where the use of Composition over Inheritance not only was exploited to its fullest, but the implementation was so clean and elegant that its charming influence was hard to resist. What could be more seductive than passing a few implementers of a given contract into one or more consumers allowing them to digest different algorithms at runtime through the reins of Polymorphism?

我第一次看到“ 战略”模式是一见钟情。 模式的逻辑似乎是实现了神话般的灵丹妙药,在这种模式中,不仅充分利用了“继承继承”的用法,而且实现如此简洁雅致,难以抗拒其迷人的影响。 还有什么比将给定合同的几个实现者传递给一个或多个使用方,让他们在运行时通过多态的方法来消化不同算法更诱人的呢?

Of course, when I started coding strategies the magic of that “first date” vanished. I realized that some of my strategy classes barely carried state, or even worse, shared extensive chunks of duplicated implementations with each other, all smelly symptoms of a bad design.

当然,当我开始编码策略时,“第一次约会”的魔力消失了。 我意识到我的一些策略类几乎没有状态,或者更糟的是,彼此之间共享大量重复实现的大块,所有这些都是不良设计的臭味。

In hindsight, this clever quote from C. S. Lewis, sums up a valuable lesson for me: “Experience is the most brutal of teachers.” The first tough lesson I learned from working with strategy classes is that sooner or later they tend to suffer of stateless issues rooted in the pattern’s intrinsic nature, and unfortunately they can’t be sorted out without drastic refactoring techniques.

事后看来, CS Lewis的这句聪明的话给我总结了一个宝贵的教训:“经验是最残酷的老师。” 我从与策略类一起工作中学到的第一个艰难的教训是,它们迟早会遭受基于模式固有本质的无状态问题的困扰,不幸的是,如果没有激烈的重构技术就无法解决它们。

While dealing with a few stateless issues here and there is something I can stand, even at the expense of being blamed for writing procedural code inside of class constructs, things are radically different when the same logic appears across multiple classes. This facet has nothing to do with the pattern’s essence or with some other tangled reason… it’s simply a nasty consequence of clunky design, many times caused by reluctance to walk Inheritance’s path.

尽管在这里处理了一些无状态问题,但我仍然可以忍受,尽管以在类构造内部编写过程代码而受责备为代价,但是当相同的逻辑出现在多个类中时,情况就大为不同。 这个方面与模式的本质或其他一些纠结的原因无关……这仅仅是笨拙的设计的令人讨厌的结果,很多时候是由于不愿意沿袭继承的道路而造成的。

This bring us back to the question whether it’s feasible to eliminate duplicated strategy logic via Inheritance rather than switching over to plain Composition. Indeed it is, and the clean up process can be conducted through an ubiquitous pattern known as Template Method design pattern.

这使我们回到了这样的问题:通过继承消除重复的策略逻辑而不是切换到简单的组合是否可行。 确实如此,清理过程可以通过称为模板方法设计模式的普遍模式进行。

Despite the flourishy name, the logic behind the Template Method pattern is ridiculously easy to assimilate. Simply put, there’s a base class (usually an abstract one), which declares a concrete method (a.k.a. the template) responsible for outlining the steps or hooks of a certain algorithm. Most of the time the base type provides boilerplate implementation for some of those steps and the remaining ones are delegated to subclasses. In turn, the subtypes override the unimplemented steps and provide their own implementations, this way creating distilled versions of the algorithm while shared implementation is neatly preserved inside the base class.

尽管名称繁琐,但“模板方法”模式背后的逻辑却很容易被吸收。 简而言之,有一个基类(通常是抽象类),它声明了一个具体方法(又称模板),负责概述某种算法的步骤或挂钩。 大多数时候,基本类型为某些步骤提供了样板实现,其余步骤则委托给子类。 反过来,子类型将覆盖未实现的步骤并提供其自己的实现,以这种方式创建算法的精简版本,同时将共享实现完整地保留在基类内部。

Beyond its simple nature, Template Method does prove that Inheritance can be a powerful approach when it comes to reusing common chunks of strategy logic. In this article I’ll be demonstrating some conrete examples to show how to use the pattern’s forces for programmatically rendering some basic jQuery-based image sliders.

除了简单性之外,Template Method确实证明了在重用常见策略逻辑块时,继承可以是一种强大的方法。 在本文中,我将演示一些具体示例,以展示如何使用模式的作用力以编程方式渲染一些基于jQuery的基本图像滑块。

构建基于jQuery的图像滑块 (Building a jQuery-Based Image Slider)

Even though template methods have a prolific presence in several frameworks that provide partial implementations for a given component while letting subtypes override abstract methods according to more refined and specific needs, it’s rather difficult to find examples on the Internet that showcase how to exploit the pattern’s functionality in realistic use cases. By no means do I intend to fill this gap, as it can be filled better by someone else. My goal here is merely to illustrate how to employ the pattern in the generation of markup segments that must cling to a given sequence. A nice example of this is the rendering of a jQuery-driven image slider, whose “underlying strategy algorithm” could be, with some subtle slants of course, dissected into the following steps:

即使模板方法在多个框架中都有大量存在,这些框架为给定组件提供部分实现,同时让子类型根据更精细和更具体的需求覆盖抽象方法,但在Internet上找到展示如何利用模式功能的示例还是相当困难的在实际的用例中。 我绝不打算填补这一空白,因为其他人可以更好地填补这一空白。 我的目的仅仅是说明如何在必须依附给定序列的标记段的生成中采用该模式。 一个很好的例子是绘制一个jQuery驱动的图像滑块,其“基本策略算法”可以通过一些细微的倾斜而分解为以下步骤:

  • Render the target images inside a block-level element, hooked up to an “id” or “class” attribute.

    在块级元素内渲染目标图像,该元素连接到“ id”或“ class”属性。
  • Include jQuery.

    包括jQuery。
  • Include the slider’s jQuery plug-in.

    包括滑块的jQuery插件。
  • Render the JavaScript that gets the slider up and running.

    渲染使滑块启动并运行JavaScript。

With these generic steps outlined, it should be pretty easy to create a base abstract class and drop into it a template method that implements partially some of these steps while delegating the implementation of the remaining ones to a few subclasses. To keep things concise, the plug-in that I’m going to use in pursuit of my didactical cause will be the nifty Cycle.

概述了这些通用步骤之后,应该很容易创建一个基本抽象类,并将一个模板方法放到其中,该模板方法部分地实现了其中一些步骤,而将其余步骤的实现委派给了几个子类。 为了简洁起见,我将用于追求教学目的的插件将是一个漂亮的Cycle

Here’s how the aforementioned base class would look like:

这是上述基类的样子:

<?php
namespace LibraryViewHelper;

abstract class AbstractCycleSlider
{
    protected $images = array();
    
    public function __construct(array $images) {
        if (empty($images)) {
            throw new InvalidArgumentException(
                "No images were supplied.");
        }
        foreach ($images as $image) {
            $extension = pathinfo($image, PATHINFO_EXTENSION);
            if (!in_array($extension, array("gif", "jpg", "png"))) {
                throw new OutOfRangeException(
                    "Only GIF/JPG/PNG files are allowed.");
            }
        }
        $this->images = $images;
    }
    
    // Hook method for rendering the target images
    protected function renderImages() {
        $output = '<div id="slider">';
        foreach ($this->images as $image) {
            $output .= '<img src="' . $image . '">';
        }
        $output .= "</div>";
        return $output;
    }
    
    // Hook method for including jQuery and the Cycle plug-in
    protected function renderLibraries() {
        return <<<ENDHTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://cloud.github.com/downloads/malsup/cycle/jquery.cycle.all.latest.js></script>
ENDHTML;
    }
    
    // Hook method for rendering a transition effect (overridden by
    // subclasses)
    abstract protected function renderEffect();
    
    // Template method for rendering the slider
    public final function render() {
        return $this->renderImages()
            . $this->renderLibraries()
            . $this->renderEffect();
    }
}

The only responsibility of the AbstractCycleSlider class is to provide implementation for some of the steps required for rendering an elemental Cycle-based slider. The whole rendering sequence is in this case defined by the render() method which effectively acts as the actual “template” for the steps in question.

AbstractCycleSlider类的唯一责任是为呈现基本的基于Cycle的滑块所需的某些步骤提供实现。 在这种情况下,整个渲染序列由render()方法定义,该方法实际上充当了所讨论步骤的实际“模板”。

The class does a decent work encapsulating common strategy logic behind its slim API, sure, but on its own it’s not very useful. But let’s no rush to pass judgment just yet, though, because the functionality the template method pattern offers rests on the benefits provided by a hierarchical structure where subtypes are responsible for providing strategy logic the base type can’t supply by default.

该类确实做了不错的工作,将通用的策略逻辑封装在其苗条的API后面,但是单独使用它并不是很有用。 但是,让我们不要急于通过判断,因为模板方法模式提供的功能取决于层次结构所提供的好处,在该层次结构中,子类型负责提供默认类型基础策略无法提供的策略逻辑。

If you scan through AbstractCycleSlider, you’ll see here’s exactly where the renderEffect() method comes into play which should be implemented down the line by one or more subclasses, that way generating disparate –yet functional–versions of the base slider.

如果您通过AbstractCycleSlider扫描,您会发现这里正是renderEffect()方法起作用的地方,应该由一个或多个子类在此行下实现,从而生成基础滑块的不同版本,但仍具有功能。

基本滑块的改进实现 (Refined Implementations of the Base Slider)

Creating a few working Cycle-driven sliders is a pretty straightforward process that boils down to subclassing the base AbstractCycleSlider and then supplying concrete implementations for its renderEffect() method.

创建一些工作的,由Cycle驱动的滑块是一个非常简单的过程,可以归结为对基础AbstractCycleSlider进行子类化,然后为其renderEffect()方法提供具体的实现。

The following subclasses do precisely that:

以下子类正是这样做的:

<?php
namespace LibraryViewHelper;

class FadeSlider extends AbstractCycleSlider
{
    protected function renderEffect() {
        return <<<ENDHTML
<script>
jQuery(document).ready(function () {
    jQuery("#slider").cycle({fx: "fade"});
});
</script>
ENDHTML;
    }
}
<?php
namespace LibraryViewHelper;

class ScrollSlider extends AbstractCycleSlider
{
    protected function renderEffect() {
        return <<<ENDHTML
<script>
jQuery(document).ready(function () {
    jQuery("#slider").cycle({fx: "scrollDown"});
});
</script>
ENDHTML;
    }
}

The implementation of the renderEffect() method provided by the above subclasses is rendering some JavaScript snippets charged with generating a couple of transition effects (Fade and Scroll) bundled with the Cycle plug-in. This shows how to delegate the implementation of strategy logic defined in a template method to a few subtypes.

上述子类提供的renderEffect()方法的实现是渲染一些JavaScript代码段,这些代码段负责生成与Cycle插件捆绑在一起的两个过渡效果(Fade和Scroll)。 这显示了如何将模板方法中定义的策略逻辑的实现委派给几个子类型。

If you want to put the subclasses to work, arm yourself with a nice set of sample images and try out the following:

如果要使用子类,请准备好一组示例图像,然后尝试以下操作:

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

$images = array(
    "sample_image1.jpg",
    "sample_image2.jpg",
    "sample_image3.jpg",
    "sample_image4.jpg",
    "sample_image5.jpg"
}; 

$slider = new FadeSlider($images);
echo $slider->render();

If all goes as expected, the above script should render a nifty image slider where the target images are looped by using a soft fade-in/out transition effect.

如果一切都按预期进行,则上述脚本应渲染一个漂亮的图像滑块,在其中使用柔和的淡入/淡出过渡效果将目标图像循环播放。

If you change your mind and want to set up a fancy scrolling slider instead, just create an instance of the ScrollSlider class instead of its counterpart FadeSlider. Consuming the existing sliders, or eventually creating a few new ones, boils down to just providing custom implementations for the renderEffect() method.

如果您改变主意,而是想设置一个漂亮的滚动滑块,只需创建ScrollSlider类的实例而不是其对应的FadeSlider 。 消耗现有的滑块,或者最终创建一些新的滑块,可以归结为只为renderEffect()方法提供自定义实现。

The bad news, on the flip side, is that each slider class will be nothing but a subtle variation of the others, meaning that renderEffect() will always get its ecosystem polluted with duplicated code.

不利的一面是,每个滑块类仅是其他滑块类的细微变化,这意味着renderEffect()始终会受到重复代码的污染。

This doesn’t mean that using the template method design pattern is a bad thing at all. It’s just that in this case the custom implementations of it are way too similar to each other because we’re dealing with the same jQuery plug-in each time! The whole design would be a lot more efficient if we appealed to the pattern for rendering sliders based on different plugins as well.

这并不意味着使用模板方法设计模式根本不是一件坏事。 只是在这种情况下,它的自定义实现彼此太相似了,因为我们每次都处理同一个jQuery插件! 如果我们也呼吁使用基于不同插件来渲染滑块的模式,则整个设计将更加高效。

使用不同的插件 (Working with Different Plug-Ins)

To recreate the above situation, say we want to render two disparate i1mage sliders: the first one would use the already familiar Cycle, and the second one would use Orbit. In that case, we should first refactor the earlier AbstractCycleSlider class to define a more granular template method:

为了重现上述情况,假设我们要渲染两个完全不同的i1mage滑块:第一个将使用已经熟悉的Cycle,第二个将使用Orbit 。 在这种情况下,我们应该首先重构早期的AbstractCycleSlider类,以定义更精细的模板方法:

<?php
namespace LibraryViewHelper;

abstract class AbstractSlider
{
    protected $images = array();
    
    public function __construct(array $images) {
        // the same constructor implementation
    }
    
    // Hook method for rendering the target images
    protected function renderImages() {
        $output = '<div id="slider">';
        foreach ($this->images as $image) {
            $output .= '<img src="' . $image . '">';
        }
        $output .= "</div>";
        return $output;
    }

    // Hook method for including jQuery
    protected function renderjQuery() {
        return <<<ENDHTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
</script>
ENDHTML;
    }

    // Hook method for including the slider's dependencies
    // (overridden by subclasses)
    abstract protected function renderDependencies();

    // Hook method for rendering a sliding effect (overridden by
    // subclasses)
    abstract protected function renderEffect();
    
    // Template method for rendering the slider
    public final function render() {
        return $this->renderImages()
            . $this->renderjQuery()
            . $this->renderDependencies()    
            . $this->renderEffect();
    }
}

This definitely looks better. Since the render() method now has been dissected into more atomic, finer-grained steps, it’s simple to create refined implementations of it.

这肯定看起来更好。 由于现在将render()方法分解为更原子的,更细粒度的步骤,因此可以轻松地创建精细的实现。

The below subclass is a basic wrapper for a Cycle-based slider:

下面的子类是基于Cycle的滑块的基本包装:

<?php
namespace LibraryViewHelper;

class Cycle extends AbstractSlider
{
    protected function renderDependencies() {
        return <<<ENDHTML
<script src="http://cloud.github.com/downloads/malsup/cycle/jquery.cycle.all.latest.js">
</script>
ENDHTML;
    }

    protected function renderEffect() {
        return <<<ENDHTML
<script>
jQuery(document).ready(function () {
    jQuery("#slider").cycle({fx: "fade"});
});
</script>
ENDHTML;
    }
}

And this one brings to life an Orbit-based slider:

这使基于轨道的滑块栩栩如生:

<?php
namespace LibraryViewHelper;

class Orbit extends AbstractSlider
{
    protected function renderDependencies() {
        return <<<ENDHTML
<link rel="stylesheet" href="/public/css/orbit.css">
<script src="/public/js/orbit.min.js">
</script>
ENDHTML;
    }

    protected function renderEffect() {
        return <<<ENDHTML
<script>
jQuery(document).ready(function () {
    jQuery("#slider").orbit();
});
</script>
ENDHTML;
    }
}

Now with these subtypes providing custom implementations for the renderDependencies() and renderEffect() methods, each plug-in can be independently rendered to the browser via a script like this:

现在,通过这些子类型为renderDependencies()renderEffect()方法提供了自定义实现,可以通过如下脚本将每个插件独立呈现给浏览器:

<?php
$orbit = new Orbit($images);
echo $orbit->render();

$cycle = new Cycle($images);
echo $cycle->render();

I’m not necessarily an active proponent of this approach for adding a layer of behavior to your HTML documents, although it is neat and unobtrusive, because the process is a lot more cumbersome than just parsing a few templates hooked up to a view class or something like that. Still, it shows in a nutshell what’s actually under the hood of the Template Method pattern and how to exploit its niceties in the implementation of a few customizable view helpers.

我不一定会积极支持这种为HTML文档添加行为层的方法,尽管这种方法既简洁又不引人注目,因为该过程比仅解析连接到视图类或模板的一些模板要麻烦得多。这样的事情。 仍然概括地说,它显示了“模板方法”模式的实质内容,以及如何在实现一些可自定义视图助手的过程中利用其优点。

结束语 (Closing Remarks)

Saying that strategy logic has been traditionally implemented and consumed through Composition isn’t breaking news. Providing consumers with a set of independent polymorphic algorithms that adhere to the formalities of a given contract makes this path a very tempting one to travel, which additionally doesn’t expose the typical oddities raised by the base type/subtype paradigm.

说策略逻辑传统上是通过组合实现和使用的,这并不是什么新闻。 为消费者提供一组独立的多态算法,这些算法遵循给定合同的形式,使这条路非常诱人,而且不会暴露基本类型/子类型范式引起的典型异常。

But on the flip side, the implementation of Inheritance-based solutions, such as the one provided by the Template Method pattern, can be effective when the steps of a certain algorithm should be selectively overridden by subtypes while keeping common strategy logic safely encapsulated in a base type.

但另一方面,当某种算法的步骤应被子类型有选择地覆盖,同时又将公共策略逻辑安全地封装在一个子类中时,基于继承的解决方案(例如模板方法模式提供的解决方案)的实施可能会很有效。基本类型。

Starting a flame war over which pattern fits the bill best doesn’t make much sense at all; just make sure to pick the one you feel gets along best with your needs at hand.

对哪种模式最适合进行一场火焰战争完全没有任何意义; 只要确保选择一个您觉得与自己的需求相处得很好的人即可。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/overriding-strategy-logic-the-template-method-pattern/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值