Labels

Thursday, November 18, 2010

Decorator Design Pattern - Structural

1.1          Introduction


The Decorator patterns allows an objects behavior to be dynamically altered at runtime. i.e It enables to attach additional responsibilities to an object dynamically.
In other words, Decorators provide a flexible alternative to subclassing for extending functionality.
It enables to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.

1.2          Description

The Decorator patterns allows an objects behavior to be dynamically altered at runtime.

This change in behavior is accomplished by wrapping an object (Here 'ConcreteComponent') with a new object.  This new wrapper object is the Decorator. The Decorator object will implement the same interface as the object being wrapped.
Because the decorated object uses the same interface as an undecorated object, other parts of your system do not need to be aware of whether or not one or more decorations are in use.

The decorator (wrapper) can now alter the behavior of the object being wrapped by either completely replacing various methods or by altering the behavior of the methods of the object being decorated.

The Decorator Pattern is used for adding additional functionality to a particular object as opposed to a class of objects. It is easy to add functionality to an entire class of objects by sub classing an object, but it is impossible to extend a single object this way. With the Decorator Pattern, you can add functionality to a single object and leave others like it unmodified.

A Decorator, also known as a Wrapper, is an object that has an interface identical to an object that it contains. Any calls that the decorator gets, it relays to the object that it contains, and adds its own functionality along the way, either before or after the call. This gives you a lot of flexibility, since you can change what the decorator does at runtime, as opposed to having the change be static and determined at compile time by sub classing.

Since a Decorator complies with the interface that the object that it contains, the Decorator is indistinguishable from the object that it contains.  That is, a Decorator is a concrete instance of the abstract class, and thus is indistinguishable from any other concrete instance, including other decorators.   This can be used to great advantage, as you can recursively nest decorators without any other objects being able to tell the difference, allowing a near infinite amount of customization.

1.3          Class Diagram




1.4          Participants

Component   
defines the interface for objects that can have responsibilities added to them dynamically.

ConcreteComponent   
defines an object to which additional responsibilities can be attached.
Decorator   
maintains a reference to a Component object and defines an interface that conforms to Component's interface.

ConcreteDecorator   
adds responsibilities to the component.

1.5          Implementation1


abstract class Component
  {
    public abstract void Operation();
  }

class ConcreteComponent : Component
  {
    public override void Operation()
    {
      Console.WriteLine("ConcreteComponent.Operation()");
    }
  }
abstract class Decorator : Component
  {
    protected Component component;

    public void SetComponent(Component component)
    {
      this.component = component;
    }

    public override void Operation()
    {
      if (component != null)
      {
        component.Operation();
      }
    }
  }

class ConcreteDecoratorA : Decorator
  {
    private string addedState;

    public override void Operation()
    {
      base.Operation();
      addedState = "New State";
      Console.WriteLine("ConcreteDecoratorA.Operation()");
    }
  }
class ConcreteDecoratorB : Decorator
  {
    public override void Operation()
    {
      base.Operation();
      AddedBehavior();
      Console.WriteLine("ConcreteDecoratorB.Operation()");
    }

    void AddedBehavior()
    {
    }
  }

class MainApp
  {
    static void Main()
    {
      // Create ConcreteComponent and two Decorators

      ConcreteComponent c = new ConcreteComponent();

      ConcreteDecoratorA d1 = new ConcreteDecoratorA();
      ConcreteDecoratorB d2 = new ConcreteDecoratorB();

      // Link decorators
      d1.SetComponent(c);
      d2.SetComponent(d1);

      d2.Operation();

      // Wait for user
      Console.Read();
    }
  }

Output –

ConcreteComponent.Operation()
ConcreteDecoratorA.Operation()
ConcreteDecoratorB.Operation()


1.6          Implementation2

Real life example where we extend the Print with Header and Footer.


public interface IReport
{
    void Print();
}

public class Report : IReport
{
    public void IReport.Print()
    {
        HttpContext.Current.Response.Write("This is the report body.");
    }
}

public class ReportHeaderDecorator : IReport
{
    private IReport _innerReport;
    public ReportHeaderDecorator(IReport innerReport)
    {
        //save a reference to the object being decorated
        _innerReport = innerReport;
    }

    public void IReport.Print()
    {
        //add header decoration first
        Response.Write("Report header");
        HttpContext.Current.Response.Write("<hr />");

        //now let the report being decorated do its printing
        _innerReport.Print();
    }
}

public class ReportFooterDecorator : IReport
{
    //save a reference to the object being decorated
    private IReport _innerReport;
    public ReportFooterDecorator(IReport innerReport)
    {
        _innerReport = innerReport;
    }
    public void IReport.Print()
    {

        //let the report being decorated do its printing first
        _innerReport.Print();

        //now we add the footer decoration
        HttpContext.Current.Response.Write("<hr />");
        Response.Write("<h6>Report footer.</h6>");

    }
}


class MainApp
  {
    static void Main()
    {
        //implementing the decorator
IReport myReport;

myReport = new Report();
myReport = new ReportHeaderDecorator(myReport);
myReport = new ReportFooterDecorator(myReport);

myReport.Print();
       // Wait for user
       Console.Read();
    }
  }

Output –

This is the report header.
This is the report body.
This is the report footer.

1.7          Decorator in MSDN

