Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Applied Java™ Patterns - Stephen Stelting, Olav Maassen.pdf
Скачиваний:
202
Добавлен:
24.05.2014
Размер:
2.84 Mб
Скачать

Depending on their behavior, some Decorator layers may be shared among multiple component objects (normally, layers that have stateless behavior, i.e. no state is maintained or used). This can reduce memory consumption in the system.

When taken to an extreme, the Decorator pattern usually produces a large number of layers: this means lots of little objects between a user and the real object. This can have a number of consequences. Debugging and testing code becomes more difficult, and the operating speed of a system can be reduced if the Decorator is improperly designed.

You must ensure that object equality is treated properly; this is especially important for the Decorator pattern, since object layers sit “in front” of each other. Typically, if equality testing is required in an application, you must code an equality operation that identifies the underlying object, or the combination of the base object and the order and “values” of each of the layers, rather than just the top layer.

Finally, it might require some work to properly handle removing layers from a system, since they could exist anywhere within the Decorator chain. To simplify matters, some Decorators define both a forward and a backward reference to make them easier to remove.

Pattern Variants

Pattern variants include the following:

As mentioned in “ Benefits and Drawbacks,” it is sometimes desirable to develop Decorator classes with a forward and a backward reference to make them easier to remove as a system runs.

Some Decorator implementations don’t use an abstract Decorator. Normally, this variation is used when there is only a single variation possible for the component.

You can create overriding Decorators, which will redefine some parts of a component’s behavior. Take care when using such a Decorator, however, since components based on this pattern can exhibit unpredictable behavior unless there are strict rules in the code governing when and how behavior can be overridden.

Related Patterns

Related patterns include the following:

Adapter (page 142) – The Adapter pattern is intended to change the interface on the same functionality, whereas the Decorator leaves the interface the same but changes the functionality.

Composite (page 157) – The Decorator may be viewed as a simpler version of the Composite pattern; instead of having a collection of Components, the Decorator keeps a maximum of one reference to another Component. The other difference is that the Decorator enhances the functionality instead of just passing on the method calls.

Strategy (page 114) – The Decorator pattern is used to modify or extend an object's external functionality, while the Strategy pattern is used to modify an object's internal behavior.

Intercepting Filter [CJ2EEP] – The Intercepting Filter pattern uses the Decorator pattern to decorate a service request without having to change the request.

Example

Note:

For a full working example of this code example, with additional supporting classes and/or a RunPattern class, see “ Decorator ” on page 462 of the “ Full Code Examples ” appendix.

This example demonstrates how to use the Decorator pattern to extend the capability of the elements in a project. The foundation of the project is the ProjectItem interface. It is implemented by any class that can be used within a project. In this case, ProjectItem defines a single method, getTimeRequired.

Example 3.14 ProjectItem.java

116

1.import java.io.Serializable;

2.public interface ProjectItem extends Serializable{

3.public static final String EOL_STRING = System.getProperty("line.separator");

4.public double getTimeRequired();

5.}

Task and Deliverable implement ProjectItem and provide the basic project functionality. As in previous demonstrations, Task represents some job in a project and Deliverable represents some concrete product.

Example 3.15 Deliverable.java

1.public class Deliverable implements ProjectItem{

2.private String name;

3.private String description;

4.private Contact owner;

5.

6.public Deliverable(){ }

7.public Deliverable(String newName, String newDescription,

8.Contact newOwner){

9.name = newName;

10.description = newDescription;

11.owner = newOwner;

12.}

13.

14.public String getName(){ return name; }

15.public String getDescription(){ return description; }

16.public Contact getOwner(){ return owner; }

17.public double getTimeRequired(){ return 0; }

18.

19.public void setName(String newName){ name = newName; }

20.public void setDescription(String newDescription){ description = newDescription; }

21.public void setOwner(Contact newOwner){ owner = newOwner; }

22.

23.public String toString(){

24.return "Deliverable: " + name;

25.}

26.}

Example 3.16 Task.java

1.import java.util.ArrayList;

2.import java.util.Iterator;

