Prototype
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 implementationsThe 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');
}
The Prototype pattern creates new objects from an existing object (the prototype) instead of using a traditional constructor. This is useful when object creation is expensive or complex, or when you need to create variations of existing objects without specifying a precise class for each variation. In Scala, this is efficiently implemented using the clone method (which requires the Cloneable trait) along with case classes for immutable data structures. The example showcases a Shape hierarchy where new shapes are created by cloning existing ones, allowing for variations without new class definitions. Scala’s immutability with case classes further leverages the pattern’s benefits by ensuring cloned objects aren’t unexpectedly modified.
import scala.reflect.ClassTag
trait Shape extends Cloneable {
def draw(): String
def clone(): Shape = super.clone().asInstanceOf[Shape]
}
case class Circle(var x: Int, var y: Int, radius: Int) extends Shape {
override def draw(): String = s"Drawing a circle at ($x, $y) with radius $radius"
}
case class Rectangle(var x: Int, var y: Int, width: Int, height: Int) extends Shape {
override def draw(): String = s"Drawing a rectangle at ($x, $y) with width $width and height $height"
}
object PrototypeExample {
def main(args: Array[String]): Unit = {
val circlePrototype = new Circle(0, 0, 5)
val rectanglePrototype = new Rectangle(0, 0, 10, 5)
val circle1 = circlePrototype.clone()
circle1.x = 10
println(circle1.draw())
val circle2 = circlePrototype.clone()
circle2.y = 5
println(circle2.draw())
val rectangle1 = rectanglePrototype.clone()
rectangle1.width = 20
println(rectangle1.draw())
}
}
The Prototype pattern is a creational design pattern that specifies the kinds of objects to create using an instance of a prototype and creates new objects by copying this prototype. This avoids expensive object creation when object complexity is high. It’s especially useful when the exact configuration of objects being created is unknown until runtime.
This PHP example implements the Prototype pattern by defining an interface (Prototype) that all prototype objects must adhere to. Concrete prototypes (ConcretePrototypeA, ConcretePrototypeB) implement this interface and define a clone() method that creates a new object with the same state as the original. A Client can then create new objects based on these prototypes without creating them from scratch, enhancing efficiency. This implements PHP’s magic method __clone() to perform a shallow copy of the object’s properties, adhering to the language’s conventions for object duplication.
<?php
interface Prototype {
public function clone(): self;
}
class ConcretePrototypeA implements Prototype {
public string $name;
public string $description;
public function __construct(string $name, string $description) {
$this->name = $name;
$this->description = $description;
}
public function clone(): self {
// Use the magic method __clone() for a shallow copy
$clone = new self($this->name, $this->description);
return $clone;
}
public function __clone() {
// Ensure ID is unique for each clone.
$this->name = $this->name . " (clone)";
}
}
class ConcretePrototypeB implements Prototype {
public int $id;
public string $type;
public function __construct(int $id, string $type) {
$this->id = $id;
$this->type = $type;
}
public function clone(): self {
// Use the magic method __clone() for a shallow copy
$clone = new self($this->id, $this->type);
return $clone;
}
public function __clone() {
// Ensure ID is unique for each clone
$this->id = $this->id + 1000;
}
}
class Client {
public Prototype $prototypeA;
public Prototype $prototypeB;
public function __construct(Prototype $prototypeA, Prototype $prototypeB) {
$this->prototypeA = $prototypeA;
$this->prototypeB = $prototypeB;
}
public function createPrototypeA(): ConcretePrototypeA {
return clone $this->prototypeA;
}
public function createPrototypeB(): ConcretePrototypeB {
return clone $this->prototypeB;
}
}
// Usage
$prototypeA = new ConcretePrototypeA("Original A", "This is a prototype A.");
$prototypeB = new ConcretePrototypeB(1, "Prototype B");
$client = new Client($prototypeA, $prototypeB);
$cloneA1 = $client->createPrototypeA();
$cloneA2 = $client->createPrototypeA();
$cloneB1 = $client->createPrototypeB();
print_r($cloneA1);
print_r($cloneA2);
print_r($cloneB1);
?>
The Prototype pattern creates new objects by cloning an existing object, known as the prototype. This avoids the complexity of creating new objects from scratch, especially when object creation is expensive or requires complex initialization. The code defines a Product class with a clone method to create replicas. A ProductFactory manages these prototypes, retrieving them by name and allowing clients to produce new objects based on these templates. This Ruby implementation leverages Object#dup for shallow copying, fitting the language’s dynamic nature and emphasis on object manipulation. It’s a flexible alternative to new when object initialization is complex.
# product.rb
class Product
attr_accessor :name, :price
def initialize(name, price)
@name = name
@price = price
end
def clone
dup # Ruby's built-in duplication method
end
def to_s
"Product: {Name: #{@name}, Price: #{@price}}"
end
end
# product_factory.rb
class ProductFactory
def initialize
@prototypes = {}
end
def register_prototype(name, product)
@prototypes[name] = product
end
def get_prototype(name)
@prototypes[name].clone if @prototypes[name]
end
end
# client.rb
factory = ProductFactory.new
product_a = Product.new("Laptop", 1200)
product_b = Product.new("Mouse", 25)
factory.register_prototype("laptop", product_a)
factory.register_prototype("mouse", product_b)
laptop_clone = factory.get_prototype("laptop")
mouse_clone = factory.get_prototype("mouse")
puts laptop_clone
puts mouse_clone
The Prototype pattern is a creational design pattern that specifies the kinds of objects to create using an instance of a prototype. Instead of instantiating new objects using a class directly (like with new), the pattern copies an existing object, thus cloning it to create new ones. This is useful when object creation is expensive or when the exact type of objects to create isn’t known until runtime.
This Swift implementation uses the NSCopying protocol (a standard way to handle object duplication in the Objective-C/Swift ecosystem) to define a prototype() method on each concrete prototype. A Factory class manages the prototypes and can efficiently create new objects by cloning them. This leverages Swift’s protocol-oriented programming and is a common practice for handling object copies, especially within UIKit or frameworks heavily influenced by Objective-C.
// Define the protocol for cloning
protocol Prototype: NSCopying {
var id: Int { get }
}
// Concrete Prototype class
class ConcretePrototype: Prototype {
var id: Int
init(id: Int) {
self.id = id
}
func prototype() -> Self {
return self.copy() as! Self
}
// Required for NSCopying
func copy(with zone: NSZone? = nil) -> Any {
return ConcretePrototype(id: self.id)
}
}
// Prototype Factory
class PrototypeFactory {
private var prototypes: [Int: Prototype] = [:]
func registerPrototype(prototype: Prototype, withID id: Int) {
prototypes[id] = prototype
}
func getPrototype(withID id: Int) -> Prototype? {
return prototypes[id]?.prototype()
}
}
// Usage
let factory = PrototypeFactory()
let prototype1 = ConcretePrototype(id: 1)
let prototype2 = ConcretePrototype(id: 2)
factory.registerPrototype(prototype: prototype1, withID: 1)
factory.registerPrototype(prototype: prototype2, withID: 2)
if let clonedPrototype1 = factory.getPrototype(withID: 1) {
print("Cloned Prototype 1 ID: \(clonedPrototype1.id)")
}
if let clonedPrototype2 = factory.getPrototype(withID: 2) {
print("Cloned Prototype 2 ID: \(clonedPrototype2.id)")
}
The Prototype pattern creates new objects from an existing object, a “prototype,” rather than using a traditional constructor. This is useful when creating objects is expensive, or when the desired object configuration is complex and better determined by modifying an existing instance. The code defines an interface Shape with a clone() method. Concrete shapes like Circle and Rectangle implement this interface. A ShapeFactory holds references to prototypes and produces clones as needed, thus avoiding repeated object creation with the same configuration. This leverages Kotlin’s concise syntax and inherent support for interfaces and object references, making it a natural fit for the pattern.
// Shape Interface
interface Shape {
fun clone(): Shape
fun draw()
}
// Concrete Shapes
data class Circle(var x: Int, var y: Int, var radius: Int, var color: String) : Shape {
override fun clone(): Shape {
return Circle(x, y, radius, color)
}
override fun draw() {
println("Drawing a Circle at ($x, $y) with radius $radius and color $color")
}
}
data class Rectangle(var x: Int, var y: Int, var width: Int, var height: Int, var color: String) : Shape {
override fun clone(): Shape {
return Rectangle(x, y, width, height, color)
}
override fun draw() {
println("Drawing a Rectangle at ($x, $y) with width $width, height $height and color $color")
}
}
// Prototype Factory
class ShapeFactory {
private val prototypes = mutableMapOf<String, Shape>()
fun registerPrototype(type: String, shape: Shape) {
prototypes[type] = shape
}
fun createShape(type: String): Shape? {
return prototypes[type]?.clone()
}
}
fun main() {
val shapeFactory = ShapeFactory()
// Register prototypes
shapeFactory.registerPrototype("Circle", Circle(10, 10, 5, "Red"))
shapeFactory.registerPrototype("Rectangle", Rectangle(0, 0, 10, 20, "Blue"))
// Create clones
val clonedCircle = shapeFactory.createShape("Circle")
val clonedRectangle = shapeFactory.createShape("Rectangle")
// Use the clones
clonedCircle?.draw()
clonedRectangle?.draw()
//Modify the clone
val anotherCircle = clonedCircle?.clone()
anotherCircle?.x = 20
anotherCircle?.draw()
}
The Prototype pattern creates new objects from an existing object, a prototype, rather than using a traditional constructor. This is useful when the creation process is costly or complex, or when the class and its objects should remain unchanged. In Rust, this is elegantly achieved using the Clone trait and potentially serde for deep copying. The example defines a Shape trait and concrete types like Rectangle and Circle implementing it. A ShapeFactory utilizes a prototype map (using HashMap) to efficiently clone existing shapes, avoiding redundant initialization logic. Rust’s ownership and borrowing rules are naturally respected through the Clone trait, making the pattern safe and expressive.
use std::collections::HashMap;
// Define the base trait
trait Shape {
fn area(&self) -> f64;
fn clone(&self) -> Box<dyn Shape>;
}
// Concrete shape: Rectangle
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn clone(&self) -> Box<dyn Shape> {
Box::new(Rectangle::new(self.width, self.height))
}
}
// Concrete shape: Circle
struct Circle {
radius: f64,
}
impl Circle {
fn new(radius: f64) -> Self {
Circle { radius }
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn clone(&self) -> Box<dyn Shape> {
Box::new(Circle::new(self.radius))
}
}
// Prototype Factory
struct ShapeFactory {
prototypes: HashMap<String, Box<dyn Shape>>,
}
impl ShapeFactory {
fn new() -> Self {
let mut prototypes = HashMap::new();
prototypes.insert("rectangle".to_string(), Box::new(Rectangle::new(10.0, 5.0)));
prototypes.insert("circle".to_string(), Box::new(Circle::new(7.0)));
ShapeFactory { prototypes }
}
fn get_shape(&self, type_name: &str) -> Option<Box<dyn Shape>> {
self.prototypes.get(type_name).map(|prototype| prototype.clone())
}
}
fn main() {
let factory = ShapeFactory::new();
if let Some(rect) = factory.get_shape("rectangle") {
println!("Rectangle Area: {}", rect.area());
}
if let Some(circ) = factory.get_shape("circle") {
println!("Circle Area: {}", circ.area());
}
}
The Prototype pattern creates new objects from an existing object, a prototype, rather than using a traditional constructor. This is useful when creating new objects is expensive, or when the exact type of objects to create isn’t known until runtime. In Go, this is naturally achieved through struct embedding and methods that allow for object cloning. This example demonstrates a Shape interface and concrete shapes. The Clone() method on each shape returns a new instance with the same properties, avoiding repeated initialization logic. Go’s interface-based approach lends itself well to defining the cloning behavior without altering the underlying structure.
package main
import "fmt"
// Shape is the interface for all shapes.
type Shape interface {
Clone() Shape
Type() string
}
// Circle represents a circle shape.
type Circle struct {
Radius float64
Color string
}
func (c *Circle) Clone() Shape {
return &Circle{Radius: c.Radius, Color: c.Color}
}
func (c *Circle) Type() string {
return "Circle"
}
// Rectangle represents a rectangle shape.
type Rectangle struct {
Width float64
Height float64
Color string
}
func (r *Rectangle) Clone() Shape {
return &Rectangle{Width: r.Width, Height: r.Height, Color: r.Color}
}
func (r *Rectangle) Type() string {
return "Rectangle"
}
// ShapeFactory manages the cloning of shapes.
type ShapeFactory struct {
Prototype Shape
}
func (f *ShapeFactory) SetPrototype(shape Shape) {
f.Prototype = shape
}
func (f *ShapeFactory) CreateShape() Shape {
return f.Prototype.Clone()
}
func main() {
// Create a prototype circle
circlePrototype := &Circle{Radius: 5.0, Color: "Red"}
// Create a factory and set the prototype
factory := &ShapeFactory{}
factory.SetPrototype(circlePrototype)
// Create new shapes from the prototype
shape1 := factory.CreateShape()
shape2 := factory.CreateShape()
// Verify that the new shapes are different instances
fmt.Printf("Shape 1: Type=%s, Radius=%f, Color=%s\n", shape1.Type(), shape1.Radius, shape1.Color)
fmt.Printf("Shape 2: Type=%s, Radius=%f, Color=%s\n", shape2.Type(), shape2.Radius, shape2.Color)
//Change the prototype after creating shape1 and shape2
circlePrototype.Color = "Blue"
shape3 := factory.CreateShape()
fmt.Printf("Shape 3: Type=%s, Radius=%f, Color=%s\n", shape3.Type(), shape3.Radius, shape3.Color)
}
The Prototype pattern creates new objects by cloning an existing object, known as the prototype. This avoids the complexity of explicit class instantiation, especially useful when object creation is expensive or when the precise object types needed are determined at runtime. This C implementation uses a common trick: a structure containing a function pointer for cloning. Each concrete prototype type will define its own clone function. This sidesteps C’s lack of built-in inheritance by enabling polymorphism through function pointers. The create_prototype function acts as a factory, allowing the creation of copies based on a registered prototype.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Prototype Interface
typedef struct Prototype Prototype;
typedef struct Prototype* (*CloneFunction)(const Prototype*);
struct Prototype {
CloneFunction clone;
// Shared data among prototypes
int type;
};
// Concrete Prototype 1: Rectangle
typedef struct Rectangle Rectangle;
struct Rectangle {
Prototype base;
int width;
int height;
};
Rectangle* rectangle_clone(const Prototype* prototype) {
Rectangle* new_rectangle = (Rectangle*)malloc(sizeof(Rectangle));
if (new_rectangle == NULL) {
perror("malloc failed");
return NULL;
}
memcpy(new_rectangle, prototype, sizeof(Rectangle));
return new_rectangle;
}
// Concrete Prototype 2: Circle
typedef struct Circle Circle;
struct Circle {
Prototype base;
int radius;
};
Circle* circle_clone(const Prototype* prototype) {
Circle* new_circle = (Circle*)malloc(sizeof(Circle));
if (new_circle == NULL) {
perror("malloc failed");
return NULL;
}
memcpy(new_circle, prototype, sizeof(Circle));
return new_circle;
}
// Prototype Manager / Factory
typedef struct PrototypeManager PrototypeManager;
struct PrototypeManager {
Prototype* prototypes[2]; // Limited to 2 prototypes for simplicity
int count;
};
PrototypeManager* create_prototype_manager() {
PrototypeManager* manager = (PrototypeManager*)malloc(sizeof(PrototypeManager));
if (manager == NULL) {
perror("malloc failed");
return NULL;
}
manager->count = 0;
return manager;
}
void register_prototype(PrototypeManager* manager, Prototype* prototype) {
if (manager->count < 2) {
manager->prototypes[manager->count++] = prototype;
} else {
fprintf(stderr, "Too many prototypes registered.\n");
}
}
Prototype* create_prototype(PrototypeManager* manager, int type) {
for (int i = 0; i < manager->count; ++i) {
if (manager->prototypes[i]->type == type) {
return (Prototype*)manager->prototypes[i]->clone(manager->prototypes[i]);
}
}
return NULL; // Prototype not found
}
int main() {
PrototypeManager* manager = create_prototype_manager();
Rectangle* rectangle = (Rectangle*)malloc(sizeof(Rectangle));
rectangle->base.clone = rectangle_clone;
rectangle->base.type = 1;
rectangle->width = 10;
rectangle->height = 5;
register_prototype(manager, (Prototype*)rectangle);
Circle* circle = (Circle*)malloc(sizeof(Circle));
circle->base.clone = circle_clone;
circle->base.type = 2;
circle->radius = 7;
register_prototype(manager, (Prototype*)circle);
Rectangle* new_rectangle = (Rectangle*)create_prototype(manager, 1);
if (new_rectangle) {
printf("New Rectangle: width=%d, height=%d\n", new_rectangle->width, new_rectangle->height);
free(new_rectangle);
}
Circle* new_circle = (Circle*)create_prototype(manager, 2);
if (new_circle) {
printf("New Circle: radius=%d\n", new_circle->radius);
free(new_circle);
}
free(manager);
free(rectangle);
free(circle);
return 0;
}
The Prototype pattern creates new objects by cloning an existing object (the prototype). This avoids the expense of creating new objects from scratch, particularly when object creation is complex. It’s useful when the specific types of objects to create are determined at runtime.
The code demonstrates a simple Prototype pattern using a base class Shape with a virtual clone() method. Concrete shapes (Circle, Rectangle) inherit from Shape and override clone() to return a new instance of themselves. A ShapeFactory manages prototypes of different shapes, allowing clients to request clones without knowing the concrete classes. This leverages C++’s polymorphism and dynamic memory allocation for flexible object creation, aligning with its OOP capabilities. The use of virtual destructors in the base class prevents memory leaks when dealing with polymorphic object deletion.
#include <iostream>
#include <string>
#include <map>
class Shape {
public:
virtual ~Shape() = default;
virtual Shape* clone() = 0;
virtual void draw() = 0;
virtual std::string type() const = 0;
};
class Circle : public Shape {
public:
Circle(int radius) : radius_(radius) {}
Shape* clone() override { return new Circle(radius_); }
void draw() override { std::cout << "Drawing a Circle" << std::endl; }
std::string type() const override { return "Circle"; }
private:
int radius_;
};
class Rectangle : public Shape {
public:
Rectangle(int width, int height) : width_(width), height_(height) {}
Shape* clone() override { return new Rectangle(width_, height_); }
void draw() override { std::cout << "Drawing a Rectangle" << std::endl; }
std::string type() const override { return "Rectangle"; }
private:
int width_;
int height_;
};
class ShapeFactory {
public:
void registerPrototype(const std::string& type, Shape* prototype) {
prototypes_[type] = prototype;
}
Shape* createShape(const std::string& type) {
auto it = prototypes_.find(type);
if (it != prototypes_.end()) {
return it->second->clone();
}
return nullptr;
}
private:
std::map<std::string, Shape*> prototypes_;
};
int main() {
ShapeFactory factory;
factory.registerPrototype("Circle", new Circle(5));
factory.registerPrototype("Rectangle", new Rectangle(10, 20));
Shape* circle = factory.createShape("Circle");
Shape* rectangle = factory.createShape("Rectangle");
circle->draw();
rectangle->draw();
delete circle;
delete rectangle;
// Clean up prototypes to avoid memory leaks, important for longer-lived factories
for(auto const& [key, val] : factory.prototypes_) {
delete val;
}
return 0;
}
The Prototype pattern is a creational design pattern that specifies the kinds of objects to create through a prototypical instance, and creates new objects by copying these prototypes. This avoids the need for explicit constructors when object creation is costly or complex, offering flexibility and reducing coupling.
The following C# example demonstrates the Prototype pattern using the ICloneable interface and implements both “Shallow Copy” and “Deep Copy” strategies. A Monster class serves as the prototype. Concrete monster types (Goblin, Dragon) clone themselves, providing different implementations of cloning to handle potentially complex object graphs (demonstrated by the Dragon’s list of Parts to showcase deep copying). This leverages C#’s built-in mechanisms for object cloning and interfaces, aligning with its flexible and type-safe nature.
using System;
using System.Collections.Generic;
using System.Linq;
// Prototype interface
public interface IPrototype<T>
{
T Clone();
}
// Base Monster class
public class Monster : IPrototype<Monster>
{
public string Name { get; set; }
public int Level { get; set; }
public Monster(string name, int level)
{
Name = name;
Level = level;
}
public virtual Monster Clone()
{
// Shallow Copy (MemberwiseClone) - good for simple objects
return (Monster)MemberwiseClone();
}
public override string ToString()
{
return $"Name: {Name}, Level: {Level}";
}
}
// Concrete Monster - Goblin (Shallow Copy)
public class Goblin : Monster
{
public Goblin(string name, int level) : base(name, level) { }
}
// Concrete Monster - Dragon (Deep Copy)
public class Dragon : Monster
{
public List<Part> Parts { get; set; }
public Dragon(string name, int level, List<Part> parts) : base(name, level)
{
Parts = parts;
}
public override Dragon Clone()
{
// Deep Copy - create new instances of nested objects
List<Part> clonedParts = Parts.Select(p => p.Clone()).ToList();
return new Dragon(Name, Level, clonedParts);
}
}
// Part class (for Dragon's deep copy example)
public class Part : ICloneable
{
public string Name { get; set; }
public Part(string name)
{
Name = name;
}
public Part Clone()
{
return (Part)MemberwiseClone();
}
public override string ToString()
{
return Name;
}
}
public class PrototypeExample
{
public static void Main(string[] args)
{
// Create a prototype Dragon
Dragon originalDragon = new Dragon("Smaug", 10, new List<Part> { new Part("Wing"), new Part("Scale") });
// Clone the Dragon
Dragon clonedDragon = originalDragon.Clone();
// Modify the cloned Dragon's parts
clonedDragon.Parts[0].Name = "Broken Wing";
Console.WriteLine("Original Dragon:");
Console.WriteLine(originalDragon);
Console.WriteLine("Parts: " + string.Join(", ", originalDragon.Parts.Select(p => p.ToString())));
Console.WriteLine("\nCloned Dragon:");
Console.WriteLine(clonedDragon);
Console.WriteLine("Parts: " + string.Join(", ", clonedDragon.Parts.Select(p => p.ToString())));
// Demonstrate Goblin shallow copy
Goblin goblin = new Goblin("Griz", 5);
Goblin clonedGoblin = goblin.Clone();
clonedGoblin.Name = "Griz Jr.";
Console.WriteLine("\nOriginal Goblin");
Console.WriteLine(goblin);
Console.WriteLine("\nCloned Goblin");
Console.WriteLine(clonedGoblin);
}
}
The Prototype pattern is a creational design pattern that allows you to create new objects by cloning an existing object—the prototype. Instead of relying on a constructor function and new operator, it uses a prototype object to create copies. This is especially useful when creating objects is expensive or complex, and you want to avoid repetitive initialization.
The code implements the pattern using TypeScript classes and the Object.assign() method for shallow cloning. Animal is the base prototype, and concrete animal types like Dog and Cat can be cloned without knowing their specific construction details. TypeScript’s class-based structure nicely supports defining prototype interfaces, and Object.assign() provides a concise way to achieve cloning, fitting idiomatic TypeScript style by leaning into built-in JavaScript functionalities where appropriate.
// Prototype Interface
interface Animal {
species: string;
name: string;
makeSound(): string;
clone(): Animal;
}
// Concrete Prototype
class Dog implements Animal {
species: string = "Dog";
name: string;
breed: string;
constructor(name: string, breed: string) {
this.name = name;
this.breed = breed;
}
makeSound(): string {
return "Woof!";
}
clone(): Animal {
return Object.assign({}, this); // Shallow copy
}
}
class Cat implements Animal {
species: string = "Cat";
name: string;
color: string;
constructor(name: string, color: string) {
this.name = name;
this.color = color;
}
makeSound(): string {
return "Meow!";
}
clone(): Animal {
return Object.assign({}, this); // Shallow copy
}
}
// Usage
const myDog = new Dog("Buddy", "Golden Retriever");
const clonedDog = myDog.clone();
const myCat = new Cat("Whiskers", "Gray");
const clonedCat = myCat.clone();
console.log(myDog.name, myDog.breed, myDog.makeSound());
console.log(clonedDog.name, clonedDog.breed, clonedDog.makeSound());
console.log(myCat.name, myCat.color, myCat.makeSound());
console.log(clonedCat.name, clonedCat.color, clonedCat.makeSound());
The Prototype pattern creates objects based on an initial, pre-existing object—the prototype—rather than a class. New objects inherit properties and methods from this prototype through the prototype chain. This avoids using class-based inheritance, providing a more flexible and dynamic approach to object creation. The code demonstrates this by defining a Shape prototype with a draw method. Specific shapes like Circle and Square are created by cloning the Shape prototype and customizing properties. This is idiomatic JavaScript as it leverages the language’s inherent prototypal inheritance, avoiding the need for class and extends keywords and embracing delegation.
// Shape Prototype
function Shape(color) {
this.color = color;
}
Shape.prototype.draw = function() {
console.log(`Drawing a ${this.color} shape.`);
};
// Circle Prototype (inherits from Shape)
function Circle(color, radius) {
// Clone the Shape prototype
Shape.call(this, color);
this.radius = radius;
}
// Set up prototype chain
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle; // Reset constructor
Circle.prototype.draw = function() {
Shape.prototype.draw.call(this); // Call Shape's draw first
console.log(`Radius: ${this.radius}`);
};
// Square Prototype (inherits from Shape)
function Square(color, side) {
Shape.call(this, color);
this.side = side;
}
Square.prototype = Object.create(Shape.prototype);
Square.prototype.constructor = Square;
Square.prototype.draw = function() {
Shape.prototype.draw.call(this);
console.log(`Side: ${this.side}`);
};
// Usage
const myCircle = new Circle('red', 5);
myCircle.draw();
const mySquare = new Square('blue', 4);
mySquare.draw();
The Prototype pattern is a creational design pattern that specifies the kinds of objects to create using an instance of a prototype and creates new objects by cloning this prototype. It’s useful when creating objects is complex or costly, and you want to avoid repetitive creation of similar objects.
The Python implementation uses the copy module’s deepcopy() function to create independent copies of prototype objects. An abstract base class Shape defines a clone method, implemented in concrete classes like Rectangle and Circle. A ShapeStore acts as a registry holding prototypes for different shapes. Clients request new shapes by cloning existing prototypes from the store, rather than instantiating concrete classes directly. This fits Python’s dynamic nature and reliance on duck typing, demonstrating flexibility and avoiding tight coupling to specific classes. Using deepcopy ensures that changes to the cloned object don’t affect the original prototype.
import copy
class Shape:
def clone(self):
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def __str__(self):
return f"Rectangle(width={self.width}, height={self.height})"
def clone(self):
return copy.deepcopy(self)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def __str__(self):
return f"Circle(radius={self.radius})"
def clone(self):
return copy.deepcopy(self)
class ShapeStore:
def __init__(self):
self.shapes = {}
def add_shape(self, key, shape):
self.shapes[key] = shape
def get_shape(self, key):
return self.shapes[key].clone()
if __name__ == "__main__":
shape_store = ShapeStore()
rectangle = Rectangle(10, 20)
shape_store.add_shape("rectangle", rectangle)
circle = Circle(5)
shape_store.add_shape("circle", circle)
clone_rectangle = shape_store.get_shape("rectangle")
clone_circle = shape_store.get_shape("circle")
print(f"Original Rectangle: {rectangle}")
print(f"Cloned Rectangle: {clone_rectangle}")
clone_rectangle.width = 15
print(f"Changed Cloned Rectangle width: {clone_rectangle}")
print(f"Original Rectangle after clone change: {rectangle}") # unaffected
print(f"Original Circle: {circle}")
print(f"Cloned Circle: {clone_circle}")
The Prototype pattern is a creational pattern that specifies the kinds of objects to create through a prototype instance and creates new objects by copying this prototype. It’s useful when creating objects is expensive or complex, and you need many similar objects. Instead of initializing each object from scratch, you clone a pre-configured prototype.
This Java example implements the Prototype pattern using the Cloneable interface and copy() method. The Shape class serves as the base prototype, and concrete shapes like Circle and Rectangle implement cloning. We use a ShapeFactory to store and retrieve prototypes, avoiding direct new instantiation for common shapes. This approach leverages Java’s object copying mechanisms and is a relatively standard way to approach prototyping, favoring interfaces over abstract classes for greater flexibility.
// Shape.java
public interface Shape extends Cloneable {
Object copy();
String getType();
}
// Circle.java
public class Circle implements Shape {
private int x;
private int y;
private int radius;
public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public Object copy() {
return new Circle(x, y, radius);
}
@Override
public String getType() {
return "Circle";
}
@Override
public String toString(){
return String.format("Circle{x=%d, y=%d, radius=%d}", x, y, radius);
}
}
// Rectangle.java
public class Rectangle implements Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public Object copy() {
return new Rectangle(width, height);
}
@Override
public String getType() {
return "Rectangle";
}
@Override
public String toString(){
return String.format("Rectangle{width=%d, height=%d}", width, height);
}
}
// ShapeFactory.java
import java.util.HashMap;
import java.util.Map;
public class ShapeFactory {
private final Map<String, Shape> prototypes = new HashMap<>();
public ShapeFactory() {
prototypes.put("Circle", new Circle(0, 0, 1));
prototypes.put("Rectangle", new Rectangle(10, 20));
}
public Shape getPrototype(String type) {
try {
return (Shape) prototypes.get(type).copy(); // Explicit cast after copy
} catch (NullPointerException e) {
return null; // Or throw an exception if the prototype isn't found
}
}
}
// Main.java
public class Main {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape circle1 = shapeFactory.getPrototype("Circle");
Shape circle2 = shapeFactory.getPrototype("Circle");
Shape rectangle1 = shapeFactory.getPrototype("Rectangle");
System.out.println(circle1);
System.out.println(circle2);
System.out.println(rectangle1);
System.out.println("circle1 == circle2: " + (circle1 == circle2)); // Should print false
}
}