Design Patterns & LLD — Go-To Mind Map Notes
Source: Dive Into Design Patterns (Alexander Shvets / Refactoring.Guru, v2023)
Format: recall-first mind map — every node is Q → hook → when → tiny Go skeleton
Diagrams: Mermaid (
mindmap/flowchart)Language: Go (interfaces + composition; no classical inheritance trees)
How to use these notes (recall pattern)
flowchart TD
C["Central problem: What varies?"]
C --> CREATE["CREATE objects"]
C --> STRUCTURE["STRUCTURE objects"]
C --> BEHAVE["COMMUNICATE / assign duties"]
Enter fullscreen mode Exit fullscreen mode
Active recall loop (best retention):
flowchart LR
Q[Trigger Q] --> Say[Say one-liner]
Say --> Sketch[Sketch participants]
Sketch --> Code[Write Go skeleton]
Code --> Check[Check notes]
Check --> Drill[Confusion pairs]
Enter fullscreen mode Exit fullscreen mode
- Cover the answer. Read only the Trigger Q.
- Say the one-liner out loud (Feynman).
- Sketch the participants from memory.
- Write 5–10 lines of Go from memory, then check.
- Drill confusion pairs (Decorator vs Proxy, Strategy vs State, etc.).
Atomic card shape (every pattern):
| Field | Purpose |
|——-|———|
| Trigger Q | When does this fire in an LLD/interview? |
| One-liner | Intent in one breath |
| Hook | Real-world metaphor to lock memory |
| Structure | Participants (Go names) |
| Skeleton | Minimal idiomatic Go |
| Trap | Common mix-up / anti-pattern |
ROOT MAP
mindmap
root((Design Patterns & LLD))
0 Mindset
1 OOP Foundations
2 Relations UML ladder
3 Design Principles
4 SOLID
5 Creational
how objects are born
6 Structural
how objects are wired
7 Behavioral
how objects talk / change
8 Decision tree
9 LLD interview playbook
Enter fullscreen mode Exit fullscreen mode
0. Mindset
Trigger Q: Pattern vs algorithm?
Answer: Algorithm = recipe (exact steps). Pattern = blueprint (shape of solution; code differs per app).
Why learn: toolkit of proven designs + shared vocabulary (“use a Strategy here”).
Levels of reuse (Gamma):
flowchart LR
Classes["Classes / libraries"] --> Patterns["Design patterns"]
Patterns --> Frameworks["Frameworks"]
Enter fullscreen mode Exit fullscreen mode
Good design aims for: code reuse + extensibility (change is constant) without rigid coupling.
1. OOP Foundations
1.1 Objects & classes
Hook: Class = blueprint; object = instance (Oscar the Cat).
State = fields; behavior = methods; members = both.
1.2 Four pillars
Pillar One-liner Go feel Abstraction Model only what the context needs (flight sim Airplane ≠ booking Airplane) types that omit irrelevant detail Encapsulation Hide internals; expose a small public surface unexported fields + methods Inheritance Build new types on existing ones embedding (limited; prefer interfaces) Polymorphism Same call, different runtime behavior interface satisfaction// Polymorphism via interface — Go's native "program to an interface"
type Flyer interface {
Fly(origin, dest string, passengers int) error
}
type Airport struct{}
func (a Airport) Depart(f Flyer) error {
return f.Fly("DEL", "BLR", 180) // works for Airplane, Helicopter, …
}
Enter fullscreen mode Exit fullscreen mode
1.3 Relations between objects (strength ladder)
flowchart LR
Dep[Dependency] --> Assoc[Association]
Assoc --> Agg[Aggregation]
Agg --> Comp[Composition]
Dep -.->|"weak → strong"| Comp
Enter fullscreen mode Exit fullscreen mode
flowchart TB
subgraph Type relations
Inh[Inheritance — is-a]
Impl[Implementation — can / fulfills contract]
end
Enter fullscreen mode Exit fullscreen mode
Relation Meaning Memory Dependency Uses briefly (param/local); change may break you “borrows” Association Long-lived link (field / always reachable) “knows” Aggregation Whole–part; parts can outlive whole “has (shared)” Composition Whole owns part lifecycle “owns” Inheritance Is-a; reuses interface + impl “is” Implementation Fulfills a contract “can”2. Design Principles (pre-SOLID)
Encapsulate What Varies
Q: Where will change hurt most?
Do: Pull varying logic into its own method/type so the stable core doesn’t thrash.
Hook: Ship compartments — a mine floods one bay, not the hull.
func (o Order) Total() float64 {
sum := 0.0
for _, li := range o.Items {
sum += li.Price * float64(li.Qty)
}
return sum * (1 + TaxRate(o.Country)) // variation isolated
}
func TaxRate(country string) float64 {
switch country {
case "US":
return 0.07
case "EU":
return 0.20
default:
return 0
}
}
Enter fullscreen mode Exit fullscreen mode
Program to an Interface, not an Implementation
Q: Can I swap the collaborator without editing callers?
Do: Depend on the methods you need, not the concrete type.
type Employee interface {
DoWork()
}
type Company struct{}
func (c Company) RunDay(staff []Employee) {
for _, e := range staff {
e.DoWork() // not *Designer, *Dev — the interface
}
}
Enter fullscreen mode Exit fullscreen mode
Favor Composition Over Inheritance
Q: Am I multiplying subclasses across dimensions (engine × cargo × nav)?
Do: “Has-a” + delegate. Runtime-swappable behaviors.
Trap: Inheritance = one dimension; multi-dimension → combinatorial explosion.
type Engine interface{ Torque() float64 }
type Navigator interface{ Route(to string) []string }
type Vehicle struct {
Engine
Navigator
}
func (v Vehicle) Drive(to string) {
_ = v.Torque()
_ = v.Route(to)
}
Enter fullscreen mode Exit fullscreen mode
3. SOLID
Letter Rule Recall phrase S One reason to change “one job, one class” O Open for extension, closed for modification “add types, don’t edit old ones” L Subtypes must be substitutable “don’t surprise the caller” I No fat interfaces “don’t force unused methods” D High-level depends on abstractions “details plug into policy”SRP
// BAD: Employee manages data AND prints timesheets
// GOOD:
type Employee struct{ ID, Name string }
type TimesheetReporter struct{}
func (TimesheetReporter) Print(e Employee) { /* format may change alone */ }
Enter fullscreen mode Exit fullscreen mode
OCP (+ Strategy)
type Shipping interface{ Cost(order Order) float64 }
type Order struct{ Shipping Shipping }
func (o Order) ShippingCost() float64 { return o.Shipping.Cost(o) }
// new shipping = new type; Order untouched
Enter fullscreen mode Exit fullscreen mode
LSP checklist (Go interfaces)
- Don’t strengthen preconditions / weaken postconditions in implementations
- Don’t throw unexpected errors the contract didn’t advertise
- No “is this the concrete type?” branches that break substitution
ISP
// BAD: CloudProvider with 40 methods
// GOOD: split
type BlobStore interface{ Put(key string, b []byte) error }
type Queue interface{ Publish(topic string, msg []byte) error }
Enter fullscreen mode Exit fullscreen mode
DIP
type ReportStore interface {
Load(id string) (Report, error)
Save(Report) error
}
type BudgetReport struct{ Store ReportStore } // high-level depends on interface
// PostgresStore / FileStore implement ReportStore — details depend on abstraction
Enter fullscreen mode Exit fullscreen mode
5. CREATIONAL — “how are objects born?”
mindmap
root((Creational))
Factory Method
subclass decides product
Abstract Factory
families of products
Builder
step-by-step complex object
Prototype
clone existing instance
Singleton
one shared instance
Enter fullscreen mode Exit fullscreen mode
Factory Method
AKA: Virtual Constructor
Trigger Q: I know the interface of a product, but not which concrete type until a subclass/config decides?
One-liner: Creator defines CreateProduct(); subclasses return concrete products.
Hook: Logistics app — Transport is Truck or Ship; Logistics.CreateTransport() deferred to RoadLogistics / SeaLogistics.
When: framework/library hooks; parallel product hierarchies; replace new Concrete sprinkled everywhere.
type Button interface{ Render() }
type Dialog interface {
CreateButton() Button // factory method
Render()
}
type WindowsButton struct{}
func (WindowsButton) Render() { /* native win btn */ }
type WindowsDialog struct{}
func (WindowsDialog) CreateButton() Button { return WindowsButton{} }
func (d WindowsDialog) Render() {
btn := d.CreateButton()
btn.Render()
}
Enter fullscreen mode Exit fullscreen mode
Trap: Not the same as Abstract Factory (one product vs families).
Relates: often grows into Abstract Factory; pairs with Template Method; Iterator may use it for iterators.
Abstract Factory
Trigger Q: Need families of related products (WinButton+WinCheckbox) and must keep them consistent?
One-liner: Interface of factory methods for each product in a family; concrete factories produce one family.
Hook: Cross-platform UI kit — GUIFactory → WinFactory / MacFactory.
type Button interface{ Paint() }
type Checkbox interface{ Paint() }
type GUIFactory interface {
CreateButton() Button
CreateCheckbox() Checkbox
}
type WinFactory struct{}
func (WinFactory) CreateButton() Button { return WinButton{} }
func (WinFactory) CreateCheckbox() Checkbox { return WinCheckbox{} }
func Application(f GUIFactory) {
f.CreateButton().Paint()
f.CreateCheckbox().Paint()
}
Enter fullscreen mode Exit fullscreen mode
Trap: Adding a new product type forces changing the factory interface (and all factories).
Builder
Trigger Q: Constructor has 10+ params / many optional steps / same process, different representations?
One-liner: Build step-by-step; director can reuse the recipe; get product at the end.
Hook: House builder — walls, doors, roof; same steps → wooden or stone house.
type House struct{ Walls, Doors, Roof string }
type HouseBuilder interface {
BuildWalls()
BuildDoors()
BuildRoof()
GetHouse() House
}
type WoodBuilder struct{ h House }
func (b *WoodBuilder) BuildWalls() { b.h.Walls = "wood" }
func (b *WoodBuilder) BuildDoors() { b.h.Doors = "wood" }
func (b *WoodBuilder) BuildRoof() { b.h.Roof = "wood" }
func (b *WoodBuilder) GetHouse() House { return b.h }
type Director struct{}
func (Director) Construct(b HouseBuilder) House {
b.BuildWalls(); b.BuildDoors(); b.BuildRoof()
return b.GetHouse()
}
Enter fullscreen mode Exit fullscreen mode
Go tip: fluent setters (WithX() *Builder) are an idiomatic Builder variant.
Trap: Overkill for simple structs — use functional options for light cases.
Prototype
Trigger Q: Creating from scratch is expensive / I don’t want to depend on concrete classes to copy?
One-liner: Clone existing objects via a common Clone() contract.
Hook: Cell mitosis; shape editor duplicate.
type Shape interface {
Clone() Shape
Draw()
}
type Circle struct {
X, Y, R int
Color string
}
func (c Circle) Clone() Shape {
cp := c // shallow copy; deep-copy slices/maps if needed
return &cp
}
func (c Circle) Draw() { /* … */ }
Enter fullscreen mode Exit fullscreen mode
Trap: Deep vs shallow copy bugs with nested references.
Singleton
Trigger Q: Exactly one instance + global access (config, logger, connection pool)?
One-liner: Ensure one instance; provide a single access point.
Hook: Government / one president.
package config
import "sync"
type Config struct{ DSN string }
var (
once sync.Once
inst *Config
)
func Get() *Config {
once.Do(func() { inst = &Config{DSN: "postgres://…"} })
return inst
}
Enter fullscreen mode Exit fullscreen mode
Trap: Hidden global state; hard to test — prefer DI; if needed, use sync.Once (thread-safe).
Pros: controlled access, lazy init. Cons: violates SRP often; masks dependencies.
6. STRUCTURAL — “how are objects wired?”
mindmap
root((Structural))
Adapter
make incompatible APIs work
Bridge
split abstraction from implementation
Composite
tree of part / whole
Decorator
wrap to add behavior
Facade
simplify a subsystem
Flyweight
share intrinsic state RAM
Proxy
stand-in controlling access
Enter fullscreen mode Exit fullscreen mode
Adapter
AKA: Wrapper
Trigger Q: Third-party / legacy API shape ≠ what my code expects?
One-liner: Translate one interface into another.
Hook: Power plug adapter.
type JSONAnalytics interface {
AnalyzeJSON(data []byte) (string, error)
}
// legacy
type XMLService struct{}
func (XMLService) AnalyzeXML(xml string) string { return "ok" }
type XMLToJSONAdapter struct{ Inner XMLService }
func (a XMLToJSONAdapter) AnalyzeJSON(data []byte) (string, error) {
xml := jsonToXML(data) // conversion
return a.Inner.AnalyzeXML(xml), nil
}
Enter fullscreen mode Exit fullscreen mode
Confusion: Adapter changes interface; Decorator keeps interface & adds behavior; Facade simplifies a subsystem.
Bridge
Trigger Q: Two independent dimensions both need to vary (shape × renderer; remote × device)?
One-liner: Split into Abstraction + Implementation hierarchies linked by composition.
Hook: Remote control (abstraction) ↔ Device (TV/Radio implementation).
type Device interface {
IsOn() bool
On(); Off()
SetVolume(int)
}
type Remote struct{ Dev Device }
func (r *Remote) Toggle() {
if r.Dev.IsOn() {
r.Dev.Off()
} else {
r.Dev.On()
}
}
type AdvancedRemote struct{ Remote }
func (r *AdvancedRemote) Mute() { r.Dev.SetVolume(0) }
Enter fullscreen mode Exit fullscreen mode
Trap: Looks like Strategy; Bridge is about structural decoupling of two hierarchies long-term.
Composite
Trigger Q: Tree of objects; clients should treat leaf and group the same?
One-liner: Uniform component interface for leaves and composites.
Hook: File system / org chart / nested boxes in a graphics editor.
type Graphic interface {
Draw()
Move(dx, dy int)
}
type Dot struct{ X, Y int }
func (d *Dot) Draw() { /* point */ }
func (d *Dot) Move(dx, dy int) { d.X += dx; d.Y += dy }
type Compound struct{ Children []Graphic }
func (c *Compound) Draw() {
for _, ch := range c.Children {
ch.Draw()
}
}
func (c *Compound) Move(dx, dy int) {
for _, ch := range c.Children {
ch.Move(dx, dy)
}
}
Enter fullscreen mode Exit fullscreen mode
Decorator
AKA: Wrapper
Trigger Q: Add responsibilities at runtime without exploding subclasses?
One-liner: Stack wrappers that share the component interface.
Hook: Wearing clothes — layers.
type Notifier interface{ Send(msg string) }
type EmailNotifier struct{}
func (EmailNotifier) Send(msg string) { /* email */ }
type SMSDecorator struct{ Inner Notifier }
func (d SMSDecorator) Send(msg string) {
d.Inner.Send(msg)
/* also SMS */
}
type SlackDecorator struct{ Inner Notifier }
func (d SlackDecorator) Send(msg string) {
d.Inner.Send(msg)
/* also Slack */
}
// usage: SlackDecorator{SMSDecorator{EmailNotifier{}}}
Enter fullscreen mode Exit fullscreen mode
Confusion pair: Decorator = add behavior, same interface. Proxy = control access (lazy, auth, remote). Adapter = change interface.
Facade
Trigger Q: Client talks to a messy subsystem of many classes?
One-liner: One simple entry API over a complex subsystem.
Hook: Ordering pizza by phone — one number, kitchen chaos hidden.
type VideoConverter struct {
// holds ffmpeg, codec, bitrate helpers…
}
func (VideoConverter) Convert(filename, format string) string {
// orchestrate: decode → filter → encode → write
return "output." + format
}
Enter fullscreen mode Exit fullscreen mode
Trap: Don’t let Facade become a god object — keep it a thin orchestrator.
Flyweight
Trigger Q: Millions of similar objects; RAM blown by duplicated immutable data?
One-liner: Share intrinsic (immutable) state; pass extrinsic state in methods.
Hook: Forest of trees — shared TreeType (texture/color), many Tree positions.
type TreeType struct{ Name, Color, Texture string } // intrinsic, shared
type TreeTypeFactory struct{ cache map[string]*TreeType }
func (f *TreeTypeFactory) Get(name, color, texture string) *TreeType {
key := name + color + texture
if t, ok := f.cache[key]; ok {
return t
}
t := &TreeType{name, color, texture}
f.cache[key] = t
return t
}
type Tree struct {
X, Y int
Type *TreeType // flyweight
}
func (t Tree) Draw() { /* use t.X,t.Y + shared Type */ }
Enter fullscreen mode Exit fullscreen mode
Proxy
Trigger Q: Need lazy load, access control, logging, caching, or remote stub in front of a real object?
One-liner: Same interface as the real subject; proxy delegates after extra work.
Hook: Credit card as proxy for bank account.
type Image interface{ Display() }
type RealImage struct{ Path string }
func (r *RealImage) Display() { /* heavy load from disk */ }
type ImageProxy struct {
Path string
real *RealImage
}
func (p *ImageProxy) Display() {
if p.real == nil {
p.real = &RealImage{Path: p.Path} // lazy
}
// optional: auth / cache / log
p.real.Display()
}
Enter fullscreen mode Exit fullscreen mode
Types: virtual (lazy), protection (ACL), remote, logging/smart reference.
7. BEHAVIORAL — “how do objects talk / change?”
mindmap
root((Behavioral))
Chain of Responsibility
pass request along handlers
Command
request as object
Iterator
traverse without exposing structure
Mediator
hub for colleague communication
Memento
snapshot / undo
Observer
pub-sub
State
behavior by internal state
Strategy
swap algorithms
Template Method
algorithm skeleton + hooks
Visitor
externalize operations on a structure
Enter fullscreen mode Exit fullscreen mode
Chain of Responsibility
AKA: CoR, Chain of Command
Trigger Q: Multiple handlers might process a request; avoid hard-coded if/else towers?
One-liner: Handlers linked in a chain; each handles or forwards.
Hook: Support tiers L1→L2→L3; corporate purchase approvals.
type Handler interface {
SetNext(Handler) Handler
Handle(amount float64) string
}
type base struct{ next Handler }
func (b *base) SetNext(h Handler) Handler { b.next = h; return h }
func (b *base) forward(amount float64) string {
if b.next != nil {
return b.next.Handle(amount)
}
return "unhandled"
}
type Manager struct{ base }
func (m *Manager) Handle(amount float64) string {
if amount <= 1000 {
return "manager approved"
}
return m.forward(amount)
}
type Director struct{ base }
func (d *Director) Handle(amount float64) string {
if amount <= 10000 {
return "director approved"
}
return d.forward(amount)
}
Enter fullscreen mode Exit fullscreen mode
Relates: often with Composite; can use Command as the request object.
Command
Trigger Q: Need undo, queue, log, or schedule operations?
One-liner: Encapsulate a request as an object (receiver + args).
Hook: Restaurant order ticket; remote control buttons.
type Command interface {
Execute()
Undo()
}
type Editor struct{ Text string }
type CopyCommand struct{ Ed *Editor; clipboard *string }
func (c *CopyCommand) Execute() { *c.clipboard = c.Ed.Text }
func (c *CopyCommand) Undo() {}
type PasteCommand struct {
Ed *Editor
clipboard *string
backup string
}
func (c *PasteCommand) Execute() {
c.backup = c.Ed.Text
c.Ed.Text += *c.clipboard
}
func (c *PasteCommand) Undo() { c.Ed.Text = c.backup }
type Button struct{ Cmd Command }
func (b Button) Click() { b.Cmd.Execute() }
Enter fullscreen mode Exit fullscreen mode
Iterator
Trigger Q: Walk a collection without exposing list/tree/graph guts?
One-liner: Iterator interface (HasNext/Next); collection creates it.
Hook: TV remote channel surfing; Go’s iter / for range spirit.
type Iterator[T any] interface {
HasNext() bool
Next() T
}
type SliceIter[T any] struct {
data []T
i int
}
func (it *SliceIter[T]) HasNext() bool { return it.i < len(it.data) }
func (it *SliceIter[T]) Next() T {
v := it.data[it.i]
it.i++
return v
}
Enter fullscreen mode Exit fullscreen mode
Go note: prefer for range / iter.Seq in modern Go; pattern still useful for custom graphs.
Mediator
AKA: Intermediary, Controller
Trigger Q: Many objects tangled in pairwise references (UI dialog chaos)?
One-liner: Colleagues talk only to a mediator; mediator coordinates.
Hook: Airport control tower; chat room.
type Mediator interface {
Notify(sender Colleague, event string)
}
type Colleague interface {
SetMediator(Mediator)
}
type AuthDialog struct { /* form fields */ }
func (d *AuthDialog) Notify(sender Colleague, event string) {
switch event {
case "loginClick":
// validate fields, call auth service…
case "checkboxChanged":
// enable/disable controls…
}
}
type Button struct {
med Mediator
}
func (b *Button) SetMediator(m Mediator) { b.med = m }
func (b *Button) Click() { b.med.Notify(b, "loginClick") }
Enter fullscreen mode Exit fullscreen mode
Confusion: Mediator centralizes communication; Observer broadcasts events (looser).
Memento
AKA: Snapshot
Trigger Q: Undo/rollback without violating encapsulation of originator state?
One-liner: Originator creates opaque memento; caretaker stores history.
Hook: Text editor undo stack; game save checkpoint.
type Memento struct{ state string } // opaque to outsiders in stricter designs
type Editor struct{ text string }
func (e *Editor) Type(s string) { e.text += s }
func (e *Editor) Save() Memento { return Memento{e.text} }
func (e *Editor) Restore(m Memento) { e.text = m.state }
type History struct{ stack []Memento }
func (h *History) Push(m Memento) { h.stack = append(h.stack, m) }
func (h *History) Pop() (Memento, bool) {
if len(h.stack) == 0 {
return Memento{}, false
}
m := h.stack[len(h.stack)-1]
h.stack = h.stack[:len(h.stack)-1]
return m, true
}
Enter fullscreen mode Exit fullscreen mode
Observer
AKA: Event-Subscriber, Listener
Trigger Q: Many objects must react when one object’s state changes?
One-liner: Subject maintains subscribers; notifies on change.
Hook: YouTube subscriptions; newspaper subscriptions.
type Observer interface{ Update(temp float64) }
type Subject interface {
Attach(Observer)
Detach(Observer)
Notify()
}
type WeatherStation struct {
temp float64
obs []Observer
}
func (w *WeatherStation) Attach(o Observer) { w.obs = append(w.obs, o) }
func (w *WeatherStation) SetTemp(t float64) {
w.temp = t
w.Notify()
}
func (w *WeatherStation) Notify() {
for _, o := range w.obs {
o.Update(w.temp)
}
}
type PhoneDisplay struct{}
func (PhoneDisplay) Update(temp float64) { /* UI */ }
Enter fullscreen mode Exit fullscreen mode
Go tip: channels / event buses are common idioms; same pub-sub idea.
State
Trigger Q: Object behavior changes drastically with internal state (and you have state×method switch hell)?
One-liner: Delegate behavior to state objects; transitions swap the state.
Hook: Vending machine; document Draft→Moderation→Published.
type State interface {
Publish(*Document)
}
type Document struct {
state State
}
func (d *Document) SetState(s State) { d.state = s }
func (d *Document) Publish() { d.state.Publish(d) }
type Draft struct{}
func (Draft) Publish(d *Document) { d.SetState(Moderation{}) }
type Moderation struct{}
func (Moderation) Publish(d *Document) { d.SetState(Published{}) }
type Published struct{}
func (Published) Publish(*Document) { /* already published */ }
Enter fullscreen mode Exit fullscreen mode
Confusion pair: State ≈ Strategy structurally; State is aware of transitions / tied to context lifecycle; Strategy is usually injected & unaware of siblings.
Strategy
Trigger Q: Family of interchangeable algorithms selected at runtime?
One-liner: Context holds a Strategy interface; swap implementations.
Hook: Navigation — driving / walking / public transport routes; payment methods.
type RouteStrategy interface {
BuildRoute(a, b string) []string
}
type Navigator struct{ Strategy RouteStrategy }
func (n Navigator) Route(a, b string) []string { return n.Strategy.BuildRoute(a, b) }
type RoadStrategy struct{}
func (RoadStrategy) BuildRoute(a, b string) []string { return []string{a, "highway", b} }
type WalkStrategy struct{}
func (WalkStrategy) BuildRoute(a, b string) []string { return []string{a, "path", b} }
Enter fullscreen mode Exit fullscreen mode
Relates: OCP’s poster child; similar to Bridge but focused on algorithms, not long-lived dual hierarchies.
Template Method
Trigger Q: Same algorithm skeleton; only some steps differ by subtype?
One-liner: Abstract steps in a fixed-order method; override hooks.
Hook: Data miner — open → extract → parse → analyze → close.
type DataMiner interface {
Open()
Extract() []byte
Parse([]byte) any
Analyze(any)
Close()
}
func Mine(m DataMiner) { // template method as function (Go-friendly)
m.Open()
defer m.Close()
raw := m.Extract()
data := m.Parse(raw)
m.Analyze(data)
}
type PDFMiner struct{}
func (PDFMiner) Open() {}
func (PDFMiner) Extract() []byte { return nil }
func (PDFMiner) Parse([]byte) any { return nil }
func (PDFMiner) Analyze(any) {}
func (PDFMiner) Close() {}
Enter fullscreen mode Exit fullscreen mode
Relates: Factory Method is often one step inside a Template Method.
Trap: Heavy inheritance; in Go prefer function template + interface hooks (as above).
Visitor
Trigger Q: Many unrelated operations on an object structure, and you can’t keep stuffing methods into element classes?
One-liner: External visitor with VisitConcreteA/B; elements Accept(v).
Hook: Insurance company agent visiting buildings; export/XML/JSON ops on a document AST.
type Shape interface {
Accept(Visitor)
}
type Visitor interface {
VisitDot(*Dot)
VisitCircle(*Circle)
}
type Dot struct{ X, Y int }
func (d *Dot) Accept(v Visitor) { v.VisitDot(d) }
type Circle struct{ X, Y, R int }
func (c *Circle) Accept(v Visitor) { v.VisitCircle(c) }
type XMLExportVisitor struct{}
func (XMLExportVisitor) VisitDot(d *Dot) { /* export dot */ }
func (XMLExportVisitor) VisitCircle(c *Circle) { /* export circle */ }
Enter fullscreen mode Exit fullscreen mode
Trap: Adding new element types is painful (must update Visitor). Best when element set is stable and operations grow.
8. Decision tree (interview speed)
flowchart TD
Start{What do you need?}
Start --> Create[CREATE]
Start --> Structure[STRUCTURE / wrap]
Start --> Behave[BEHAVIOR / communication]
Create --> C1[one of several products] --> FM[Factory Method]
Create --> C2[family of products] --> AF[Abstract Factory]
Create --> C3[complex step build] --> B[Builder]
Create --> C4[copy existing] --> P[Prototype]
Create --> C5[exactly one] --> S[Singleton — last resort]
Structure --> S1[incompatible API] --> Ad[Adapter]
Structure --> S2[two varying hierarchies] --> Br[Bridge]
Structure --> S3[tree part/whole] --> Co[Composite]
Structure --> S4[add behavior same API] --> De[Decorator]
Structure --> S5[simplify subsystem] --> Fa[Facade]
Structure --> S6[share RAM state] --> Fl[Flyweight]
Structure --> S7[control access / lazy / remote] --> Pr[Proxy]
Behave --> B1[pass along handlers] --> Ch[Chain of Responsibility]
Behave --> B2[undo / queue / log action] --> Cm[Command]
Behave --> B3[traverse collection] --> It[Iterator]
Behave --> B4[untangle many-to-many talk] --> Me[Mediator]
Behave --> B5[snapshot / undo state] --> Mm[Memento]
Behave --> B6[notify many listeners] --> Ob[Observer]
Behave --> B7[behavior by state machine] --> St[State]
Behave --> B8[swap algorithm] --> Sy[Strategy]
Behave --> B9[shared algorithm skeleton] --> Tm[Template Method]
Behave --> B10[add ops to stable structure] --> Vi[Visitor]
Enter fullscreen mode Exit fullscreen mode
9. Confusion pairs (drill these)
Pair Difference Adapter vs Decorator vs Proxy change interface / add behavior / control access Adapter vs Facade one object vs whole subsystem simplification Strategy vs State injected algorithm vs self-transitioning states Strategy vs Bridge algorithm swap vs dual hierarchy decoupling Mediator vs Observer central coordinator vs distributed pub-sub Chain vs Decorator stop/forward request vs stack all behaviors Factory Method vs Abstract Factory one product hook vs product families Composite vs Decorator tree of children vs linear wrapper chain Command vs Strategy request object (often undoable) vs replaceable algorithm Template Method vs Strategy inheritance/hooks vs composition/delegate10. LLD practices checklist (from the book → Go habits)
- Find what varies → extract interface / strategy / state.
-
Depend on interfaces → accept
interface{…}at boundaries. - Compose → embed small collaborators; avoid deep type hierarchies.
- SRP → split God structs.
- OCP → add types, don’t edit battle-tested cores.
- LSP → implementations must honor the contract.
-
ISP → small interfaces (
io.Readerstyle). - DIP → high-level packages define interfaces; infra implements.
- Name the pattern in design docs — shared language.
- Don’t pattern-hunt — complexity only when change pressure justifies it.
11. Quick Go idiom map (book OOP → Go)
Book concept Go idiom Abstract class Interface + optional helper funcs Protected members Same package / unexported Multiple inheritance Interface embedding Polymorphism Implicit interface satisfaction Singletonsync.Once or DI container
Observer
interfaces, channels, event bus
Iterator
for range, iter.Seq
Decorator/Proxy
wrapping structs implementing same interface
답글 남기기