Pattern - Prototype
Higor Diego
Posted on January 14, 2023
The Prototype pattern was created by Gamma et al. in his book "Design Patterns: Elements of Reusable Object-Oriented Software", published in 1995. This Prototype pattern is a creational design pattern in software development.
It is used to create new objects by copying existing objects, instead of creating new objects from scratch. The purpose of this pattern is to reduce the complexity of object creation and allow new objects to be created without specifying their classes. It is a good practice for dealing with complex object creation issues, and can be used to facilitate the implementation of shallow and deep object copying mechanisms.
The pattern is implemented by creating a "Prototype" interface that defines a "clone" method for creating copies of an object. Concrete classes implement this method to create copies of themselves. This pattern is useful when creating new objects might be costly or impractical, such as when objects need to be created at runtime or when objects are instantiated dynamically.
The Prototype pattern has several advantages, including:
- Creating objects without specifying their classes: The pattern allows new objects to be created without specifying their classes, which means that classes only need to be defined once and then can be used to create new objects. This helps keep the code clean and maintainable.
- Reduced complexity of object creation: The pattern helps to reduce the complexity of object creation, allowing objects to be created quickly and easily.
- Possibility of creating objects at runtime: The pattern allows objects to be created at runtime, which means that new objects can be created dynamically.
- Support for shallow and deep copies: The pattern supports creating shallow and deep copies of objects. Shallow copy creates a copy of the object but shares the same underlying data. Deep copy creates a complete copy of the object and underlying data.
- Code reuse: The pattern allows already written and tested code to be reused to create new objects, which helps to save time and effort.
- Easy to change implementation: as the objects are created from existing prototypes, it's easy to change the implementation without having to refactor much of the code
- Flexibility: the use of the prototype pattern allows us to change the behavior or state of a class without having to modify the source code
- Loose coupling: The pattern has a loose coupling between classes, which means that classes are independent of each other and can be easily modified or replaced without affecting other classes.
Below is a simple code example using the Prototype pattern.
// Interface Prototype
class Prototype {
clone() {}
}
// Concrete Prototype
class ConcretePrototypeA extends Prototype {
constructor(name) {
super();
this.name = name;
}
clone() {
return new ConcretePrototypeA(this.name);
}
}
// Usage
let prototypeA = new ConcretePrototypeA("Prototype A");
let copyOfPrototypeA = prototypeA.clone();
console.log(copyOfPrototypeA.name); // Output: "Prototype A"
In this example, the Prototype class is an interface that defines the clone method. The ConcretePrototypeA class is a concrete implementation of the Prototype class and implements the clone method to create a copy of the object.
On usage, a ConcretePrototypeA object is created and stored in the prototypeA variable, and soon after it is cloned to another object and stored in the copyOfPrototypeA variable.
As mentioned earlier, this way of implementing it can be a way of making deep copies, but it is important to remember that this pattern has several ways of being implemented, and some libraries already have implementations of object clones, for example lodash.cloneDeep() .
Simple, right?
Imagine another scenario in which you need to perform a search and insert data via a form through an entry point that is an API.
Api listed was:
Follow the solution below:
// Interface Prototype
class Request {
constructor(url) {
this.url = url;
}
clone() {}
makeRequest() {}
}
// Concrete Prototype
class GetRequest extends Request {
constructor(url) {
super(url);
}
clone() {
return new GetRequest(this.url);
}
makeRequest() {
return fetch(this.url).then((response) => response.json())
}
}
class PostRequest extends Request {
constructor(url, body) {
super(url);
this.body = body;
}
clone() {
return new PostRequest(this.url, this.body);
}
makeRequest() {
return fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(this.body)
}).then((response) => response.json())
}
}
// Usage
let getRequest = new GetRequest("https://reqres.in/api/users");
let postRequest = new PostRequest("https://reqres.in/api/users", { "name": "morpheus", "job": "leader" });
let requests = [getRequest, postRequest, getRequest.clone(), postRequest.clone()];
requests.forEach(request => {
request.makeRequest();
});
The Request class is a prototype interface, which defines the constructor and the clone() and makeRequest() methods. The GetRequest and PostRequest classes are concrete prototypes, which extend the Request class and implement their own clone() and makeRequest() methods.
In the GetRequest class, the clone() method returns a new instance of GetRequest, with the same url as the current instance. The makeRequest() method uses the fetch() function to perform a GET request to the specified url and returns the response in JSON format.
In the PostRequest class, the clone() method returns a new instance of PostRequest, with the same url and body as the current instance. The makeRequest() method uses the fetch() function to perform a POST request to the specified url, with the body specified in the constructor, and returns the response in JSON format.
At the end of the code, an instance of GetRequest and another of PostRequest is created. Next, a requests array is created and the original instances and their copies are added. Finally, the makeRequest() method is called for all instances in the requests array.
The Prototype pattern is useful when you need to create new objects from existing objects, without having to specify their class or type. Some situations where the Prototype pattern can be applied include:
- When creating new objects is costly or time-consuming, and it is more efficient to create a copy of an existing object.
- When objects need to be modified dynamically, but still need to maintain their basic structure.
- When you need to create multiple instances of an object with minor variations, such as changing just a few attributes.
- When you need to avoid using multiple constructors or static methods to create different types of objects.
In general, the Prototype pattern is a good choice when you need to create objects flexibly and efficiently without having to worry about the details of their implementation.
Conclusion
The Prototype design pattern is a creation pattern that allows you to create new objects from existing objects without having to specify their class or type. This is done by implementing a cloning method that creates a copy of the original object. It is useful when creating new objects is costly or time-consuming, when objects need to be dynamically modified but still need to maintain their basic structure, when you need to create multiple instances of an object with small variations, or when you need to avoid using multiple constructors or static methods to create different types of objects.
Hope this helps, until next time.
Posted on January 14, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.