+ -
当前位置:首页 → 问答吧 → 12->The Decorator Pattern(HRTSEA预定)

12->The Decorator Pattern(HRTSEA预定)

时间:2006-03-27

来源:互联网

12
The Decorator
Pattern
IF YOU’VE DEVELOPED OBJECT-ORIENTED PHP code for even a short time or have come this far
in this book, you know that you can change or augment the capabilities of a class via inheritance,
an essential feature of any object-oriented programming language. If an existing PHP class is missing
a method or if an existing method needs a little more “oomph,” you simply extends the class into
a new class and bolt on the extra code.
But subclassing is not always possible or appropriate. What if you want to change the behavior of
an object after it’s been instantiated? Or, what if you want to slightly extend the behavior of many
classes? The former can only be done at run-time; the latter is obviously possible, but may lead to a
proliferation of subtly-different classes―a maintenance nightmare.
The Problem
How can you structure your code to easily add conditional or rarely used features without putting the
extra code directly in your class?

作者: PHPChina   发布时间: 2006-03-26

...

作者: Bantu   发布时间: 2006-03-28

没人??上一章基本完成。这章也给我了。

[ 本帖最后由 HRTSEA 于 2006-3-29 11:43 编辑 ]

作者: HRTSEA   发布时间: 2006-03-29

你要翻译这12章?

作者: PHPChina   发布时间: 2006-03-29

欢迎!鼓励下。另外你前面翻译的中文回复到帖子上。我们进行校对。

作者: PHPChina   发布时间: 2006-03-29

12装饰器模式
若你从事过面向对象的php开发,即使很短的时间或者仅仅通过本书了解了一些,你会知道,你可以 通过继承改变或者增加一个类的功能,这是所有面向对象语言的一个基本特性。如果已经存在的一个php类缺少某些方法,或者须要给方法添加更多的功能(魅力),你也许会仅仅继承这个类来产生一个新类―这建立在额外的代码上。
但是产生子类并不总是可能或是合适的。如果  你希望改变一个已经初始化的对象的行为,你怎么办?或者,你希望继承许多类的行为,改怎么办?前一个,只能在于运行时完成,后者显然时可能的,但是可能会导致产生大量的不同的类―可怕的事情。
问题
你如何组织你的代码使其可以容易的添加基本的或者一些很少用到的 特性,而不是直接不额外的代码写在你的类的内部?
解决方案
装饰器模式提供了改变子类的灵活方案。装饰器模式允许你在不引起子类数量爆炸的情况下动态的修饰对象,添加特性。
当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。看看以下例子,你可以更好的理解这种观点。考虑一个建立在组件概念上的“form”表单库,在那里你需要为每一个你想要表现的表单控制类型建立一个类。这种类图可以如下所示:

Select and TextInput类是组件类的子类。假如你想要增加一个“labeled”带标签的组件―一个输入表单告诉你要输入的内容。因为任何一个表单都可能需要被标记,你可能会象这样继承每一个具体的组件:

上面的类图看起来并不怎么坏,下面让我们再增加一些特性。表单验证阶段,你希望能够指出一个表单控制是否合法。你为非法控制使用的代码又一次继承其它组件,因此又需要产生大量的子类:
That class diagram doesn’t look too bad, so let’s add another feature. During form validation you
want to be able to indicate if a form control is invalid. The code you need to apply for an “invalid”
control again applies to any widget, so it’s off to the races to make even more subclasses:

