Design Patterns : #5 Prototype Pattern
Manojkumar Gupta
Engineering Manager | E-commerce | Recommendation & Observability System | Ex-Flipkart
How it is different from Factory Patterns?...
#1 Factory is used to introduce loose coupling between objects as the factory will take care of all the instantiation logic hiding it from the clients. Factory method is used to delegate the responsibility of choosing which implementation or subclass you want to use like Car interface can be implemented by SportsCar and EconomicalCar and based upon budget factory will return appropriate object.
Prototype on the other hand is used when the cost of creating an object is large and it is ok to copy an existing instance than creating a new instance. It could be in terms of memory or computation for example, you have complex objects like Trade so, you can create a basic default object and on runtime can just clone it and do some changes as per requirement.
#2 Factory Method returns the new Instance of type what we are interested in where as in prototype The related subclass return the instance of itself with clone method. Being more Specific In Factory Method creation is carried away through inheritance where as in Prototype creation through delegation i.e Polymorphism.
Prototype is best thought of as a way to optimize Factory (copy instead of create) or to perform dependency injection (configure factory for a specific implementation/ configuration).
What Prototype Pattern gives us...
- Adding and removing products at run-time: Prototypes let you incorporate a new concrete product class into a system simply by registering a prototypical instance with the client. That's a bit more flexible than other creational patterns, because a client can install and remove prototypes at run-time.
- Specifying new objects by varying values: Highly dynamic systems let you define new behavior through object composition by specifying values for an object's variables. You effectively define new kinds of objects by instantiating existing classes and registering the instances as prototypes of client objects. A client can exhibit new behavior by delegating responsibility to the prototype. This kind of design lets users define new "classes" without programming. In fact, cloning a prototype is similar to instantiating a class. The Prototype pattern can greatly reduce the number of classes a system needs.
- Specifying new objects by varying structure: Many applications build objects from parts and subparts. Editors for circuit design, for example, build circuits out of subcircuits. For convenience, such applications often let you instantiate complex, user-defined structures, say, to use a specific subcircuit again and again. The Prototype pattern supports this as well. We simply add this subcircuit as a prototype to the palette of available circuit elements. As long as the composite circuit object implements Clone as a deep copy, circuits with different structures can be prototypes.
Now lets see some code...
/* Prototype base class. */ class Prototype { protected: std::string type; int value; public: virtual Prototype* clone() = 0; std::string getType() { return type; } int getValue() { return value; } }; class ConcretePrototype1 : public Prototype { public: ConcretePrototype1(int number) { type = "Type1"; value = number; } Prototype* clone() { return new ConcretePrototype1(*this); } }; class ConcretePrototype2 : public Prototype { public: ConcretePrototype2(int number) { type = "Type2"; value = number; } Prototype* clone() { return new ConcretePrototype2(*this); } }; /* Factory that manages prorotype instances and produces their clones. */ class ObjectFactory { static Prototype* type1value1; static Prototype* type1value2; static Prototype* type2value1; static Prototype* type2value2; public: static void initialize() { type1value1 = new ConcretePrototype1(1); type1value2 = new ConcretePrototype1(2); //Set diff value type2value1 = new ConcretePrototype2(1); type2value2 = new ConcretePrototype2(2);//Set diff value with same obj } static Prototype* getType1Value1() { return type1value1->clone(); } static Prototype* getType1Value2() { return type1value2->clone(); } static Prototype* getType2Value1() { return type2value1->clone(); } static Prototype* getType2Value2() { return type2value2->clone(); } }; Prototype* ObjectFactory::type1value1 = 0; Prototype* ObjectFactory::type1value2 = 0; Prototype* ObjectFactory::type2value1 = 0; Prototype* ObjectFactory::type2value2 = 0; int main() { ObjectFactory::initialize(); // Called one one which is costly affair Prototype* object; /* All the object were created by cloning the prototypes. */ object = ObjectFactory::getType1Value1(); std::cout << object->getType() << ": " << object->getValue() << std::endl; object = ObjectFactory::getType1Value2(); std::cout << object->getType() << ": " << object->getValue() << std::endl; object = ObjectFactory::getType2Value1(); std::cout << object->getType() << ": " << object->getValue() << std::endl; object = ObjectFactory::getType2Value2(); std::cout << object->getType() << ": " << object->getValue() << std::endl; return 0; }
Some more code example...
public class ShoppingCart implements Cloneable{ private List<String> myCart; public ShoppingCart(){ myCart = new ArrayList<String>(); } public ShoppingCart(List<String> list){ this.myCart=list; } public void loadData(){ //read all ShoppingCart from database and put into the list myCart.add("Item1"); myCart.add("Item2"); myCart.add("Item3"); myCart.add("Item4"); } public List<String> getmyCart() { return myCart; } @Override public Object clone() throws CloneNotSupportedException{ List<String> temp = new ArrayList<String>(); for(String s : this.getmyCart()){ temp.add(s); } return new ShoppingCart(temp); } } class PrototypePattern { public static void main(String[] args) throws CloneNotSupportedException { ShoppingCart CartItem = new ShoppingCart(); CartItem.loadData(); //Use the clone method to get the ShoppingCart object ShoppingCart CartItemNew = (ShoppingCart) CartItem.clone(); ShoppingCart CartItemNew1 = (ShoppingCart) CartItem.clone(); // Add new item in preloaded list of items List<String> list = CartItemNew.getmyCart(); list.add("Item5"); // remove item in preloaded list of items List<String> list1 = CartItemNew1.getmyCart(); list1.remove("Item4"); System.out.println("CartItem List: "+CartItem.getmyCart()); System.out.println("CartItemNew List: "+list); System.out.println("CartItemNew1 List: "+list1); } }
Thumb Rule
- Factory Method: creation through inheritance. Prototype: creation through delegation.
- Prototype doesn't require subclassing, but it does require an "initialize" operation. Factory Method requires subclassing, but doesn't require Initialize.
- Designs that make heavy use of the Composite and Decorator patterns often can benefit from Prototype as well.
- Prototype co-opts one instance of a class for use as a breeder of all future instances.
- Prototypes are useful when object initialization is expensive, and you anticipate few variations on the initialization parameters. In this context, Prototype can avoid expensive "creation from scratch", and support cheap cloning of a pre-initialized prototype.
- Prototype is unique among the other creational patterns in that it doesn't require a class – only an object. Object-oriented languages like Self and Omega that do away with classes completely rely on prototypes for creating new objects.
Hope it helps...