150
creational object-creation

Prototype

Reference Wikipedia ↗
Prototype — class diagram
Plate 150 class diagram

The Prototype pattern is a creational design pattern that empowers you to create new objects by cloning existing ones, offering a flexible alternative to traditional instantiation methods. Instead of specifying new object creation through classes, it delegates the creation responsibility to a prototype instance. This proves particularly useful when the creation process is expensive or complex, or when the exact type of objects to be created isn’t known in advance.

This pattern avoids the limitations of class hierarchies for object creation, enabling the creation of a hierarchical structure of objects by using existing instances as prototypes. It relies on the abstract Prototype interface, defining the clone() method, which is implemented by concrete prototype classes. Clients request new objects by calling clone() on a prototype, resulting in identical copies with potentially modified state.

Usage

The Prototype pattern is frequently used in scenarios like:

  • Object Creation is Expensive: When creating an object involves significant computational cost, cloning an existing instance can be much faster.
  • Reducing Class Instantiation: Avoid creating multiple classes that differ only in a small number of initialization parameters.
  • Dynamic Object Creation: When the specific types of objects to be created are determined at runtime, prototypes allow for flexible instantiation.
  • Configuration Management: Creating default configurations or templates by cloning a base object.
  • Game Development: Cloning game objects (e.g., enemies, power-ups) efficiently is a common use case.

Examples

  • JavaScript Object.create(): JavaScript’s Object.create() method internally utilizes the Prototype pattern. It allows you to create new objects with specified prototype objects, inheriting properties and methods from the prototype. javascript const animal = { type: ‘animal’, makeSound: function() { console.log(‘Generic animal sound’); } };

    const dog = Object.create(animal); dog.type = ‘dog’; dog.makeSound = function() { console.log(‘Woof!’); };

    console.log(dog.type); // Output: dog dog.makeSound(); // Output: Woof!

  • Git: The version control system Git essentially uses a prototype pattern when branching. A new branch is created as a shallow copy (prototype) of the existing branch, after which changes are applied to the new branch without modifying the original. This allows for experimentation and parallel development. Technically, Git uses content-addressable storage and only stores unique content, but from a conceptual point of view, the branching logic resembles the Prototype pattern.

Specimens

15 implementations
Specimen 150.01 Dart View specimen ↗

The Prototype pattern creates new objects from an existing object, a prototype, rather than using a traditional constructor. This is useful when object creation is expensive or when the exact type of objects to be created isn’t known until runtime. We achieve this in Dart using the clone() method, which each prototype can implement to create a copy of itself. This approach avoids tight coupling to specific classes and allows flexible object instantiation. The Dart example demonstrates a base Shape class and concrete Circle and Rectangle prototypes. Cloning allows creation of new shapes with varying attributes without directly instantiating the classes. This aligns with Dart’s object-oriented nature and promotes code reuse.

abstract class Shape implements Cloneable<Shape> {
  String color;

  Shape(this.color);

  Shape clone();

  @override
  String toString() {
    return 'Shape(color: $color)';
  }
}

class Circle extends Shape {
  int radius;

  Circle(String color, this.radius) : super(color);

  @override
  Circle clone() {
    return Circle(color, radius);
  }

  @override
  String toString() {
    return 'Circle(color: $color, radius: $radius)';
  }
}

class Rectangle extends Shape {
  int width;
  int height;

  Rectangle(String color, this.width, this.height) : super(color);

  @override
  Rectangle clone() {
    return Rectangle(color, width, height);
  }

  @override
  String toString() {
    return 'Rectangle(color: $color, width: $width, height: $height)';
  }
}

// Extend Cloneable to allow use of the clone method.
extension Cloneable<T> on T {
  T clone() {
    return this as T;
  }
}

void main() {
  final circle = Circle('red', 5);
  final rectangle = Rectangle('blue', 10, 20);

  final clonedCircle = circle.clone();
  final clonedRectangle = rectangle.clone();

  print('Original Circle: $circle');
  print('Cloned Circle: $clonedCircle');

  print('Original Rectangle: $rectangle');
  print('Cloned Rectangle: $clonedRectangle');
}