这里子类爆炸并不是唯一的问题。想一想那些重复的代码,你需要重新设计你的整个类层次。有没有更好的方法!确实,装饰器模式是避免这种情况的好方法。
装饰器模式结构上类似与代理模式(参见第2章)。一个装饰器对象保留有对对象的引用,而且忠实的重新建立被装饰对象的公共接口。装饰器也可以增加方法,扩展被装饰对象的接口,任意重载方法,甚至可以在脚本执行期间有条件的重载方法。
Here the explosion of subclasses isn’t the only problem. Think about all of the duplicated code you’d now have spread throughout your entire class hierarchy. There has to be a better way! Indeed, the Decorator pattern is the way out of this mess.
The  Decorator  pattern  is  structurally  very  similar  to  the  Proxy  pattern  (see  Chapter  11).  A Decorator object holds a reference to an object and faithfully recreates the public interface to the decorated object. The Decorator can also add methods, extending the interface of the decorated object or can override methods at will, even overriding methods conditionally during the execution
of a script.
为了探究装饰器模式,让我们以前面讨论过的表单组件库为例,并且用装饰器模式而不是继承,实现“lable”和“invalidation”两个特性。
To explore the Decorator pattern, let’s take the notion of the form widget library discussed ear- lier and implement the label and invalidation features using the Decorator pattern instead of inher- itance.
Sample Code
示例代码:
组件库包含哪些特性?
1.        容易创建表单元素
2.        将表单元素以html方式输出
3.        在每个元素上实现简单的验证
What should the widget library do?
        Easily create form elements;
        Output form elements as an HTML form; and
        Perform some simple validation on each element.
本例中,我们创建一个包含姓,名,邮件地址,输入项的表单。所有的区域都是必须的,而且E-mail必须看起来是有效的E―mail地址。用HTML语言表示,表单的代码象下面所示:
For this example, let’s create a form with inputs for a first name, a last name, and an email address. All of the fields should be required and the email address should vaguely resemble a valid email address. As HTML, the form might look something like this:


<form  action=”formpage.php”  method=”post”>
<b>First  Name:</b>  <input  type=”text”  name=”fname”  value=””><br>
<b>Last  Name:</b>  <input  type=”text”  name=”lname”  value=””><br>
<b>Email:</b>  <input  type=”text”  name=”email”  value=””><br>
<input  type=”submit”  value=”Submit”>
</form>


增加一些css样式后,表单渲染出来如下图所示:
And with a little bit of CSS styling might render like this:

为建立统一的API,我们创建一个基本的组件类(如果这是php5的例子,这或许会使用接口)。既然所有的组件(表单元素)都必须渲染一些输出,组建类可以仅仅只有一个paint()方法。
//代码
To establish a uniform API, let’s create a Widget base class (if this was a PHP5 example, this might be
an interface). Since all widgets (form elements) must render at least some output, Widget holds only
a paint() method.

让我们以一个基本的text输入组件开始。它(组件)必须要包含输入区域的名字(name)而且输入内容可以以HTML的方式渲染。
//代码

Let’s start with a basic text input widget. It must include the name of the input field and the value of
the input and must be able to render as HTML.
一个基本的测试可以验证HTML代码是否正确――作为参数传入给构造函数的名字,值(内容)是否传递到渲染后的输出中:
//代码
A basic test can verify that the HTML is correct and the name and value passed in as parameters to the constructor carry through to the rendered output:

TextInput组件工作正常,但是它的用户接口非常糟糕,它缺少友好的描述,如“First Name” 或者 “Email Address.” 。因此,下一个增加到组件类的合理的特性就是一个描述。我们进入有能够统一增加(一些特性)能力的装饰器模式。
The TextInput widget works, but its user interface is horrible, as it lacks a friendly description, such
as “First Name” or “Email Address.” So, the next logical feature to add to a Widget is a description. Enter the Decorator pattern, which can add a capability uniformly to any Widget.
作为开始,我们建立一个普通的可以被扩展产生具体的特定装饰器的WidgetDecorator类。至少WidgetDecorator类应该能够在它的构造函数中接受一个组件,并复制公共方法paint()。
To start, let’s make a generic lass that can be extended to create specific con- crete decorators. At a minimum, the WidgetDecorator class must accept a Widget in its constructor and replicate the public paint() method.


为建立一个标签(lable),需要传入lable的内容,以及原始的组件:
To construct a label, pass the content of the label and an original widget:

有标签的组件也需要复制paint()方法,并将标签信息增加到输出中:

Labeled also needs to intercept the paint() call and add the label information to the output:
你可以用一个测试检验它:
//代码

