430 likes | 570 Views
Type Laundering & Prototype Pattern. Kunal Chaudhary. Prototype Pattern. Dr W. W. Applicability. When the classes to instantiate are specified at run-time, for example, by dynamic loading; or To avoid building a class hierarchy of factories that parallels the class hierarchy of products; or
E N D
Type Laundering&Prototype Pattern Kunal Chaudhary
Prototype Pattern • Dr W. W.
Applicability • When the classes to instantiate are specified at run-time, for example, by dynamic loading; or • To avoid building a class hierarchy of factories that parallels the class hierarchy of products; or • It may be more convenient to install a corresponding number of prototypes and clone them rather than instantiating the class manually.
Participants • Prototype (Graphic) declares an interface for cloning itself. • ConcretePrototype(Staff, WholeNote, HalfNote) implements an operation for cloning itself. • Client (GraphicTool) creates a new object by asking a prototype to clone itself.
Consequences • Adding and removing products at run-time. • Specifying new objects by varying values. • Specifying new objects by varying structure. • Reduced subclassing. • Configuring an application with classes dynamically.
Implementation Issues • Using a prototype manager. • Implementing the Clone operation. • Initializing clones.
Dr W. W.’s Story • It is a painful road to achieve the success in Dr. W. W’s Diabolical plan to conquer the world. • But he did it using the Prototype Pattern.
Multiple Frankesteins from a Prototype • So, the cloning machine looks like this: class CloningMachine { function CloningMachine( ) { } public function buildClone( ): frankenstein { return new frankenstein( ); } public function buildManyClones( cloneNum: Number ) { var returnArray: Array = new Array( ); for( var k=0; k< clonesNum; k++ ) { returnArray[ k ] = new frankenstein( ); } return returnArray; } }
Code! • And the frankenstein will be like: class frankenstein { function frankenstein( ) { trace( "beeeee. I'm a new frankenstein" ); } }
Smart plan again (still lacks…..) • So, he decides to add the functionality needed to clone horses. How?. Look class CloningMachine { function CloningMachine( ) { } public function buildClone( ): frankenstein { return new frankenstein( ); } public function buildhorseClone( ): horse { return new horse( ); } public function buildManyClones( cloneNum: Number ) { var returnArray: Array = new Array( ); for( var k=0; k< clonesNum; k++ ) { returnArray[ k ] = new frankenstein( ); } return returnArray; } }
A new idea, but not enough • After thinking about the problem carefully, Professor W.W. gives it another try: class CloningMachine { function CloningMachine( ) { } public function buildClone( type: String ): Object { if( type == "frankenstein" ) { return new frankenstein( ); } else if ( type == "horse" ) { return new horse( ); } } public function buildManyClones( cloneNum: Number ) { var returnArray: Array = new Array( ); for( var k=0; k< clonesNum; k++ ) { returnArray[ k ] = new frankenstein( ); } return returnArray; } }
Look Below… • So, here’s a frankenstein: class frankenstein implements ICloneable { function frankenstein( ) { trace( "Hi, I will be cloned soon" ); } public function clone( ): ICloneable { trace( "Beeee, I'm a new frankenstein" ); return new frankenstein( ); } public function toString( ): String { return "{ Hi, I'm a frankenstein. }"; } }
Horse Implementation • And here’s a horse: class horse implements ICloneable { function horse( initFlag: Boolean ) { trace( "Muuuu, I'm a horse. I will also be cloned soon" ); if( initFlag == true ) { init( ); } } private function init( ) { trace( "I'm an initialized horse, whatever that means" ); } public function clone( ): ICloneable { return new horse( true ); } public function toString( ): String { return "{ Yes, I'm a cloned horse, muuuuuu }"; } }
New Cloning Machine • And Professor W.W. will want to do something like this: var cMachine: CloningMachine = new CloningMachine( ); cMachine.buildClone( new frankenstein( ) ); cMachine.buildClone( new horse( ) ); trace( "--" ); var frankensteinClones: Array = cMachine.buildManyClones( 5, new frankenstein( ) ); var horseClones: Array = cMachine.buildManyClones( 5, new horse( ) ); trace( "--" ); trace( "frankensteinClones " + frankensteinClones ); trace( "horseClones " + horseClones );
Cloning, continued… • So, finally, the cloning machine will be able to receive an animal ( an instance of a class ), and tell it to create as many copies of itself as needed: class CloningMachine { function CloningMachine( ) { } public function buildClone( template: ICloneable ): ICloneable { return template.clone( ); } public function buildManyClones( clonesNum: Number, template: ICloneable ): Array { var returnArray: Array = new Array( ); for( var k=0; k< clonesNum; k++ ) { returnArray[ k ] = template.clone( ); } return returnArray; } }
Bad News !!! • Dr. W. W. lost and still didn’t win over the world because apparently GOODNESS WINS OVER PROTOTYPE PATTERN!!!!!
Good uses of Prototype pattern • The first widely known application of the pattern in an object-oriented language was in ThingLab, where users could form a composite object and then promote it to a prototype by installing it in a library of reusable objects.
Related Patterns • Prototype and Abstract Factory are competing patterns in some ways.They can also be used together, however. An Abstract Factory might store a set of prototypes from which to clone and return products objects. • Designs that make heavy use of the Composite and Decorator patterns often can benefit from Prototype as well.
Type Laundering • If you are using the dynamic cast again and again because your framework does not let you make the extensions to its interfaces then my friend you have bugs in your design.
Good News! • You can turn those bugs into useful features. Thanks to type laundering!
Example • Take the vending machine. Its CoinInsertedEvent subclass adds a Cents getCoin() operation that returns the value of the coin a customer deposited. Another kind of event, CoinReleaseEvent, gets instantiated when the customer wants his or her money back. These operations and others would be implemented using rep. Clients of these events could of course use rep directly, assuming it's public. • But there's little reason to make it so: rep offers almost no abstraction, and it makes clients work pretty hard to get at the information they need. More likely, rep would be protected---of interest only to subclasses, which use it to implement more specialized interfaces.
Dealing with Loss • Since there is no universal interface for events, all that the framework knows about events is that they implement a basic interface because events are defined long after the framework is designed. • There are 2 questions that arise: How does the framework create instances of domain-specific subclasses? How does application code access subclass-specific operations when all it gets from the framework is objects of type Event?
1st Answer • Answer to the 1st question lies in defining one of the many creational patterns within the framework. Example: By defining factory method pattern in the framework, a new instance of domain-specific Event subclass can be obtained.
2nd Answer • Answer to the 2nd question lies in another question, which is, are there patterns for recovering type information from an instance? • Using visitor pattern lost type information can be recovered without resorting to dynamic casts to do the same.
Celebrate Adversity • The whole point is to use the loss of type information to our advantage and not mourn about it. • Lets leave event for now and lets move on the issues in the memento pattern.
What is Memento?(Small review) • Memento captures and externalizes an object’s state in order to restore the object to the same state at a later time. • Externalization must not violate encapsulation.
Cursor Example Here is an example: Structure s; Cursor c; for (s.first(c); s.more(c); s.next(c)) { Element e = s.element(c); // use Element e } The cursor has no client-accessible operations. The way to the pull this off is to give the object two different interfaces in C++.
A Need in Friend • Using the friend keyword allows access to a broad interface while limiting access to other classes. class Cursor { public: virtual ~Cursor(); private: friend class Structure; Cursor () { _current = 0; } ListElem* current () { return _current; } // gets _current void current (ListElem* e) { _current = e; } // sets _current private: ListElem* _current; };
Better Code • In this case, structure operations manipulate _current to keep track of the point in the traversal . class Structure { // ... virtual void first (Cursor& c) { c.current(_head); // _head is the head of the linked list, // which Structure keeps internally } virtual bool more (Cursor& c) { return c.current()->_next != 0; } virtual void next (Cursor& c) { c.current(c.current()->_next); // set current to next ListElem* } virtual Element& element (Cursor& c) { return c.current()->_element; } // ... };
Problems in Memento • The memento pattern furrows away only that much information in cursor memento which is necessary to mark the current stage of traversal. • A shortcoming is that substructure code cannot access cursor’s covert interface. • It cannot implement other cursor dependent functionality because it cannot override the cursor-handling operations inherited from structure.
Alternate way • One work around is to define protected operations in structure. class Structure { // ... protected: ListElem* current (Cursor& c) { return c.current(); } void current (Cursor& c, ListElem* e) { c.current(e); } // ... }; It extends structures privileges to its subclasses.
Event’s loss is Cursor’s Gain • The way we transform a design bug into a feature is known as type laundering and this is how it is done: The idea is to define an abstract base class for Cursor that includes only those aspects of its interface that should be public. class Cursor { public: virtual ~Cursor () { } protected: Cursor () { } };
progress in the Cursor Example • Just so that the cursor acts as an abstract class, the constructor is protected to preclude instantiation. class ListCursor : public Cursor { public: ListCursor () { _current = 0; } ListElem* current () { return _current; } void current (ListElem* e) { _current = e; } private: ListElem* _current; };
Better Arrangement • This arrangement means that Structure operations that take a Cursor as an argument must downcast it to a ListCursor before they can access the extended interface: class Structure { // ... virtual void first (Cursor& c) { ListCursor* lc; if (lc = dynamic_cast<ListCursor*>(&c)) { lc->current(_head); // _head is the head of the linked list, // which Structure keeps internally } } // ... };
Instantiation • The final task to this design is to determine how cursors get instantiated. • We use a variation on Factory Method to abstract the instantiation process. class Structure { public: // ... virtual Cursor* cursor () { return new ListCursor; } // ... };
Another problem • But, since something of type Cursor* is returned by cursor( ), clients can access subclass-specific operation only when they start dynamic casting in order to find out the type. • Meanwhile, structure subclasses are free to redefine cursor-manipulating operations.
Memento Comparison • The following diagram illustrates type-laundering based implementation and how it differs from implementation based in memento pattern.
Main differences • The most important difference is the use of ConcreteMemento subclass that adds the privileged interface to the bare-bones Memento interface. • Type laundering absolves a C++ implementation from using friend and having to work around its shortcomings.
Conclusion • Basically, Type laundering cleans up your design and makes it efficient with a lot less work.