Prototype Pattern (with Registry)
Vector Graphics / Diagramming Editor
Context
You are building a vector graphics editor (or diagramming tool) with a palette of tools: Rect-
angle, Ellipse, TextBox, Arrow, etc. Each shape comes with style, constraints, and sometimes
nested parts. Constructing a fully wired shape from scratch can be costly. Users pick a tool
and place many instances of the same kind of shape.
1 Part A — Why Prototype?
1) What pains are we feeling?
• Are shape constructors doing heavy, repetitive work (loading styles, adding handles, attaching
constraints)?
• Do we want to create new shapes by example, not by knowing their concrete class names?
• Can the set of shapes change at runtime (plugins, feature flags), so hard-coding construc-
tors is brittle?
2) What properties do we desire?
• On click, the palette should produce a fresh shape by copying a prototypical instance.
• Creation should be fast and decoupled from concrete types.
• We should be able to add, remove, or reconfigure available shapes without touching
client code.
3) Insight
Keep one configured exemplar per shape type. To create a new instance, clone the exemplar.
Keep those exemplars in a registry (a small in-memory catalog) so tools and frameworks can
request clones by key.
2 Part B — Solution: Prototype + Registry (Shapes)
1) Roles
• Prototype (Shape) exposes copy() (or clone()) to duplicate itself.
• Concrete Prototypes (Rectangle, Ellipse, TextBox) implement copying deep enough for
independence.
• Registry (Prototype Manager) maps keys to prototypes and returns clones.
• Client (Tool/Canvas) asks the registry for a fresh shape to place and then positions it.
1
2) Java sketch (explicit copy(), not Cloneable)
1 // --- Prototype contract ---
2 interface Shape {
3 Shape copy () ; // deep enough for independence
4 void draw ( java . awt . Graphics2D g ) ;
5 void setPosition ( int x , int y ) ;
6 }
7
8 // --- Style value objects ( copied or shared , as appropriate ) ---
9 final class StrokeStyle {
10 final float width ; final boolean dashed ;
11 StrokeStyle ( float w , boolean d ) { this . width = w ; this . dashed = d ; }
12 StrokeStyle copy () { return new StrokeStyle ( width , dashed ) ; }
13 }
14 final class FillStyle {
15 final int rgba ; // ARGB
16 FillStyle ( int rgba ) { this . rgba = rgba ; }
17 FillStyle copy () { return new FillStyle ( rgba ) ; }
18 }
19
20 // --- Concrete prototypes ---
21 final class Rectangle implements Shape {
22 private int x ,y ,w , h ;
23 private final StrokeStyle stroke ;
24 private final FillStyle fill ;
25
26 Rectangle ( int w , int h , StrokeStyle s , FillStyle f ) {
27 this . w = w ; this . h = h ; this . stroke = s ; this . fill = f ;
28 }
29 public Shape copy () {
30 // Copy styles ( immutables could be shared ; copied here for
clarity )
31 return new Rectangle (w ,h , stroke . copy () , fill . copy () ) ;
32 }
33 public void setPosition ( int x , int y ) { this . x = x ; this . y = y ; }
34 public void draw ( java . awt . Graphics2D g ) { /* draw rect with stroke /
fill */ }
35 public String toString () { return " Rect ( " + w + " x " + h + " ) @ ( " + x + " ," + y + " ) " ;
}
36 }
37
38 final class Ellipse implements Shape {
39 private int cx , cy , rx , ry ;
40 private final StrokeStyle stroke ;
41 private final FillStyle fill ;
42
43 Ellipse ( int rx , int ry , StrokeStyle s , FillStyle f ) {
44 this . rx = rx ; this . ry = ry ; this . stroke = s ; this . fill = f ;
45 }
46 public Shape copy () {
47 return new Ellipse ( rx , ry , stroke . copy () , fill . copy () ) ;
48 }
49 public void setPosition ( int x , int y ) { this . cx = x ; this . cy = y ; }
50 public void draw ( java . awt . Graphics2D g ) { /* draw ellipse with
stroke / fill */ }
51 public String toString () { return " Ellipse ( r = " + rx + " ," + ry + " ) @ ( " + cx + " ,
" + cy + " ) " ; }
2
52 }
53
54 final class TextBox implements Shape {
55 private int x , y ; private String text ;
56 private final FillStyle fill ;
57
58 TextBox ( String text , FillStyle f ) { this . text = text ; this . fill = f ; }
59 public Shape copy () { return new TextBox ( text , fill . copy () ) ; }
60 public void setPosition ( int x , int y ) { this . x = x ; this . y = y ; }
61 public void draw ( java . awt . Graphics2D g ) { /* draw text with fill */
}
62 public String toString () { return " TextBox (\" " + text + " \") @ ( " + x + " ," + y +
")"; }
63 }
64
65 // --- Prototype Registry ---
66 final class ShapeRegistry {
67 private final java . util . Map < String , Shape > store = new java . util .
HashMap < >() ;
68
69 public void register ( String key , Shape proto ) { store . put ( key .
toLowerCase () , proto ) ; }
70 public void unregister ( String key ) { store . remove ( key . toLowerCase () )
; }
71 public Shape create ( String key ) {
72 Shape p = store . get ( key . toLowerCase () ) ;
73 if ( p == null ) throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Unknown : " + key )
;
74 return p . copy () ; // <-- clone the prototype
75 }
76 }
77
78 // --- Client : Palette Tool and Canvas ---
79 final class Tool {
80 private final ShapeRegistry registry ; private final String key ;
81 Tool ( ShapeRegistry r , String key ) { this . registry = r ; this . key = key ; }
82 public Shape onClick ( int x , int y ) {
83 Shape s = registry . create ( key ) ;
84 s . setPosition (x , y ) ;
85 return s ;
86 }
87 }
88
89 class Demo {
90 public static void main ( String [] args ) {
91 ShapeRegistry reg = new ShapeRegistry () ;
92 reg . register ( " rect " , new Rectangle (120 , 80 , new StrokeStyle (2.0
f , false ) , new FillStyle (0 xFF3366CC ) ) ) ;
93 reg . register ( " ellipse " , new Ellipse (60 , 40 , new StrokeStyle (1.5
f , true ) , new FillStyle (0 xAA33CC66 ) ) ) ;
94 reg . register ( " text " , new TextBox ( " Hello " , new FillStyle (0
xFFFFFFFF ) ) ) ;
95
96 Tool rectTool = new Tool ( reg , " rect " ) ;
97 Tool textTool = new Tool ( reg , " text " ) ;
98
99 System . out . println ( rectTool . onClick (100 ,100) ) ; // fresh clone
100 System . out . println ( textTool . onClick (220 ,120) ) ; // fresh clone
3
101
102 // Plugins could : reg . register (" swimlane " , new Swimlane (...) ) ;
103 // And tools can immediately use it without code changes
elsewhere .
104 }
105 }
3) Why this helps (reflection)
• Clients create instances by cloning a configured exemplar; they need no knowledge of con-
structor details.
• Fewer subclasses: many variations become different registered prototypes rather than new
classes.
• The registry lets you add/remove shapes at runtime and inspect what is available.
• Dynamically loaded features can self-register a prototype on load.
3 Part C — Shallow vs Deep Copy & Initialization
Implementing copy() correctly is the most important design decision:
• Decide what is shared vs copied. Immutable style objects can be shared safely; mutable ones
should be copied.
• Complex shapes (composites, constraints, event hooks) often require deep copies or custom
reattachment logic.
• A uniform copy() signature cannot take arbitrary parameters. Common practice: copy
then initialize (e.g., position, text content) via setters or a small initialize(...) method.
4 Part D — When Prototype vs Factories
• Use Prototype when construction is expensive, when objects are better created “by exam-
ple,” or when the set of creatable products changes at runtime.
• Use Factory Method when you have a fixed algorithm that needs to create objects at one
step, and the concrete product varies by subclass of the creator.
• Use Abstract Factory when you must create families of related objects that should be kept
consistent.
• You can implement an abstract factory using a registry of prototypes: each creation method
returns a clone from the registry.
4
5 UML — Prototype with Registry for Shapes
ShapeR
Shape (Prototype)
+ copy(): Shape stores prototypes - map: String
+ draw(g): void + register(key,
+ setPosition(x,y): void + unregister(ke
+ create(key):
Rectangle Ellipse TextBox
Tool / Can
+ copy(): Shape + copy(): Shape + copy(): Shape
... ... ... + onClick(x,y):