You can verify this works with a test like this:


  
我们已经看到TextInput和Labeled类的能力,你可以装配一个类整体来管理表单(form)。
With the basic capabilities of TextInput and Labeled, you can start to assemble a class to manage
the form in aggregate.
FormHandler类有一个静态的build()方法从表单的各种元素创建一个部件的数组。
FormHandler has a static build() method to create an array of Widget form elements:

//代码



Some code to realize FormHandler might be:


现在,这段代码并不能工作―没有通过$_post提交的数据。因为这段代码必须要使用一个MockObject对象 (参见第6章)测试,现在我们可以将$_post数据包装在一个类似哈希的对象中―与Registry(参见第五章)类似,或者模仿WACT的DataSource从Specification pattern (see Chapter 10)
//代码

   
Now, this code doesn’t do you much good without the corresponding $_POST values. Because this
code must be tested using a MockObject (see Chapter 6), let’s wrap the $_POST values in a hash-like object  similar  to  a  Registry  (see  Chapter  5)  or  the  simulated  WACT  DataSource  from  the Specification pattern (see Chapter 10):

作者: HRTSEA   发布时间: 2006-04-08

一个非常方便方法可以扮演Factory方法,可以自动的用$_post数据填充哈希数组。

A convenience method can act as both a Factory and a means of automatically filling the hash withthe keys from $_POST.
使用这个Post类,你可以编辑你的FormHandler::build()  方法,默认使用已经存在的$_post数据:
Using  Post  class,  you  can  modify  to  use  the  existing  $_POST  values  fordefaults:
现在你可以创建一个php脚本使用FormHandler类来产生HTML表单:
//代码

You can now create a PHP script to use this FormHandler to generate the HTML form:




现在,你已经拥有了一个提交给它自身并且能保持posted数据的表单处理(form handler) 类。
You now have a form handler that posts back to itself and retains the posted values.
现在。我们继续为表单添加一些验证机制。方法是编辑另一个组件装饰器类来表达一个“invalid”状态并扩展FormHandler类增加一个validate()方法以处理组件示例数组。如果组件非法(“invalid”),我们通过一个“invalid”类将它包装在<span>元素中。这里是一个证明这个目标的测试                                                                             
//代码 Let’s move on to adding some validation for the form. The approach is to write another Widget Decorator to represent an “invalid” state and to extend the FormHandler class to add a validate() method to process the array of Widget instances.  If a Widget is “invalid,” let’s make it stand out by wrapping it in a <span> element with a class of “invalid”. Here’s a test that demonstrates that goal:



class  WidgetTestCase  extends  UnitTestCase  {
//  ...
function  testInvalid()  {
$text  =&  new  Invalid(new  TextInput(‘email’));
$output  =  $text->paint();

$this->assertWantedPattern(
‘~^<span  class=”invalid”><input[^>]+></span>$~i’,  $output);
}
}

这里是Invalid  WidgetDecorator子类:
//代码Here’s the Invalid WidgetDecorator subclass:



class  Invalid  extends  WidgetDecorator  {
function  paint()  {
return  ‘<span  class=”invalid”>’.$this->widget->paint().’</span>’;
}
}

装饰器的一个有点是你可以将他们串在一起(使用)。Invalid装饰器仅仅知道:它正在包装一个组件:它不必关心组件是否是一个TextInput,  Select,或者是一个有标签的被装饰版本的组件 。
One nice thing about Decorators is that you can chain them together. The Invalid Decorator justknows that it is wrapping a widget: it doesn’t care if the widget is a TextInput, a Select, or a Labeled- decorated version of any Widget.
212

The Decorator Pattern


这导致了下一个合理的测试用例://代码
This leads to the next logical test case:



class  WidgetTestCase  extends  UnitTestCase  {
//  ...
function  testInvalidLabeled()  {
$text  =&  new  Invalid(
new  Labeled(
‘Email’
,new  TextInput(‘email’)));
$output  =  $text->paint();

$this->assertWantedPattern(‘~<b>Email:</b>  <input~i’,  $output);
$this->assertWantedPattern(
‘~^<span  class=”invalid”>.*</span>$~i’,  $output);
}
}
有了Invalid装饰器,我们来处理FormHandler::validate() 方法:
//代码
With the Invalid Decorator in hand, let’s tackle the FormHandler::validate() method:



