| Lesson 7 | Pattern scope |
| Objective | Distinguish between Class and Object patterns and know when to use each. |
Design Pattern Scope: Class vs. Object
In the GoF catalog, scope describes where the variability is resolved:
Class patterns use inheritance and are decided largely at compile time;
Object patterns use composition/delegation and are decided at run time.
Both scopes appear across creational, structural, and behavioral categories.
At a glance
- Class patterns: variation by overriding (inheritance). Relationships are fixed in the type system. Fewer in number; used where algorithm skeletons or construction hooks must be enforced.
- Object patterns: variation by composition (interfaces + injected collaborators). Relationships can change at runtime; most GoF patterns are object-scoped.
Common misconceptions (quick fixes)
- Not just creational: “Class vs. Object” is scope, not the same as creational/structural/behavioral.
- Naming matters: Avoid the vague phrase “Factory Pattern.” Prefer precise names:
Factory Method (class scope) vs. Abstract Factory (object scope).
- Adapter has two forms: Class Adapter (multiple inheritance; class scope) and Object Adapter (composition; object scope).
Decision guide
- Need a fixed algorithm skeleton with overridable steps? Choose a Class pattern (e.g., Template Method).
- Need to swap behavior at runtime or test with mocks? Choose an Object pattern (e.g., Strategy, State).
- Need to defer object choice but keep creation hook in a base class? Factory Method (class).
- Need to produce families of related objects without binding to concretes? Abstract Factory (object).
Mini examples (Java)
Class scope - Template Method (compile-time skeleton, subclasses fill steps):
abstract class ReportGenerator {
public final String generate() { // algorithm skeleton
String data = fetch();
String normalized = transform(data);
return render(normalized);
}
protected abstract String fetch();
protected String transform(String d) { return d.trim(); } // default step
protected abstract String render(String normalized);
}
final class HtmlReport extends ReportGenerator {
protected String fetch() { return "..."; }
protected String render(String s) { return "<html>" + s + "</html>"; }
}
Object scope - Strategy (runtime-composable policy):
interface Compression {
byte[] apply(byte[] input);
}
final class GzipCompression implements Compression {
public byte[] apply(byte[] input) { /* ... */ return input; }
}
final class ZipCompression implements Compression {
public byte[] apply(byte[] input) { /* ... */ return input; }
}
final class BackupService {
private Compression strategy;
BackupService(Compression strategy) { this.strategy = strategy; }
void setStrategy(Compression c) { this.strategy = c; } // swap at runtime
byte[] backup(byte[] payload) { return strategy.apply(payload); }
}
GoF scope map (concise)
Representative, not exhaustive. When in doubt, prefer the object form.
- Class patterns (few):
Factory Method (creational),
Template Method and Interpreter (behavioral),
Adapter (class) (structural).
- Object patterns (most):
Creational - Abstract Factory, Builder, Prototype, Singleton;
Structural - Adapter (object), Bridge, Composite, Decorator, Facade, Flyweight, Proxy;
Behavioral - Chain of Responsibility, Command, Iterator, Mediator,
Memento, Observer, State, Strategy, Visitor.
Trade-offs by scope
- Class scope: Simpler call paths and static guarantees; less runtime flexibility; subclass coupling may grow over time.
- Object scope: More flexible and testable; small indirection overhead; requires disciplined interface design.
Checklist
- Write the intent and forces first (what must vary, how often, who owns the change?).
- Pick the scope that localizes change with minimal complexity.
- Design interfaces and contracts; keep concrete types out of public APIs.
- Document consequences (perf, complexity, test seams) for your chosen scope.
- Use precise names-Factory Method vs. Abstract Factory-and avoid ambiguous “Factory Pattern.”