3.public class Task implements ProjectItem{

4.private String name;

5.private ArrayList projectItems = new ArrayList();

6.private Contact owner;

7.private double timeRequired;

8.

9.public Task(){ }

10.public Task(String newName, Contact newOwner,

11.double newTimeRequired){

12.name = newName;

13.owner = newOwner;

14.timeRequired = newTimeRequired;

15.}

16.

17.public String getName(){ return name; }

18.public ArrayList getProjectItems(){ return projectItems; }

19.public Contact getOwner(){ return owner; }

20.public double getTimeRequired(){

21.double totalTime = timeRequired;

22.Iterator items = projectItems.iterator();

23.while(items.hasNext()){

24. ProjectItem item = (ProjectItem)items.next();

25. totalTime += item.getTimeRequired();

26.}

27.return totalTime;

28.}

29.

30.public void setName(String newName){ name = newName; }

31.public void setOwner(Contact newOwner){ owner = newOwner; }

32.public void setTimeRequired(double newTimeRequired){ timeRequired = newTimeRequired; }

34.public void addProjectItem(ProjectItem element){

35.if (!projectItems.contains(element)){

36. projectItems.add(element);

37.}

38.}

117

39.public void removeProjectItem(ProjectItem element){

40.projectItems.remove(element);

41.}

42.

43.public String toString(){

44.return "Task: " + name;

45.}

46.}

It's time to introduce a decorator to extend the basic capabilities of these classes. The class ProjectDecorator will provide the central ability to augment Task and Deliverable.

Example 3.17 ProjectDecorator.java

1.public abstract class ProjectDecorator implements ProjectItem{

2.private ProjectItem projectItem;

3.

4.protected ProjectItem getProjectItem(){ return projectItem; }

5.public void setProjectItem(ProjectItem newProjectItem){ projectItem = newProjectItem; }

7.public double getTimeRequired(){

8.return projectItem.getTimeRequired();

9.}

10.}

The ProjectDecorator implements the ProjectItem interface and maintains a variable for another ProjectItem, which represents the “decorated” element. Note that ProjectDecorator delegates the getTimeRequired method to its internal element. This would be done for any method that would depend on the functionality of the underlying component. If a Task with a required time of five days were decorated, you would still expect it to return a value of five days, regardless of any other capabilities it might have.

There are two subclasses of ProjectDecorator in this example. Both demonstrate a way to add some extra feature to project elements. The DependentProjectItem class is used to show that a Task or Deliverable depends on another ProjectItem for completion.

Example 3.18 DependentProjectItem.java

1.public class DependentProjectItem extends ProjectDecorator{

2.private ProjectItem dependentItem;

3.

4.public DependentProjectItem(){ }

5.public DependentProjectItem(ProjectItem newDependentItem){

6.dependentItem = newDependentItem;

7.}

8.

9. public ProjectItem getDependentItem(){ return dependentItem; }

10.

11.public void setDependentItem(ProjectItem newDependentItem){ dependentItem =

newDependentItem; }

12.

13.public String toString(){

14.return getProjectItem().toString() + EOL_STRING

15. + "\tProjectItem dependent on: " + dependentItem;

16.}

17.}

SupportedProjectItem decorates a ProjectItem, and keeps an ArrayList of supporting documents—file objects that represent additional information or resources.

Example 3.19 SupportedProjectItem.java

1.import java.util.ArrayList;

2.import java.io.File;

3.public class SupportedProjectItem extends ProjectDecorator{

4.private ArrayList supportingDocuments = new ArrayList();

6.public SupportedProjectItem(){ }

7.public SupportedProjectItem(File newSupportingDocument){

8.addSupportingDocument(newSupportingDocument);

9.}

10.

11.public ArrayList getSupportingDocuments(){

12.return supportingDocuments;

13.}

118

14.

15.public void addSupportingDocument(File document){

16.if (!supportingDocuments.contains(document)){

17. supportingDocuments.add(document);

18.}

19.}

21.public void removeSupportingDocument(File document){

22.supportingDocuments.remove(document);

23.}

24.

25.public String toString(){

26.return getProjectItem().toString() + EOL_STRING

27. + "\tSupporting Documents: " + supportingDocuments;

28.}

29.}

The benefit of defining additional capabilities in this way is that it is easy to create project items that have a combination of capabilities. Using these classes, you can make a simple task that depends on another project item, or a task with supporting documents. You can even chain Decorators together and create a task that depends on another task and has supporting documents. This flexibility is a key strength of the Decorator pattern.

119