Decoration mode (Decorator pattern)
——. NET Design Pattern series Ten
TERRYLEE,2006 year March
Overview
In software systems, sometimes we use inheritance to extend the functionality of an object, but because of the static traits introduced into the type, this extension lacks flexibility, and as the number of subclasses increases (the expansion function increases), the combination of each seed class (the combination of extended functions) causes more subclasses to swell. How do you enable the "Expansion of object functionality" to be implemented dynamically, as needed? At the same time, avoid the problem of sub-class expansion caused by the increase of expansion function. So that the impact of any "functional expansion change" will be minimized? This is the decorator pattern to be talked about in this paper.
Intention
Add some extra responsibilities to an object dynamically. For added functionality, the decorator mode is more flexible than generating subclasses. [GOF "design mode"]
Structure diagram
Figure 1 Decorator Pattern structure diagram
Examples of life
The adornment mode dynamically adds additional responsibilities to an object. No matter whether a picture or a picture frame can be hung on the wall, but usually have a picture frame, and is actually a picture frame is hanging on the wall. Before hanging on the wall, the painting can be covered with glass and put into a frame, when the painting, glass and picture frames form an object.
Figure 2 Decorative Pattern object diagram using a picture frame as an example
Decoration Mode Commentary
In software development, it is often a question of adding functionality dynamically to an object rather than to the whole class, or in the case of my favorite logging example (perhaps not particularly appropriate in decorator mode). Now we need to develop a log of the components, in addition to supporting database records DatabaseLog and text file records Textfilelog Two ways, we also have to add some additional features in different application environments, such as the need to log information error severity level, The priority level of logging information needs to be logged, as well as features such as extended properties for log information. Here, if we do not consider the design pattern, the method of solving the problem is very simple, can be implemented through the inheritance mechanism, the log class structure diagram is as follows:
Figure 3
The implementation code is as follows:
Public abstract class Log
{
public abstract void Write (string log);
}
public class Databaselog:log
{
public override void Write (string log)
{
//...... Log in to the database
}
}
public class Textfilelog:log
{
public override void Write (string log)
{
//...... Log to a text file
}
}
The ability to log information for error severity and logging information priority is required, as long as subclass DatabaseLog and Textfilelog are used to generate subclasses, and two new interfaces Ierror and I priorities are introduced, and the class structure diagram is as follows:
Figure 4
The implementation code is as follows:
public interface Ierror
{
void SetError ();
}
public interface Ipriority
{
void SetPriority ();
}
public class Dberrorlog:databaselog, Ierror
{
public override void Write (string log)
{
Base. Write (log);
}
public void SetError ()
{
//...... Feature extensions, which enable logging of error severity levels
}
}
public class Dbprioritylog:databaselog, ipriority
{
public override void Write (string log)
{
Base. Write (log);
}
public void SetPriority ()
{
//...... Feature extensions for record priority levels
}
}
public class Tferrorlog:textfilelog, Ierror
{
public override void Write (string log)
{
Base. Write (log);
}
public void SetError ()
{
//...... Feature extensions, which enable logging of error severity levels
}
}
public class Tfprioritylog:textfilelog, ipriority
{
public override void Write (string log)
{
Base. Write (log);
}
public void SetPriority ()
{
//...... Feature extensions for record priority levels
}
}
At this point, you can see that if you need the appropriate functionality, you can use these subclasses directly. Here we adopt the inheritance of the class to solve the problem of the extension of the object function, this way can achieve our intended purpose. However, it has brought a series of problems. First of all, the previous analysis is just a function extension, if you need to record the error severity level, and need to record the priority, subclasses need to do multiple inheritance of the interface, which in some cases violates the class's single responsibility principle, notice the blue area in:
Figure 5
Implementation code:
public class Dbeplog:databaselog, Ierror, ipriority
{
public override void Write (string log)
{
SetError ();
SetPriority ();
Base. Write (log);
}
public void SetError ()
{
//...... Feature extensions, which enable logging of error severity levels
}
public void SetPriority ()
{
//...... Feature extensions for record priority levels
}
}
public class Tfeplog:databaselog, Ierror, ipriority
{
public override void Write (string log)
{
SetError ();
SetPriority ();
Base. Write (log);
}
public void SetError ()
{
//...... Feature extensions, which enable logging of error severity levels
}
public void SetPriority ()
{
//...... Feature extensions for record priority levels
}
}
Second, with the expansion of the function later, the subclass will rapidly expand, you can see that the appearance of the subclass is actually DatabaseLog and Textfilelog two sub-class and the new interface of an arrangement of a combination of relations, so the class structure will become very complex and difficult to maintain, as the Li Jianzhong teacher said "Subclass complex subclass, how many subclasses"; Finally, the extension of this way is a kind of static extension, and does not really implement the dynamic addition of the extension function, the client program can not choose how and when to add extensions.
Now it's time for the decorator mode to appear, and the solution is to embed the log object in another object, which expands the functionality. First we need to define an abstract wrapper class Logwrapper, let it inherit from the log class, the structure diagram is as follows:
Figure 6
The implementation code is as follows:
Public abstract class Logwrapper:log
{
Private Log _log;
Public logwrapper (log log)
{
_log = log;
}
public override void Write (string log)
{
_log. Write (log);
}
}
Now for each extended feature, add a wrapper class subclass to allow them to implement specific extension functions, such as the medium Green Zone:
Figure 7
The implementation code is as follows:
public class Logerrorwrapper:logwrapper
{
Public Logerrorwrapper (Log _log)
: Base (_log)
{
}
public override void Write (string log)
{
SetError (); //...... Feature Extensions
Base. Write (log);
}
public void SetError ()
{
//...... Logging error severity levels implemented
}
}
public class Logprioritywrapper:logwrapper
{
Public Logprioritywrapper (Log _log)
: Base (_log)
{
}
public override void Write (string log)
{
SetPriority (); //...... Feature Extensions
Base. Write (log);
}
public void SetPriority ()
{
//...... Record prioritization achieved
}
}
Here, the Logerrorwrapper class and the Logprioritywrapper class really implement extensions to the functionality of the error severity and priority levels. Let's take a look at how the client program calls it:
public class Program
{
public static void Main (string[] args)
{
Log log = new DatabaseLog ();
Logwrapper lew1 = new Logerrorwrapper (log);
Extended Logging error severity level
Lew1. Write ("Log Message");
Logprioritywrapper lpw1 = new Logprioritywrapper (log);
Extended record priority levels
Lpw1. Write ("Log Message");
Logwrapper lew2 = new Logerrorwrapper (log);
Logprioritywrapper lpw2 = new Logprioritywrapper (LEW2); This is lew2.
Also extends the error severity and priority levels
Lpw2. Write ("Log Message");
}
}
Notice in the above program in the third paragraph of the decoration to truly reflect the decorator model of the subtlety, here a total of two times: the first time the log object to the wrong severity of the decoration, became the Lew2 object, the second time to decorate the Lew2 object, then became the Lpw2 object, The Lpw2 object at this time extends the functionality of the error severity and priority levels. That means we can continue packing as we need them. Some people here might say that the constructor of the Logprioritywrapper class receives a log object, why is it possible to pass in the Logerrorwrapper object here? The class structure diagram shows that the Logerrorwrapper class is actually a subclass of the log class.
Let's analyze what benefits this will bring. First of all, the expansion function has realized a real dynamic increase, only when the need for a certain function of the packaging, and secondly, if there is a new extension function, only need to add a corresponding wrapper subclass (Note: This is not possible at any time), without having to inherit many subclasses, There is no expansion of the subclass, and the decorator pattern also fits nicely with the "precedence object combination rather than inheritance" and "open-closed" principle in object-oriented design principles.
. NET decoration mode in the
1.. NET in decorator mode A typical use is about stream, which has the following class structure:
Figure 8
As you can see, BufferedStream and CryptoStream are actually two wrapper classes, where the Decorator pattern omits the abstract decorative character (Decorator), the sample code is as follows:
Class Program
{
public static void Main (string[] args)
{
MemoryStream ms =
New MemoryStream (new byte[] {100,456,864,222,567});
Extended buffering Capabilities
BufferedStream buff = new BufferedStream (MS);
Extended buffering, encrypted functionality
CryptoStream crypto = new CryptoStream (buff);
}
}
With anti-compilation, you can see the code for the BufferedStream class (listed only), which is inherited from the Stream class:
public sealed class Bufferedstream:stream
{
Methods
Private BufferedStream ();
Public BufferedStream (Stream stream);
Public BufferedStream (Stream stream, int buffersize);
Fields
private int _buffersize;
Private Stream _s;
}
2. There is a Dbcommandwrapper wrapper class in the Enterprise Library Daab, which implements the packaging of the IDbCommand class and provides the function of parameter processing. The structure diagram is as follows:
Figure 9
The schematic code is as follows:
Public abstract class Dbcommandwrapper:marshalbyrefobject, IDisposable
{
}
public class Sqlcommandwrapper:dbcommandwrapper
{
}
public class Oraclecommandwrapper:dbcommandwrapper
{
}
Effect and key points of realization
1. The component class acts as an abstract interface in decorator mode and should not implement specific behavior. And the decorator class should be transparent to the component class, in other words the component class does not need to know the decorator class, and the decorator class extends the functionality of the component class from the outside.
2. The Decorator class behaves as an inheritance of the Is-a component on the interface, that is, the decorator class inherits the interfaces that the component class has. But in the implementation of the HAS-A component of the composition of the relationship, that is, decorator class and use another component class. We can "decorate" a component object with one or more decorator objects, and the decorated object is still a Component object.
3. Decortor mode is not to solve the problem of "multiple inheritance of multiple subclasses", the application of decorator mode is to solve "the extension function of principal class in multiple directions"--meaning of "adornment".
4. The use of the decorator model in practice can be very flexible. If there is only one concretecomponent class and no abstract component class, then the decorator class can be a subclass of Concretecomponent.
Figure 10
If there is only one Concretedecorator class, then there is no need to create a separate decorator class, but you can combine the responsibilities of decorator and Concretedecorator into a single class.
Figure 11
5. The advantage of the decorator model is that it provides a more flexible extension than inheritance, and can create a combination of many different behaviors by using different decorative classes and arranging combinations of these decorations.
6. Because of the use of adornment mode, a smaller number of destination classes are required than using inheritance relationships. The use of fewer classes, of course, makes the design easier to carry out. However, on the other hand, using the adornment pattern produces more objects than using the inheritance relationship. More objects make it difficult to find errors, especially when they look alike.
Applicability
The decorating mode should be used in the following cases:
1. You need to extend the functionality of a class or add additional responsibilities to a class.
2. You need to dynamically add functionality to an object that can be revoked dynamically.
3. It is necessary to increase the number of functions resulting from the permutations of some basic functions, thus making the inheritance relationship impractical.
Summarize
The decorator mode adopts object combination rather than inheritance, realizes the ability of dynamically expanding object function at run time, and can extend multiple functions according to need, avoids the "flexibility difference" and "multi-subclass derivation problem" which is caused by using inheritance alone. At the same time, it conforms well to the principle of object-oriented design, which is "preferential use of object combination rather than inheritance" and "open-closed".
Resources
Shanhong, Java and patterns, electronic industry publishing house
James W. Cooper, C # Design model, electronic industry Press
Alan Shalloway James R. Trott, "Design Patterns explained", China Power Press
MSDN Webcast "C # Object-oriented design mode discussion on (decorator) decorative mode (structured mode)"
Net design mode the second part of the structural pattern (9): Decorative mode (Decorator pattern)