Skip to main content

The Prototype Pattern In TypeScript

The prototype pattern is a creational design pattern that allows cloning objects, even complex ones, without coupling to their specific classes. All prototype classes have a common interface that makes it possible to copy objects even if their concrete classes are unknown. Prototype objects can produce full copies since objects of the same class can access each other's private fields.

The Prototype pattern is especially useful when you need to create a copy of an existing object while ensuring that the copies of different classes are handled correctly, given TypeScript has type checking.

Classic Implamentation Of The Prototype Pattern

Let's imagine we have a User class, and we want to clone a User object:

interface UserDetails {
name: string;
age: number;
email: string;
}

interface Prototype {
clone(): Prototype;

getUserDetails(): UserDetails;
}

/**
* Concrete Prototype
*/
class ConcretePrototype implements Prototype {
private user: UserDetails;

constructor(user: UserDetails) {
this.user = user;
}

public clone(): ConcretePrototype {
// Deep copy is necessary if the object fields have reference types
const clone = Object.create(this);
clone.user = { ...this.user };
return clone;
}

public getUserDetails(): UserDetails {
return this.user;
}
}

/**
* The client code.
*/
function clientCode() {
const p1 = new ConcretePrototype({
name: "John",
age: 30,
email: "john@example.com",
});
const p2 = p1.clone();

if (p1.getUserDetails() === p2.getUserDetails()) {
console.log("Cloned objects are the same instance.");
} else {
console.log("Cloned objects are not the same instance.");
}
}

clientCode();

In this code, UserDetails is an interface that represents a user object. In the ConcretePrototype class, we're creating a deep copy of this object in the clone method. Instead of toString(), we now have getUserDetails(), which returns the user object.

In the client code, we create a ConcretePrototype with a user object, clone it, and compare the getUserDetails of the two instances. If the clone operation works correctly, the two objects should not be the same instance even though they have the same data.

Please remember that we are performing a shallow copy of the object. If your object contains nested objects or arrays, you might need to perform a deep copy. In the clone method, that might involve iterating through each property of the object to ensure that you create a new instance of it.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

When To Use The Prototype Pattern

Prototype pattern is typically used when creating a new object instance is more costly than copying an existing one, or when the system needs to be independent of how its products are created, composed, and represented.

Here are some indicators that might suggest the Prototype pattern could be appropriate:

  1. Complex Object Creation: If you have a system where object creation is complex (due to complex initialization, large numbers of attributes, or other factors) and you find that many similar objects need to be created, the Prototype pattern could be useful. Instead of repeating the complex construction process for each new object, you could clone a prototype.

  2. High Cost of Object Creation: If you have a situation where creating each object from scratch is expensive in terms of memory or CPU (for example, if creating an object involves a database query), using the Prototype pattern allows you to clone a pre-loaded object, which can be more efficient.

  3. Similar Object Instances: If you need multiple objects that are similar ( but not identical) to an existing instance, you might consider the Prototype pattern. After cloning the object, you can modify the clone to achieve the necessary differences.

  4. Dynamic Typing or Run-time Configuration: If the exact type or state of objects your system needs can only be determined at runtime, the Prototype pattern could be useful. Instead of hard-coding your system to create specific types of objects, it can work with any object that follows the prototype interface.

  5. Preserving Historical States: If you are creating an application where you need to save the state of an object and be able to go back to it later ( for example, for an undo feature in a text editor or for a save/load game feature), the Prototype pattern can help. By saving each state as a prototype, you can clone the prototype to restore a previous state.

  6. Large Object Graphs: If your application works with large object graphs ( for example, complex data structures), and if a user's action might result in a small change to the graph, it can be more efficient to clone the entire graph and modify the clone rather than recreating the graph for each action.

As with any design pattern, it's important to use the Prototype pattern judiciously and to ensure it's a good fit for the problem at hand. Overuse of any pattern can lead to unnecessarily complex and hard-to-understand code.

Conclusion

In conclusion, the Prototype Pattern is an efficient way to create new objects by copying existing ones, particularly when the creation process is costly in terms of time or computational resources. The pattern ensures that the clones are independent of their originals, thus preventing changes to the clones from affecting the originals. This TypeScript implementation demonstrates the application of the Prototype Pattern in a practical context, illustrating the pattern's benefits in enhancing code maintainability and performance.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle