But the Decorator pattern suggests giving the client the ability to specify whatever combination of "features" is desired.
Widget* aWidget = new BorderDecorator( new HorizontalScrollBarDecorator( new VerticalScrollBarDecorator( new Window( 80, 24 )))); aWidget->draw();This flexibility can be achieved with the following design
Another example of cascading (or chaining) features together to produce a custom object might look like ...
Stream* aStream = new CompressingStream( new ASCII7Stream( new FileStream( "fileName.dat" ))); aStream->putString( "Hello world" );The solution to this class of problems involves encapsulating the original object inside an abstract wrapper interface. Both the decorator objects and the core object inherit from this abstract interface. The interface uses recursive composition to allow an unlimited number of decorator "layers" to be added to each core object.
Note that this pattern allows responsibilities to be added to an object, not methods to an object's interface. The interface presented to the client must remain constant as successive layers are specified.
Also note that the core object's identity has now been "hidden" inside of a decorator object. Trying to access the core object directly is now a problem.
CoreFunctionality.doThis()
.
The client may, or may not, be interested in OptionalOne.doThis()
and OptionalTwo.doThis()
. Each of these classes always
delegate to the Decorator base class, and that class always delegates
to the contained "wrappee" object.
Although paintings can be hung on a wall with or without frames, frames are often added, and it is the frame which is actually hung on the wall. Prior to hanging, the paintings may be matted and framed, with the painting, matting, and frame forming a single visual component.
Before | After | |
---|---|---|
// Inheritance run amok class A { public: virtual void do_it() { cout << 'A'; } }; class AwithX : public A { public: /*virtual*/ void do_it() { A::do_it(); do_X(); }; private: void do_X() { cout << 'X'; } }; class AwithY : public A { public: /*virtual*/ void do_it() { A::do_it(); do_Y(); } protected: void do_Y() { cout << 'Y'; } }; class AwithZ : public A { public: /*virtual*/ void do_it() { A::do_it(); do_Z(); } protected: void do_Z() { cout << 'Z'; } }; class AwithXY : public AwithX, public AwithY { public: /*virtual*/ void do_it() { AwithX::do_it(); AwithY::do_Y(); } }; class AwithXYZ : public AwithX, public AwithY, public AwithZ { public: /*virtual*/ void do_it() { AwithX::do_it(); AwithY::do_Y(); AwithZ::do_Z(); } }; int main( void ) { AwithX anX; AwithXY anXY; AwithXYZ anXYZ; anX.do_it(); cout << '\n'; anXY.do_it(); cout << '\n'; anXYZ.do_it(); cout << '\n'; } // AX // AXY // AXYZ | // Replacing inheritance with wrapping-delegation // // Discussion. Use aggregation instead of inheritance to // implement embellishments to a "core" object. Client // can dynamically compose permutations, instead of the // architect statically wielding multiple inheritance. class I { public: virtual ~I() { } virtual void do_it() = 0; }; class A : public I { public: ~A() { cout << "A dtor" << '\n'; } /*virtual*/ void do_it() { cout << 'A'; } }; class D : public I { public: D( I* inner ) { m_wrappee = inner; } ~D() { delete m_wrappee; } /*virtual*/ void do_it() { m_wrappee->do_it(); } private: I* m_wrappee; }; class X : public D { public: X( I* core ) : D( core ) { } ~X() { cout << "X dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'X'; } }; class Y : public D { public: Y( I* core ) : D( core ) { } ~Y() { cout << "Y dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'Y'; } }; class Z : public D { public: Z( I* core ) : D( core ) { } ~Z() { cout << "Z dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'Z'; } }; int main( void ) { I* anX = new X( new A ); I* anXY = new Y( new X( new A ) ); I* anXYZ = new Z( new Y( new X( new A ) ) ); anX->do_it(); cout << '\n'; anXY->do_it(); cout << '\n'; anXYZ->do_it(); cout << '\n'; delete anX; delete anXY; delete anXYZ; } // AX // AXY // AXYZ // X dtor A dtor // Y dtor X dtor A dtor // Z dtor Y dtor X dtor A dtor |
Adapter changes an object's interface, Decorator enhances an object's responsibilities. Decorator is thus more transparent to the client. As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters. [GoF, p149]
Composite and Decorator have similar structure diagrams, reflecting the fact that both rely on recursive composition to organize an open-ended number of objects. [GoF, p219]
A Decorator can be viewed as a degenerate Composite with only one component. However, a Decorator adds additional responsibilities - it isn't intended for object aggregation. [GoF, p184]
Decorator is designed to let you add responsibilities to objects without subclassing. Composite's focus is not on embellishment but on representation. These intents are distinct but complementary. Consequently, Composite and Decorator are often used in concert. [GoF, p220]
Composite could use Chain of Responsibility to let components access global properties through their parent. It could also use Decorator to override these properties on parts of the composition. [GoF, p349]
Decorator and Proxy have different purposes but similar structures. Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests. [GoF, p220]
Decorator lets you change the skin of an object. Strategy lets you change the guts. [GoF, p184]