473,398 Members | 2,125 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,398 developers and data experts.

Patterns: the Decorator pattern

11,448 Expert 8TB
Greetings,

last week we talked a bit about the Visitor design pattern. This week we'll
talk a bit about additional functionality that is sometimes wanted, i.e.
the functionality is optional. Assume there is a lot of optional functionality
that people want.

This article discusses the Decorator (or 'Wrapper') pattern. For the sake of
the example we'll use array manipulation. People always fiddle diddle with
arrays, i.e. they copy values from one array to another, move elements around
in arrays etc. etc. as if there were no Object Oriented programming.

Since the early days of Fortran people have been messing around with arrays
and mutilating those poor processors that have to copy data over and over
all the time.

Let's do something about it: The following interface is an abstraction of
an array:
Expand|Select|Wrap|Line Numbers
  1. public interface Sequence<T> {
  2.     public int length();
  3.     public T get(int idx);
  4.     public void set(T, int idx);
  5. }
A Sequence has a length and we can get and set elements in a Sequence; nothing
spectacular. Note that this interface is a generic interface: the Sequence
manipulates elements of type 'T'.

Let's encapsulate a simple array:
Expand|Select|Wrap|Line Numbers
  1. public class ArraySequence<T> implements Sequence<T> {
  2.  
  3.     // the encapsulated array:
  4.     private T[] array;
  5.  
  6.     // the constructor:
  7.     public ArraySequence(T[] array) { this.array= array; }
  8.  
  9.     // interface implementation:
  10.     public int length() { return array.length; }
  11.     public T get(int idx) { return array[idx]; }
  12.     public void set(T elem, int idx) { array[idx]= elem; }
  13. }
Why build all that code for just a simple array? We could've used the array
itself in the first place, so why all this complicated stuff? This is why:
sometimes we want to manipulate arrays but we don't want to copy and move
and swap all those elements over and over again: we build Decorators for it
instead. The ArraySequence class does nothing special: it simply encapsulates
an array for us. Let's build an abstract class from which we can easily build
other concrete Sequence implementations:
Expand|Select|Wrap|Line Numbers
  1. public abstract class AbstractSequence<T> implements Sequence<T> {
  2.     // another sequence:
  3.     protected Sequence<T> seq;
  4.  
  5.     // an abstract method that gives us an index value:
  6.     protected abstract int index(int idx);
  7.  
  8.     // the constructor:
  9.     public AbstractSequence(Sequence<T> seq) { this.seq= seq; }
  10.  
  11.     // interface implementation:
  12.     public int length() { return seq.length(); }
  13.     public T get(int idx) { return seq.get(index(idx)); }
  14.     public void set(T elem, int idx) { seq.set(elem, index(idx)); }
  15. }
The class by itself can't do anything (it's abstract), but it gives us all
the functionality we need for concrete Sequence implementations. The following
Sequence treats all elements of a Sequence in reverse order:
Expand|Select|Wrap|Line Numbers
  1. public class ReverseSequence<T> extends AbstractSequence<T> {
  2.     // implementation of index() method:
  3.     protected int index(int idx) { return seq.length()-idx-1; }
  4.  
  5.     // the constructor:
  6.     public ReverseSequence(Sequence<T> seq) { super(seq); }
  7. }
You see no interface implementation in this class because everything already
was implemented by the AbstractSequence; all that this class implements is
the index() method which gives the additional functionality. Note that the
encapsulated 'seq' Sequence is passed to the superclass in the constructor.