class  FormHandlerTestCase  extends  UnitTestCase  {
//  ...

function  testValidateMissingName()  {
$post  =&  new  Post;
$post->set(‘fname’,  ‘Jason’);
$post->set(‘email’,  ‘[email protected]’);

$form  =  FormHandler::build($post);
$this->assertFalse(FormHandler::validate($form,  $post));

$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[0]->paint());
$this->assertWantedPattern(‘/invalid/i’,  $form[1]->paint());
$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[2]->paint());
}
}



这个测试捕获(包含)了所有的基本方面:建立一个Post实例的存根,使用它建立一个组件集合,然后将集合传送给validate方法。
//代码This test captures all of the basics:  set up a stub Post instance, use it to build a Widget collection,
and then pass that collection to the validate method.


class  FormHandler  {
function  validate(&$form,  &$post)  {
//  first  name  required
if  (!strlen($post->get(‘fname’)))  {
$form[0]  =&  new  Invalid($form[0]);}

The Decorator Pattern        213



//  last  name  required
if  (!strlen($post->get(‘lname’)))  {
$form[1]  =&  new  Invalid($form[1]);
}
}
}

丑陋的代码
当我看这段代码时,我发现了两个丑陋之处:通过数字索引访问表单元素,需要传递$_post数组
给validation方法。在以后的重构中,最好是创建一个组件集合用一个以表单元素名字索引的关联数组表示或者用一个Registry模式作为更合理的一步。你也可以给类Widget增加一个方法返回它的
当前数值,取消需要传递$_Post实例给Widget集合的构造函数。所有这些都超出了这个例子目的的范围。

Ugly Code
Two “uglies” stare back at me when I look at this code: accessing the form element by a numeric index and  having  to  pass  the  $_Post array  into  the  validation.  In  later  refactoring,  it’d  probably  be  better  to make a Widget collection as an associative array indexed by the form element name, or perhaps a Registryas a next logical step. You could also add a method to the Widget class to return it’s current value, remov-
ing the need to pass around the $_Post instance past the construction of the Widget collection. Both of these are out of scope for the purpose of this example.



为了验证目的,我们继续增加一个简单的 正则方法(regex)来验证email地址://代码
With the names validating, let’s move on to adding a simple regex to validate the email address:



class  FormHandlerTestCase  extends  UnitTestCase  {
//  ...

function  testValidateBadEmail()  {
$post  =&  new  Post;
$post->set(‘fname’,  ‘Jason’);
$post->set(‘lname’,  ‘Sweat’);
$post->set(‘email’,  ‘jsweat_php  AT  yahoo  DOT  com’);

$form  =  FormHandler::build($post);
$this->assertFalse(FormHandler::validate($form,  $post));

$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[0]->paint());
$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[1]->paint());
$this->assertWantedPattern(‘/invalid/i’,  $form[2]->paint());
}
}
实现这个简单的email验证的代码如下:
//代码
Code to implement this simple email validation might look like:



class  FormHandler  {
function  validate(&$form,  &$post)  {
//  first  name  required
if  (!strlen($post->get(‘fname’)))  {
$form[0]  =&  new  Invalid($form[0]);}
//  last  name  required

作者: HRTSEA   发布时间: 2006-04-08

214

The Decorator Pattern



if  (!strlen($post->get(‘lname’)))  {
$form[1]  =&  new  Invalid($form[1]);
}
//  email  has  to  look  real
if  (!preg_match(‘~\w+@(\w+\.)+\w+~’
,$post->get(‘email’)))  {
$form[2]  =&  new  Invalid($form[2]);
}
}
}

你也可以创建一个测试用例以验证form表单何时有效://代码

You can also create a test case for when the form does validate:



