Prototype & Factory Method Design Patterns in Java - ClauseGuard
Introduction When ClauseGuard processes an uploaded contract, two problems appear immediately at scale. First, it needs to pick the right document parser -- PDF or DOCX -- without hardcoding that decision into business logic. Second, it needs to stamp out hundreds of clause objects fast without reconstructing them from scratch each time. Prototype and Factory Method are two classic Gang-of-Four creational patterns that address exactly these two problems. This post walks through both: the concept, the UML, the Java code, and when to reach for each one. What you will learn ● What the Prototype pattern is and when it helps ● What the Factory Method pattern is and when it helps ● Java implementation of both patterns -- under 60 lines each ● UML class structure with correct attributes and relationships ● How both patterns compose in the ClauseGuard pipeline Pattern 1 -- Prototype TL;DR -- Clone don't construct. The Problem A single contract upload in ClauseGuard can produce 50-200 clause objects. Building each StandardClause or HighRiskClause from scratch -- allocating memory, setting defaults, registering metadata -- adds measurable overhead at scale. The Prototype pattern solves this by maintaining a ClauseRegistry of pre-built template objects and returning independent clones on demand instead of calling new every time. UML Class Structure «interface» ClausePrototype - clone(): ClausePrototype - getType(): String ^ (realizes) ^ (realizes) | | StandardClause HighRiskClause - clauseID: String - clauseID: String - text: String - text: String - riskCategory: - riskCategory: "Standard" "High Risk" - clone() + clone() - getType() + getType() ClauseRegistry ----uses----> ClausePrototype - registry: Map - register(key, proto): void - getClone(key): ClausePrototype ^ dashed = realization (implements) | ----> solid = association (uses) Java Implementation // ClausePrototype.java public interface ClausePrototype { ClausePrototype clone(); String getType(); } // StandardClause.java public class StandardClause implements ClausePrototype { private String clauseID, text; private String riskCategory = "Standard"; public StandardClause(String clauseID, String text) { this.clauseID = clauseID; this.text = text; } // Copy constructor -- used by clone() private StandardClause(StandardClause src) { this.clauseID=src.clauseID; this.text=src.text; this.riskCategory=src.riskCategory; } @override public ClausePrototype clone() { return new StandardClause(this); } @override public String getType() { return riskCategory; } } // HighRiskClause.java -- same structure, different default public class HighRiskClause implements ClausePrototype { private String clauseID, text; private String riskCategory = "High Risk"; private HighRiskClause(HighRiskClause src) { this.clauseID=src.clauseID; this.text=src.text; this.riskCategory=src.riskCategory; } @override public ClausePrototype clone() { return new HighRiskClause(this); } @override public String getType() { return riskCategory; } } // ClauseRegistry.java -- holds templates, clones on demand public class ClauseRegistry { private Map registry = new HashMap<>(); public void register(String key, ClausePrototype proto) { registry.put(key, proto); } public ClausePrototype getClone(String key) { ClausePrototype p = registry.get(key); if (p == null) throw new IllegalArgumentException("No prototype: " + key); return p.clone(); // returns fresh independent copy } } When to Use It Use Prototype when Avoid it when Object construction is expensive Objects are cheap to construct Need many similar objects fast Every object is completely unique Decouple creation from client Deep-copy logic becomes complex Template values rarely change Object state changes too frequently Pattern 2 -- Factory Method TL;DR -- Decide at runtime, not compile time. The Problem ClauseGuard accepts both PDF and DOCX uploads. Without a pattern, every upload handler needs a hardcoded if (isPDF) branch, tightly coupling the controller to the parser implementation. Adding a new format would mean touching existing code, violating the Open/Closed Principle. Factory Method pushes the type decision into dedicated factory subclasses. The caller simply invokes parserFactory.parse(filePath) and receives the correct extracted text without knowing whether a PDFParser or DOCXParser was created internally. UML Class Structure «abstract» «abstract» ParserFactory «creates» ----> DocumentParser - createParser(fp): DocumentParser - filePath: String - parse(fp): String + parse(): String {template method} + validate(): boolean^ (extends) ^ (extends) | | PDFParserFactory --creates--> PDFParser - createParser(fp) - library: "PDFBox" {returns PDFParser} + parse(): String DOCXParserFactory --creates--> DOCXParser - createParser(fp) - library: "Apache POI" {returns DOCXParser} + parse(): String ^ solid = inheritance (extends) | ----> dashed = dependency (creates) Java Implementation // DocumentParser.java -- abstract product public abstract class DocumentParser { protected String filePath; public DocumentParser(String fp) { this.filePath = fp; } public abstract String parse(); // subclasses implement this public boolean validate() { return filePath != null && !filePath.isBlank(); } } // PDFParser.java -- concrete product (Apache PDFBox) public class PDFParser extends DocumentParser { private final String library = "PDFBox"; public PDFParser(String fp) { super(fp); } @override public String parse() { return "PDF via " + library + " : " + filePath; } } // DOCXParser.java -- concrete product (Apache POI) public class DOCXParser extends DocumentParser { private final String library = "Apache POI"; public DOCXParser(String fp) { super(fp); } @override public String parse() { return "DOCX via " + library + " : " + filePath; } } // ParserFactory.java -- abstract creator with template method public abstract class ParserFactory { // Factory method -- subclass decides which parser to return public abstract DocumentParser createParser(String fp); // Template method -- calls createParser() internally public String parse(String fp) { DocumentParser parser = createParser(fp); if (!parser.validate()) throw new IllegalArgumentException("Invalid file path"); return parser.parse(); } } // Concrete factories -- one line each, that is the beauty public class PDFParserFactory extends ParserFactory { @override public DocumentParser createParser(String fp) { return new PDFParser(fp); } } public class DOCXParserFactory extends ParserFactory { @override public DocumentParser createParser(String fp) { return new DOCXParser(fp); } } When to Use It Use Factory Method when Avoid it when Type is unknown until runtime Only one concrete type ever exists Add new types without changing callers A simple if/else is genuinely enough Subclasses should control creation Extra abstraction adds no real value Unit testing needs mock parsers Team unfamiliar with the overhead How They Work Together in ClauseGuard Neither pattern knows about the other -- they compose cleanly through the pipeline. Factory Method handles "what type of file is this?" at the upload layer. Once text is extracted and segmented, the Prototype Registry handles "stamp out clause objects fast" at the analysis layer. CLAUSEGUARD UPLOAD PIPELINE +---------------------------+ | Contract uploaded | +-------------+-------------+ | +----------v----------+ | FACTORY METHOD | .pdf? -> PDFParserFactory -> PDFParser | ParserFactory | .docx? -> DOCXParserFactory -> DOCXParser +----------+----------+ | extracted text +----------v----------+ | Clause Segment | splits into 50-200 clause objects +----------+----------+ | clause text list +----------v----------+ | PROTOTYPE | registry.getClone("standard") | ClauseRegistry | registry.getClone("high-risk") +----------+----------+ | populated clause objects +----------v----------+ | Risk Dashboard | Standard / Needs Review / High Risk +---------------------+ Pattern Comparison Aspect Prototype Factory Method Solves Expensive repeated object creation Runtime type selection without coupling Key method clone() createParser() Relationship Interface + concrete classes Abstract class + subclasses ClauseGuard role Clone clause templates from registry Choose PDF vs DOCX parser at upload GoF category Creational Creational MoSCoW Should Must Open/Closed Registry isolates construction New parsers need zero existing changes Takeaway " Neither of these patterns is theoretically complex -- you could understand both in an afternoon. The real skill is recognising which problem each one fits. When object construction is the bottleneck, reach for Prototype. When the type of object you need is a runtime decision you want to hide from the caller, reach for Factory Method. In ClauseGuard, both problems exist in the same pipeline -- which is exactly why both patterns earn their place. Top comments (0)
Comments
No comments yet. Start the discussion.