Using multiple decorators to add functionality to an object?
I am trying to understand the decorator pattern and from the examples I understand how decorator objects can be used to extend existing functionality at runtime by overriding methods of the decorated object and how they add additional functionality via new method implementations.
Where I am getting a bit confused is where multiple开发者_开发技巧 decorators are used and how to access the extended functionality. Typically when looking at examples of the decorator pattern something like this is shown:
IBusinessObject businessObject = new MailDecorator(
new SmsDecorator(
new FaxDecorator(
new BusinessObject()
)
)
);
the idea being to take an object and dynamically add mail, sms and fax functionaliy. Now if you wanted to send a mail which is a method provided by the MailDecorator class you could do something like this:
If(businessObject is MailDecorator)
{
((MailDecorator) businessObject).SendMail();
}
However this would not work if you wanted to send an SMS or fax as in the normal implementation of the decorator pattern you will not have access to the object referenced by the decorator.
Is this a limitation of the decorator pattern or is it beyond the scope of what you can expect to achieve with this pattern or am I completely misunderstanding something here?
Would a different pattern be more appropriate?The standard definition
“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality”
implies that this should be achievable with this pattern but appears to break down when using multiple decorators on one object.
This is a common misconception of the decorator pattern. What you can do with the decorator pattern is to extend the functionality, not the API.
What does this mean?
It means, you can add new functionality, to the methods provided by the API, in your case IBusinessObject
. Lets say your interface has a method XmlDocument Export()
which is implemented in BusinessObject
and returns the data in the BusinessObject
instance in an XmlDocument
instance.
Now, you could create a LoggingDecorator
, which implements the Export
method like so:
public XmlDocument Export()
{
_logger.Log("Exporting...");
var result = _decoratedObject.Export();
_logger.Log("Done exporting...");
return result;
}
Or you can create a BusinessObjectSigningExportDecorator
which signs the returned XmlDocument
using the xml-dsig algorithm:
public XmlDocument Export()
{
var result = _decoratedObject.Export();
return SignXmlDocument(result);
}
You could then use them together like so:
IBusinessObject businessObject = new LoggingDecorator(
new BusinessObjectSigningExportDecorator(
new BusinessObject()
)
);
var xmlDocument = businessObject.Export();
The call to Export
will now write the log messages and sign the xml export.
But you still can use BusinessObject
without a decorator or with only one of the decorators.
The reason for using the decorator pattern is to be able to transparently add functionality. As you can see, the user of the businessObject
variable of type IBusinessObject
doesn't know and doesn't need to know the actual type that is used. It will work in the case with or without decorators.
Thinking further: When you have a factory that returns IBusinessObject
s you can extend the functionality, without changing the code that uses them and without changing the implementation of the class the implement IBusinessObject
. You just need to create another decorator and "attach" it inside the factory and therefore, you are making a change, that occurs only in a limited area of the code. So if this change breaks anything, you know exactly what code is responsible for the breakage.
Additionally, this enforces separation of concerns, because your normal business object implementation doesn't know and care about which signing algorithm should be used or that one needs to be used altogether.
精彩评论