class  FormHandlerTestCase  extends  UnitTestCase  {
//  ...

function  testValidate()  {
$post  =&  new  Post;
$post->set(‘fname’,  ‘Jason’);
$post->set(‘lname’,  ‘Sweat’);
$post->set(‘email’,  ‘[email protected]’);

$form  =  FormHandler::build($post);
$this->assertTrue(FormHandler::validate($form,  $post));

$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[0]->paint());
$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[1]->paint());
$this->assertNoUnwantedPattern(‘/invalid/i’,  $form[2]->paint());
}
}
这又提出了在本方法内追踪任何验证失败的需求,因此它可以返回true如果所有的都合格。
//代码
This creates the need to track any validation failures inside the method so it can return true if every-
thing checks out.



class  FormHandler  {
//  ...
function  validate(&$form,  &$post)  {
$valid  =  true;
//  first  name  required
if  (!strlen($post->get(‘fname’)))  {
$form[0]  =&  new  Invalid($form[0]);
$valid  =  false;
}
//  last  name  required
if  (!strlen($post->get(‘lname’)))  {
$form[1]  =&  new  Invalid($form[1]);
$valid  =  false;}



The Decorator Pattern        215



//  email  has  to  look  real
if  (!preg_match(‘~\w+@(\w+\.)+\w+~’
,$post->get(‘email’)))  {
$form[2]  =&  new  Invalid($form[2]);
$valid  =  false;
}
return  $valid;
}
}
那些就是所有需要为页面添加验证的building blocks 。这里是本游戏(章)结尾的一个截图。以及产生它的页面代码:
//代码
Those are all the building blocks required to add validation to the page.  Here’s a screen shot of the
end game.













And the page to generate it:



<html>
<head>
<title>Decorator  Example</title>
<style  type=”text/css”>
.invalid  {color:  red;  }
.invalid  input  {  background-color:  red;  color:  yellow;  }
#myform  input  {  position:  absolute;  left:  110px;  width:  250px;    font-weight:  bold;}
</style>
</head>
<body>
<form  action=”<?php  echo  $_SERVER[‘PHP_SELF’];  ?>”  method=”post”>
<div  id=”myform”>
<?php error_reporting(E_ALL);
require_once  ‘widgets.inc.php’;

$post  =&  Post::autoFill();
$form  =  FormHandler::build($post);
if  ($_POST)  { FormHandler::validate($form,  $post);
}

foreach($form  as  $widget)  {
echo  $widget->paint(),  “<br>\n”;



216

The Decorator Pattern



}

?>
</div>
<input  type=”submit”  value=”Submit”>
</form>
</body>
</html>


结论
装饰器模式是对你产生影响的那些模式中的另一个,当你使用他们工作一段时间以后。装饰器模式允许你可以简单的通过严格的继承问题。你可以这样认为装饰器:在运行时可以有效地改变对象的类或者甚至多次―当你在你的脚本不同的场合使用这个类。
也许装饰器模式最重要的一个方面是它的超过继承的能力。“问题”部分展现了一个使用继承的子类爆炸。基于装饰器模式的解决方案,UML类图展现了这个简洁灵活的解决方案。

Issues
Decorators are another one of those design patterns that grow on you after you’ve worked with them
a bit. The Decorator pattern allows you to easily bypass rigid inheritance problems. You can think of
a Decorator as effectively changing the class of an object at run-time or perhaps even several timesas you use the object in different contexts throughout your scripts.
Perhaps the most important aspect of the Decorator pattern is it’s ability to “trump” inheritance. The “Problem” section showed a subclass explosion using inheritance. With a Decorator-based solu- tion, the UML class diagram now resembles this more succinct and flexible solution:

作者: HRTSEA   发布时间: 2006-04-08

word版本
怎么上传附件???

[ 本帖最后由 HRTSEA 于 2006-4-8 17:59 编辑 ]

作者: HRTSEA   发布时间: 2006-04-08

怎么都没人去wiki。。。。

作者: Bantu   发布时间: 2006-04-08

热门下载

更多