If you create a ReverseSequence, given another Sequence you can treat the
other Sequence as if it were reversed (i.e. last element first and vice versa).
The added functionality is implemented in the index() method. Here's a bit
of code that shows how it's used:
Expand|Select|Wrap|Line Numbers
  1.     Integer[] a= { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
  2.     Sequence rev= new ReverseSequence<Integer>(
  3.                 new ArraySequence<Integer>(a));
  4.     ...
  5.     for (int i= 0, n= rev.length(); i < n; i++)
  6.         System.out.print(rev.get(i)+" ");
  7.     System.out.println();
The output is: 9 8 7 6 5 4 3 2 1 0
From the snippet above you can see why a Decorator is also named a Wrapper:
the Sequence objects 'wrap' other Sequence objects (passed to the constructors).
Also note from the snippet above that our code didn't touch the array directly
anymore after we've wrapped it in a Sequence.

Here's another Sequence implementation: it puts the elements from the first
part of the other Sequence interleaved with the elements from the last part
of the sequence:
Expand|Select|Wrap|Line Numbers
  1. public class MergeSequence<T> extends AbstractSequence<T> {
  2.     // implementation of index() method:
  3.     protected int index(int idx) { 
  4.         if ((idx&1) == 0) return idx/2; // idx is even
  5.         return (seq.length()+idx)/2; // idx is odd
  6.     }
  7.  
  8.     // the constructor:
  9.     public MergeSequence(Sequence<T> seq) { super(seq); }
  10. }
If you use the code snippet above but use a MergeSequence instead of a
ReverseSequence the output will be: 0 5 1 6 2 7 3 8 4 9. I leave it up to
you what the output will be if you wrap your original array in both an
MergeSequence and a ReverseSequence as in:
Expand|Select|Wrap|Line Numbers
  1. Sequence magic= new MergeSequence<Integer>(
  2.             new ReverseSequence<Integer>(
  3.                 new ArraySequence<Integer>(a)));
I also leave it up to you to figure out if it would make any difference if
you'd used this wrapping order instead:
Expand|Select|Wrap|Line Numbers
  1. Sequence magic= new ReverseSequence<Integer>(
  2.             new MergeSequence<Integer>(
  3.                 new ArraySequence<Integer>(a)));
Note that if you wanted to do what these two Wrappers do using just a simple
array you have to play clever tricks and/or use temporary array(s) to hold the
intermediate results.

Here's a bit more complicated Sequence: it catenates two other sequences as
if they were just one bigger Sequence. We do have some work to do in the
implementation of the interface methods for this class so we don't
extend from the AbstractSequence. Here goes:
Expand|Select|Wrap|Line Numbers
  1. public class CatenateSequence<T> implements Sequence<T> {
  2.     // the first and second Sequence:
  3.     private Sequence<T> first, second;
  4.  
  5.     // little helper method:
  6.     private boolean isFirst(int idx) { return idx < first.length(); }
  7.  
  8.     // implementation of index() method:
  9.     private int index(int idx) { 
  10.         if (isFirst(idx)) return idx;
  11.         return idx-first.length();
  12.     }
  13.  
  14.     // the constructor:
  15.     public CatenateSequence(Sequence<T> first, Sequence<T> second) {
  16.         this.first = first;
  17.         this.second= second;
  18.     }
  19.  
  20.     // interface implementation:
  21.     public int length() { return first.length()+second.length(); }
  22.     public T get(int idx) { 
  23.         if (isFirst(idx)) return first.get(index(idx));
  24.         return second.get(index(idx)); 
  25.     }
  26.     public void set(T elem, int idx) {
  27.         if (isFirst(idx)) first.set(elem, index(idx));
  28.         else second.set(elem, index(idx));
  29.     }
  30. }
The last piece of code shows how we can catenate three arrays together
using the CatenateSequence Wrapper:
Expand|Select|Wrap|Line Numbers
  1. Integer[] a= { 0, 1, 2 };
  2. Integer[] b= { 3, 4, 5 };
  3. Integer[] c= { 6, 7, 8, 9 };
  4. ...
  5. Sequence cat= new CatenateSequence<Integer>(
  6.             new ArraySequence<Integer>(a),
  7.             new CatenateSequence<Integer>(
  8.                 new ArraySequence<Integer>(b),
  9.                 new ArraySequence<Integer>(c)));
If you print this Sequence, the output again will be: 0 1 2 3 4 5 6 7 8 9.

Note that sneakily I used another design pattern too for the CatenateSequence
class: the Composite pattern. But we'll talk in a next tip about that one.

Guess what a mess we can make if we wrap the CatenateSequence in the other
Sequences again. Note that not a single array element is moved or copied.
We add functionality at will by wrapping up a Sequence in another Sequence
that implements the functionality for us. We can wrap Sequences in any
order we want and thus we can accomplish any functionality we want. We now
have three classes:

1: ReverseSequence
2: MergeSequence
3: CatenateSequence

If we had implemented any or all of those functionalities using separate
classes we would've ended up with a big number of classes. Now we only
have three of them which we can combine in any order we want and as many
as we want. (I've excluded the ArraySequence class in the count because we
always need it to encapsulate raw, simple arrays).

The Readers, Writers, InputStreams and OutputStreams in the Java core classes
are implemented in a similar way: you wrap (or 'decorate') byte or character
streams using all these wrappers. Each wrapper adds a bit of functionality if
you need it.

Next time we talk a bit about the why and when we can or cannot use inheritance
of implementation (extend a class from another one). The results may be
surprising now and then.

kind regards,

Jos
Apr 29 '07 #1
0 6542

Sign in to post your reply or Sign up for a free account.

Similar topics

8
by: NKOBAYE027 | last post by:
Hi Folks: Can anyone direct me to a good resource on design patterns. I'd like to use them for a project I'm working on regarding set theory - in particular I'd like to use the Decorator pattern to...
2
by: Tim Smith | last post by:
Dear All, Silly question, but I am having trouble understanding the diagram on the inside back cover entitled "Design Pattern Relationships." It shows the relationships between all of the...
8
by: Alex Vinokur | last post by:
Any links to get started with Design Patterns, including samples? Thanks in advance. Alex Vinokur email: alex DOT vinokur AT gmail DOT com http://mathforum.org/library/view/10978.html...
11
by: FluffyCat | last post by:
In Febraury - April of 2002 I put together in Java examples of all 23 of the classic "Gang Of Four" design patterns for my website. Partly I wanted to get a better understanding of those patterns....
12
by: Steve Jorgensen | last post by:
The classing Visual Basic and VBA support for polymorphism, let's face it, is a bit on the weak side, and built-in support for inheritance is non-existent. This little essay is about some patterns...
3
by: yb | last post by:
Hi, I just started reading design patterns and looking at the Lexi example. I'm very new to this so please bear with me. I understand the Decorator pattern, but a bit confused by the Maze...
0
by: AMDRIT | last post by:
I am looking for better concrete examples, as I am a bit dense, on design patterns that facilitate my goals. I have been out to the code project, planet source code, and microsoft's patterns and...
9
by: Christian Hackl | last post by:
Hi! I've got a design question related to the combination of the NVI idiom (non-virtual interfaces, ) and popular object-oriented patterns such as Proxy or Decorator, i.e. those which have the...
9
by: Tyno Gendo | last post by:
Hi I'm trying to learn patterns, which I hope to use in my PHP code, although I'm finding it hard to get any real impression of how patterns fit in properly, I've done the following test code...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.