.NET uses the System.IO.Stream class to reading input, writing output, or both. Whether the data involves characters in a text file, TCP/IP network traffic, or something else entirely, chances are you will have access to it via a Stream. Since the class for working with file data (FileStream) and the class for working with network traffic (NetworkStream) both inherit from Stream, you can easily write code that processes the data independent of its origins.
Here's a method for printing out some bytes from a Stream to the console:
public static void PrintBytes(Stream s)
{
    int b;
    while((b = fs.ReadByte()) >= 0)
    {
        Console.Write(b + " ");
    }
}
Reading a single byte at a time is typically not the most efficient way to access a stream. Better is to read a chunk from the disk all at once and then consume the chunk from memory byte by byte.
The Framework includes the BufferedStream class for doing just that. The constructor for BufferedStream takes as the parameter whatever stream you would like buffered access to i.e either FileStream or NetworkStream. BufferedStream overrides the main methods of Stream, such as Read and Write, to provide the buffering functionality.

Similarly, you can use System.Security.Cryptography.CryptoStream to encrypt and decrypt Streams on the fly, without the rest of the application needing to know anything more than the fact that it is a Stream. 

Figure 3 Using Different Streams (Close Figure 3) 

MemoryStream ms = new MemoryStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8});
PrintBytes(ms);
 
BufferedStream buff = new BufferedStream(ms);
PrintBytes(buff);
buff.Close();
 
FileStream fs = new FileStream("../../decorator.txt", FileMode.Open);
PrintBytes(fs);
fs.Close();
 
This ability to dynamically attach new functionality to objects transparently using composition is an example of the Decorator pattern, as shown in Figure 4. Given any instance of Stream (either FileSteam or NetworkStream), you can add the capability for buffered access by wrapping it in a BufferedStream, or attach encryption by wrapping in a CrytoStream, without changing the interface to the data. Since you are just composing objects, this can be done at run time, rather than using a technique like inheritance, which is a compile-time decision. 




Note – Here BufferedStream & CryptoStream are decorators for FileStream/NetworkStream

FileStream fs = new FileStream("", FileMode.Append);
BufferedStream buff = new BufferedStream(fs); // Dcorating FileStream Object
buff.Close();

1.8          Implementation4: Real life example with third party components

Q: I'm using a method from a third-party library that expects me to pass a Stream to it. I want to be able to trace that library's activities with my Stream in order to better understand some behavior in my application. Is there a way to do this?

Ans: You have several options. The first and simplest option is to make use of the classic Decorator design pattern. A decorator is an object that has the same interface as another object it contains. In object-oriented terms, it is an object that has an "is-a" and a "has-a" relationship with a specific type. Consider the CryptoStream class in the System.Security.Cryptography namespace. CryptoStream derives from Stream (it "is-a" Stream), but it also accepts a Stream in its constructor and stores that Stream internally (it "has-a" stream); that underlying stream is where the encrypted data is stored. CryptoStream is a decorator.

You can implement your own Stream-derived decorator class that lets you intercept and monitor each and every call to the stream. An example implementation is shown in Figure 1. The InterceptStream class derives from Stream and overrides all of the methods to be intercepted and traced. It also has a constructor that accepts a Stream as a parameter and stores the specified stream to a private member variable. Each of the overridden methods delegates to the corresponding method on the stored stream, but first makes a call to a delegate with information about the invocation, including what method was called and what the parameters to that call were.




public delegate void InterceptStreamHandler(
    MethodBase method, params object[]arguments);

public class InterceptStream : Stream
{
    private Stream _stream;
    private InterceptStreamHandler _callback;

    public InterceptStream(Stream s, InterceptStreamHandler callback)
    {
        if (s == null) throw new ArgumentNullException("s");
        if (callback == null) throw new ArgumentNullException("callback");
        _stream = s;
        _callback = callback;
    }

    public override IAsyncResult BeginRead(byte[] buffer, int offset,
        int count, AsyncCallback callback, object state)
    {
        _callback(MethodBase.GetCurrentMethod(), buffer, offset,
            count, callback, state);
        return _stream.BeginRead (buffer, offset, count, callback, state);
    }

    public override IAsyncResult BeginWrite(byte[] buffer, int offset,
        int count, AsyncCallback callback, object state)
    {
        _callback(MethodBase.GetCurrentMethod(), buffer, offset,
            count, callback, state);
        return _stream.BeginWrite (buffer, offset, count,
            callback, state);
    }

    public override bool CanRead
    {
        get
        {
            _callback(MethodBase.GetCurrentMethod());
            return _stream.CanRead;
        }
    }

    public override string ToString()
    {
        _callback(MethodBase.GetCurrentMethod());
        return _stream.ToString();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _callback(MethodBase.GetCurrentMethod(), buffer, offset, count);
        _stream.Write(buffer, offset, count);
    }

    public override void WriteByte(byte value)
    {
        _callback(MethodBase.GetCurrentMethod(), value);
        _stream.WriteByte(value);
    }
}



This technique is useful for more than just debugging. For example, it is also useful for tracking progress in an application. Consider an application that uses a BinaryFormatter to deserialize a very large file. Passing an InterceptStream to the formatter instead of the original stream will let you keep track of how much of the target stream has been processed by the formatter, possibly using that information to update a progress bar.

Hope this helps.

Thanks & Regards,
Arun Manglick

No comments:

Post a Comment