01Why design patterns — shared vocabulary for recurring problemsDesign patterns का — पुनरावृत्त समस्यांसाठी सामायिक शब्दसंग्रह★ start here★ इथून सुरू करा▶
Scenario: in a review you say "let's inject a strategy here and wrap the client in a decorator with a circuit breaker" — and the whole team instantly knows the shape of the solution. That's the real value of patterns: names for solutions that recur, so you design and communicate faster and recognize structures already proven in the wild.
परिस्थिती: review मध्ये तुम्ही म्हणता 'इथे एक strategy inject करू आणि client ला circuit breaker असलेल्या decorator मध्ये wrap करू' — आणि संपूर्ण team ला उपायाचा आकार लगेच कळतो. patterns चं खरं मूल्य हेच: पुनरावृत्त होणाऱ्या उपायांना नावं, म्हणजे तुम्ही जलद design आणि संवाद करता आणि सिद्ध झालेल्या रचना ओळखता.
What
काय
Design patterns are named, reusable solutions to problems that keep recurring in software design — a shared vocabulary (from the 1994 "Gang of Four" book and beyond), not copy-paste code.
Design patterns म्हणजे software design मध्ये पुन्हा पुन्हा येणाऱ्या समस्यांचे नावाचे, पुनर्वापर करण्याजोगे उपाय — एक सामायिक शब्दसंग्रह (1994 च्या 'Gang of Four' पुस्तकातून आणि पुढे), copy-paste code नाही.
Why
का
They compress communication ("it's an adapter"), encode hard-won tradeoffs, and help you recognize structures you already see in frameworks — so you don't reinvent (or misuse) them.
ते संवाद संकुचित करतात ('हे adapter आहे'), कष्टाने मिळवलेल्या तडजोडी encode करतात, आणि frameworks मध्ये तुम्ही आधीच पाहता त्या रचना ओळखायला मदत करतात — म्हणून तुम्ही त्या पुन्हा-शोधत (किंवा गैरवापर करत) नाही.
How
कसं
Learn the problem each solves, not just its diagram. Reach for one when you hit its problem — never apply a pattern for its own sake (topic 28).
प्रत्येक कोणती समस्या सोडवतो ते शिका, फक्त आकृती नाही. त्याची समस्या भेटेल तेव्हा एक घ्या — कधीही pattern-साठी-pattern लावू नका (topic 28).
CREATIONAL how objects are CREATED Factory, Builder, Singleton, Pool… STRUCTURAL how objects are COMPOSED Adapter, Decorator, Facade, Proxy… BEHAVIORAL how objects INTERACT Strategy, Observer, Command, State… MODERN app/infra architecture Repository, Pub/Sub, Circuit Breaker a pattern = (recurring problem) → (proven solution) → (its tradeoffs)
// without vocabulary — a long explanation: "make an object that stands in for the real client, and around it wrap something that adds retry, and something that trips after N failures…" // with vocabulary — one sentence a reviewer parses instantly: const client = new CircuitBreaker(new RetryDecorator(new HttpClient(), 3)) // ^ pattern 27 ^ pattern 09 (decorator) ^ the real thing // the NAMES carry the design. that's what patterns buy you.
✅ Do
- Learn the problem each pattern solves and its tradeoffs
- Use patterns to communicate design intent quickly
- Recognize patterns already in your frameworks and lean on them
✅ हे करा
- प्रत्येक pattern कोणती समस्या सोडवतो आणि त्याच्या तडजोडी शिका
- design हेतू जलद संवादायला patterns वापरा
- तुमच्या frameworks मध्ये आधीच असलेले patterns ओळखा आणि त्यांवर झुका
❌ Don't
- Apply a pattern because it's clever — apply it because you have its problem
- Treat the GoF diagrams as mandatory boilerplate to reproduce verbatim
❌ हे टाळा
- pattern हुशार आहे म्हणून लावू नका — त्याची समस्या आहे म्हणून लावा
- GoF आकृत्या हुबेहूब पुन्हा बनवायच्या अनिवार्य boilerplate समजू नका
Optional replacing Null Object). Know them to recognize and communicate — reach for them only when the recurring problem actually shows up.Optional). ओळखायला आणि संवादायला शिका — समस्या खरंच आली तरच एक घ्या.02Factory Method — decide the concrete type in one placeFactory Method — concrete type एका ठिकाणी ठरवा★ core★ गाभा▶
Scenario: your code needs a Logger, but which one — console in dev, JSON-to-stdout in prod, a no-op in tests — depends on config. Scattering new JsonLogger() everywhere hard-codes the choice. A factory method centralizes "which concrete class to build" behind a single call: Logger.create(env).
परिस्थिती: तुमच्या code ला Logger हवा, पण कोणता — dev मध्ये console, prod मध्ये JSON, tests मध्ये no-op — config वर अवलंबून. सगळीकडे new JsonLogger() विखुरणं निवड hardcode करतं. एक factory method 'कोणती concrete class बांधायची' हे एका call मागे केंद्रित करतं: Logger.create(env).
What
काय
Factory Method: a method whose job is to create and return an object, deciding the concrete type internally. Callers ask for the abstraction and get back a ready-made instance.
Factory Method: एक method जिचं काम object बनवून परत करणं, concrete type आत ठरवणं. Callers abstraction मागतात आणि तयार instance मिळतो.
Why
का
It puts the "which class + how to build it" decision in one place, so callers stay decoupled from concrete constructors and construction logic isn't duplicated.
ते 'कोणती class + कशी बांधायची' निर्णय एका ठिकाणी ठेवतं, म्हणून callers concrete constructors पासून decoupled राहतात आणि construction logic नक्कल होत नाही.
How
कसं
Replace scattered new X() with a factory function/method that returns the interface type and picks the implementation (by config, input, or platform).
विखुरलेले new X() एका factory function/method ने बदला जी interface type परत करते आणि implementation निवडते (config, input, किंवा platform नुसार).
callers: const log = makeLogger(env) ← ask for a Logger
│
┌──────┴───────┐ factory decides:
env==='prod'? env==='test'?
│ │
JsonLogger NoopLogger (else) ConsoleLogger
change the selection rule ONCE, in the factory — not at every call siteReact.createElement is a factory: JSX compiles to React.createElement(type, props), which builds the right element object for you — you never new a fiber. document.createElement('div') is the DOM's factory doing the same.React.createElement म्हणजे factory: JSX React.createElement(type, props) मध्ये compile होतं, जे योग्य element object तुमच्यासाठी बांधतं — तुम्ही fiber कधीच new करत नाही. document.createElement('div') म्हणजे DOM चा factory तेच करतो.logging.getLogger() (Python), a DriverManager.getConnection(url) that returns the right JDBC driver, and DocumentBuilderFactory all hide "which concrete class + how to build it" behind one call.logging.getLogger() (Python), योग्य JDBC driver परत करणारा DriverManager.getConnection(url), आणि DocumentBuilderFactory सगळे 'कोणती concrete class + कशी बांधायची' एका call मागे लपवतात.interface Logger { info(msg: string): void; error(msg: string): void }
class ConsoleLogger implements Logger { /* ... */ }
class JsonLogger implements Logger { /* ... */ }
class NoopLogger implements Logger { info(){} error(){} }
function makeLogger(env: string): Logger { // the factory method
if (env === 'production') return new JsonLogger()
if (env === 'test') return new NoopLogger()
return new ConsoleLogger()
}
// callers: const log = makeLogger(process.env.NODE_ENV) — no `new` at call sitesReact.createElement / document.createElement, Python logging.getLogger, Java DocumentBuilderFactory / Calendar.getInstance() / DriverManager.getConnection, AWS SDK S3Client.builder().build(), and ORM createConnection(config). Any createX() / makeX() / getX() that hides which concrete class you get is a factory.React.createElement / document.createElement, Python logging.getLogger, Java DocumentBuilderFactory / Calendar.getInstance() / DriverManager.getConnection, AWS SDK S3Client.builder().build(), आणि ORM createConnection(config). कुठलंही createX() / makeX() / getX() जे तुम्हाला कोणती concrete class मिळते ते लपवतं तो factory.✅ Do
- Centralize "which implementation + how to build it" in a factory
- Return the interface type so callers stay decoupled
- Use it when construction depends on config, input, or platform
✅ हे करा
- 'कोणती implementation + कशी बांधायची' factory मध्ये केंद्रित करा
- callers decoupled ठेवायला interface type परत करा
- construction config, input, किंवा platform वर अवलंबून असेल तेव्हा वापरा
❌ Don't
- Wrap a single trivial
newin a factory for no reason - Let the factory grow into a God object that builds unrelated things
❌ हे टाळा
- एका क्षुल्लक
newला विनाकारण factory मध्ये गुंडाळू नका - factory ला असंबंधित गोष्टी बांधणारा God object होऊ देऊ नका
03Abstract Factory — families of related objectsAbstract Factory — संबंधित objects ची कुटुंबं▶
Scenario: your app targets AWS and GCP. On AWS you need an S3 blob store, SQS queue, and CloudWatch metrics; on GCP, GCS, Pub/Sub, and Stackdriver. You must never mix an S3 store with a Pub/Sub queue. An abstract factory hands you a whole compatible family at once: cloud.blobStore(), cloud.queue(), cloud.metrics() — all AWS, or all GCP.
परिस्थिती: तुमचा app AWS आणि GCP लक्ष्य करतो. AWS वर S3 blob store, SQS queue, CloudWatch metrics लागतात; GCP वर GCS, Pub/Sub, Stackdriver. तुम्ही S3 store ला Pub/Sub queue सोबत कधीच मिसळू नये. एक abstract factory तुम्हाला संपूर्ण सुसंगत कुटुंब एकाच वेळी देतो: सगळे AWS, किंवा सगळे GCP.
What
काय
Abstract Factory: an interface for creating a family of related objects without specifying their concrete classes, so the whole set stays consistent.
Abstract Factory: संबंधित objects च्या कुटुंबाला त्यांच्या concrete classes न सांगता बनवायचं interface, म्हणून संपूर्ण संच सुसंगत राहतो.
Why
का
It guarantees the pieces you get work together (all AWS, or all GCP, or all "dark theme") and lets you swap the entire family by swapping one factory.
ते तुम्हाला मिळणारे तुकडे एकत्र चालतात याची हमी देतं (सगळे AWS, किंवा सगळे GCP) आणि एक factory बदलून संपूर्ण कुटुंब बदलू देतं.
How
कसं
Define a factory interface with several create-methods; implement one factory per family; inject the chosen factory and build all pieces through it.
अनेक create-methods असलेलं factory interface ठरवा; प्रति कुटुंब एक factory राबवा; निवडलेला factory inject करा आणि सगळे तुकडे त्यातून बांधा.
interface CloudFactory { blobStore() queue() metrics() }
┌───────────────────────┴───────────────────────┐
AwsFactory GcpFactory
blobStore → S3 queue → SQS blobStore → GCS
metrics → CloudWatch queue → PubSub, metrics → Stackdriver
inject AwsFactory ⇒ you CAN'T accidentally get an S3 store with a PubSub queueWidgetFactory that makes a matching Button, Checkbox, and Menu for macOS or Windows — so you never render a Windows button next to a macOS checkbox. Same shape as "all-AWS vs all-GCP."WidgetFactory जो macOS किंवा Windows साठी जुळणारा Button, Checkbox, Menu बनवतो — म्हणून तुम्ही Windows button macOS checkbox शेजारी कधीच render करत नाही.ThemeFactory yields a consistent set of colors, spacings, and components for "dark" or "light." One switch changes the entire coordinated family, not thirty individual choices.ThemeFactory 'dark' किंवा 'light' साठी सुसंगत रंग, spacings, आणि components चा संच देतो. एक switch संपूर्ण समन्वित कुटुंब बदलतो, तीस स्वतंत्र निवडी नाही.interface CloudFactory {
blobStore(): BlobStore
queue(): Queue
metrics(): Metrics
}
class AwsFactory implements CloudFactory {
blobStore(){ return new S3Store() }
queue(){ return new SqsQueue() }
metrics(){ return new CloudWatch() }
}
class GcpFactory implements CloudFactory {
blobStore(){ return new GcsStore() }
queue(){ return new PubSubQueue() }
metrics(){ return new Stackdriver() }
}
// wire once: const cloud: CloudFactory = onAws ? new AwsFactory() : new GcpFactory()
// everything built from `cloud` is guaranteed same-familyDbProviderFactory), Java's DocumentBuilderFactory/TransformerFactory, and theming systems. Whenever swapping one setting must swap a coordinated group of implementations together, it's an abstract factory.DbProviderFactory), Java चं DocumentBuilderFactory/TransformerFactory, आणि theming systems. जेव्हा एक setting बदलणं म्हणजे समन्वित गट बदलणं, तेव्हा तो abstract factory.✅ Do
- Use it when several objects must come from the same family
- Swap the entire family by injecting a different factory
- Keep the create-methods cohesive (one real family per factory)
✅ हे करा
- अनेक objects एकाच कुटुंबातून यायला हवेत तेव्हा वापरा
- वेगळा factory inject करून संपूर्ण कुटुंब बदला
- create-methods cohesive ठेवा (प्रति factory एक खरं कुटुंब)
❌ Don't
- Reach for it when you only ever build one kind of object (that's Factory Method)
- Add a factory family for variation that will never have a second member
❌ हे टाळा
- तुम्ही फक्त एकच प्रकारचा object बांधता तेव्हा घेऊ नका (तो Factory Method)
- कधीच दुसरा सदस्य न येणाऱ्या variation साठी factory कुटुंब जोडू नका
secrets()) forces you to edit every factory implementation — so only pay for it when you genuinely have multiple, related products that must stay in lockstep across swappable families.04Builder — assemble complex objects step by stepBuilder — जटिल objects टप्प्याटप्प्याने जुळवा★ very common★ खूप सामान्य▶
Scenario: constructing an HTTP request needs a URL, method, headers, query params, body, timeout, retries — most optional. A constructor with ten positional args (new Request(url, 'POST', null, null, headers, …)) is unreadable and error-prone. A builder assembles it fluently: request().url(u).header('Auth', t).timeout(5000).build().
परिस्थिती: HTTP request बांधायला URL, method, headers, query params, body, timeout, retries लागतात — बहुतेक optional. दहा positional args असलेला constructor अवाचनीय आणि चूक-प्रवण. एक builder ते fluently जुळवतो: request().url(u).header('Auth', t).timeout(5000).build().
What
काय
Builder: separate the step-by-step construction of a complex object from its representation, usually via a fluent chain that ends in build() (which validates invariants).
Builder: जटिल object च्या टप्प्याटप्प्याच्या construction ला त्याच्या representation पासून वेगळं करा, बहुधा fluent chain द्वारे जी build() ने संपते (जो invariants तपासतो).
Why
का
It kills the "telescoping constructor" (many overloads / long positional arg lists), makes optional fields readable, and validates the whole thing once at build() — so you never get a half-configured object.
ते 'telescoping constructor' (अनेक overloads / लांब positional arg याद्या) नष्ट करतं, optional fields वाचनीय करतं, आणि संपूर्ण गोष्ट build() ला एकदा validate करतं — म्हणून तुम्हाला अर्ध-configured object कधीच मिळत नाही.
How
कसं
A builder object accumulates settings via chainable methods (each returns this), then build() constructs and validates the final immutable object.
एक builder object chainable methods द्वारे settings जमवतो (प्रत्येक this परत करतो), मग build() अंतिम immutable object बांधतो आणि validate करतो.
request() ❌ vs telescoping constructor:
.url('/orders') new Request('/orders','POST',null,
.method('POST') headers,null,5000,3,false…)
.header('Auth', token) which null is the body? good luck.
.timeout(5000)
.retries(3)
.build() ← validates here, returns an immutable Requestdb('users').where('active', true).orderBy('name').limit(10) — accumulate a complex SQL statement step by step, then execute. Each call returns the builder so you can keep chaining.db('users').where('active', true).orderBy('name') — एक जटिल SQL statement टप्प्याटप्प्याने जमवतात, मग execute करतात. प्रत्येक call builder परत करतो म्हणून तुम्ही chain करत राहता.Request.Builder, Java's HttpRequest.newBuilder(), and axios/fetch config objects all assemble a request from many optional parts and validate at the end.Request.Builder, Java चा HttpRequest.newBuilder(), आणि axios/fetch config objects सगळे अनेक optional भागांतून request जमवतात आणि शेवटी validate करतात.class RequestBuilder {
private req: any = { method: 'GET', headers: {}, timeout: 30000 }
url(u: string){ this.req.url = u; return this } // each step returns this
method(m: string){ this.req.method = m; return this }
header(k: string, v: string){ this.req.headers[k] = v; return this }
timeout(ms: number){ this.req.timeout = ms; return this }
build(): Request {
if (!this.req.url) throw new Error('url is required') // invariant checked ONCE
return Object.freeze(this.req) // immutable result
}
}
const r = new RequestBuilder().url('/orders').method('POST').header('Auth', tok).build()Request.Builder, Java HttpRequest/StringBuilder/Stream.Builder, query builders (Knex, jOOQ, SQLAlchemy, ActiveRecord's chainable scopes), Kubernetes manifest builders, protobuf message builders, Flutter widget trees, and "test data builders" (aUser().withEmail(...).build()) that keep test setup readable. Lombok's @Builder generates them automatically.Request.Builder, Java HttpRequest/StringBuilder/Stream.Builder, query builders (Knex, jOOQ, SQLAlchemy, ActiveRecord चे chainable scopes), Kubernetes manifest builders, protobuf message builders, Flutter widget trees, आणि 'test data builders' (aUser().withEmail(...).build()). Lombok चं @Builder ते आपोआप निर्माण करतं.✅ Do
- Use it for objects with many optional/complex parts
- Validate all invariants once in
build()and return something immutable - Use builders to make test setup readable
✅ हे करा
- अनेक optional/जटिल भाग असलेल्या objects साठी वापरा
- सगळे invariants
build()मध्ये एकदा validate करा आणि immutable परत करा - test setup वाचनीय करायला builders वापरा
❌ Don't
- Build a builder for a 2-field object — a constructor or object literal is clearer
- Let the builder return a usable object before
build()validates it
❌ हे टाळा
- 2-field object साठी builder बांधू नका — constructor किंवा object literal स्पष्ट
build()validate करण्याआधी builder ला वापरण्याजोगा object परत करू देऊ नका
Request(url, method='POST', timeout=5000) is as readable as a builder with less ceremony. Reach for a full Builder when you need staged construction, validation at the end, an immutable result, or a fluent DSL (like a query builder). For "just a lot of optional fields," a config object is often enough.Request(url, method='POST', timeout=5000) builder इतकंच वाचनीय, कमी सोपस्कारासह. पूर्ण Builder तेव्हा घ्या जेव्हा तुम्हाला staged construction, शेवटी validation, immutable result, किंवा fluent DSL हवं. 'फक्त भरपूर optional fields' साठी config object अनेकदा पुरेसं.05Singleton — one instance, and why it's often a trapSingleton — एक instance, आणि तो अनेकदा सापळा का⚠ use sparingly⚠ जपून वापरा▶
Scenario: you want exactly one database connection pool for the whole app, so you make it a global Singleton. Convenient — until tests can't swap it, two modules fight over its state, and it becomes hidden global coupling. Singleton is the most overused and criticized pattern; know it, and prefer DI (topic 18 in OOP) for most cases.
परिस्थिती: तुम्हाला संपूर्ण app साठी नक्की एक database connection pool हवा, म्हणून तुम्ही तो global Singleton करता. सोयीचं — जोपर्यंत tests तो बदलू शकत नाहीत, दोन modules त्याच्या state वरून भांडतात, आणि तो लपलेलं global coupling होतं. Singleton हा सर्वात गैरवापरलेला आणि टीकाकृत pattern; तो जाणा, आणि बहुतेक वेळा DI ला झुकतं द्या (OOP topic 18).
What
काय
Singleton: ensure a class has exactly one instance and provide a global access point to it.
Singleton: एका class ला नक्की एक instance असल्याची खात्री करा आणि त्याला global access point द्या.
Why
का
Some resources are genuinely single (a process-wide connection pool, a config registry, a metrics client). But the "global access point" part causes hidden coupling and testing pain.
काही resources खरोखर एकच असतात (process-wide connection pool, config registry, metrics client). पण 'global access point' भाग लपलेलं coupling आणि testing त्रास घडवतो.
How
कसं
Prefer injecting a single instance created at your composition root over a global getInstance(). If you must use a true Singleton, keep it stateless-ish and behind an interface.
global getInstance() पेक्षा composition root ला बनवलेला एक instance inject करणं ला झुकतं द्या. खरा Singleton लागलाच, तर तो stateless-ish आणि interface मागे ठेवा.
❌ classic Singleton (global): ✅ single instance via DI:
Db.getInstance().query(...) const db = new Pool(url) // once, at root
│ reachable from ANYWHERE new OrderService(db) // injected
│ hidden dependency, hard to new UserService(db) // shared, explicit
▼ mock in tests, shared mutable tests: new OrderService(fakeDb)
global state = spooky coupling one instance, but dependencies are visiblegetInstance()" part is the global-state problem that makes code hard to test and reason about — which is why DI is usually preferred.getInstance() ने पोहोचता येणारा' भाग म्हणजे global-state समस्या जी code test आणि समजायला कठीण करते — म्हणूनच DI ला झुकतं दिलं जातं.// ❌ classic Singleton — global access point, hard to test
class Db {
private static instance: Db
static getInstance(){ return Db.instance ??= new Db() }
}
Db.getInstance().query('...') // called from anywhere; hidden global dependency
// ✅ create ONE instance at the composition root, inject it (topic: DI)
const pool = new Pool(process.env.DATABASE_URL) // exactly one, made once
const orders = new OrderService(pool) // dependency is explicit + swappable
// tests: new OrderService(fakePool) // trivial — no global to stubRuntime.getRuntime() and many framework globals are cited as cautionary tales; modern DI containers (Spring, .NET, NestJS) provide "singleton scope" that gives you one instance with injection and testability. "Singleton = global state" is a well-known criticism — most style guides say prefer DI.Runtime.getRuntime() आणि अनेक framework globals सावधगिरीच्या कथा; modern DI containers (Spring, .NET, NestJS) 'singleton scope' देतात जो तुम्हाला एक instance आणि injection+testability देतो. 'Singleton = global state' ही सुप्रसिद्ध टीका.✅ Do
- Prefer a single instance created at the root and injected
- Use a DI container's singleton scope if you need one instance
- Keep any true Singleton behind an interface so tests can substitute it
✅ हे करा
- root ला बनवलेला आणि injected एक instance ला झुकतं द्या
- एक instance हवा असेल तर DI container चा singleton scope वापरा
- कुठलाही खरा Singleton interface मागे ठेवा म्हणजे tests बदलू शकतात
❌ Don't
- Sprinkle
getInstance()calls through business logic (hidden global coupling) - Store mutable request/user state on a Singleton (thread-safety + test hell)
❌ हे टाळा
- business logic मध्ये
getInstance()calls विखरू नका (लपलेलं global coupling) - Singleton वर mutable request/user state ठेवू नका (thread-safety + test नरक)
getInstance() can build two instances under a race. If you take one lesson from this card: the value you usually want is "exactly one instance," and you get that far more safely by constructing it once and injecting it than by making it globally reachable. Reserve true Singletons for genuinely process-global, stateless services.getInstance() race मध्ये दोन instances बांधू शकतं. एक धडा घ्याल तर: तुम्हाला हवं ते value अनेकदा 'नक्की एक instance' असतं, आणि ते global-पोहोचण्याजोगं करण्यापेक्षा एकदा बांधून inject करून खूप सुरक्षितपणे मिळतं.06Prototype — create by cloning an existing objectPrototype — अस्तित्वात असलेल्या object ला clone करून बनवा▶
Scenario: you have a fully-configured "default" object — a game enemy with 40 tuned fields, or a base API-request template with auth + retries set — and you need many near-identical copies with small tweaks. Rebuilding each from scratch is wasteful and error-prone. The Prototype pattern clones a preconfigured instance and adjusts the differences.
परिस्थिती: तुमच्याकडे पूर्ण-configured 'default' object आहे — 40 tuned fields असलेला game enemy, किंवा auth + retries set असलेला base API-request template — आणि तुम्हाला लहान बदलांसह अनेक जवळपास-एकसारख्या प्रती हव्यात. प्रत्येक शून्यातून पुन्हा बांधणं वाया आणि चूक-प्रवण. Prototype pattern preconfigured instance clone करतो आणि फरक जुळवतो.
What
काय
Prototype: create new objects by copying an existing "prototype" instance rather than constructing from scratch, then tweak what differs.
Prototype: शून्यातून बांधण्याऐवजी अस्तित्वात असलेल्या 'prototype' instance ला copy करून नवीन objects बनवा, मग जे वेगळं ते जुळवा.
Why
का
When construction is expensive or configuration is elaborate, cloning a known-good instance is faster and avoids re-specifying dozens of fields correctly each time.
construction महाग असेल किंवा configuration विस्तृत असेल, तेव्हा known-good instance clone करणं जलद आणि प्रत्येक वेळी डझनभर fields बरोबर पुन्हा-सांगणं टाळतं.
How
कसं
Give objects a clone() (or use structuredClone/Object.create), keep a registry of prototypes, and clone-then-modify to produce variants.
objects ला clone() द्या (किंवा structuredClone/Object.create वापरा), prototypes चं registry ठेवा, आणि variants बनवायला clone-मग-बदला करा.
prototype: baseRequest { auth, retries:3, timeout:5s, headers{…} }
│ clone()
▼
variant A = clone(baseRequest); A.url = '/orders'
variant B = clone(baseRequest); B.url = '/users'; B.timeout = 1s
40 tuned fields copied for free; you only set what's DIFFERENTObject.create(proto) makes a new object backed by an existing one. Prototype is baked into the language, not bolted on.Object.create(proto) अस्तित्वात असलेल्यावर आधारित नवीन object बनवतो. Prototype language मध्ये बेक केलेलं, बोल्ट-ऑन नाही.git branch off a known-good commit — start from a configured baseline instead of a blank page.git branch — कोऱ्या पानाऐवजी configured baseline पासून सुरू करा.const baseRequest = { // a fully-configured prototype
headers: { Authorization: token }, retries: 3, timeout: 5000, method: 'GET',
}
// structuredClone gives a deep copy — the base is untouched
const getOrders = { ...structuredClone(baseRequest), url: '/orders' }
const getUser = { ...structuredClone(baseRequest), url: '/users', timeout: 1000 }
// only the DIFFERENCES are specified; auth/retries/etc. come from the prototype
// classic OO form: objects expose clone()
class Enemy { clone(): Enemy { return structuredClone(this) } }Object.create/structuredClone, Java's Cloneable/clone(), C#'s MemberwiseClone/ICloneable, prototype registries in game engines (spawn enemies from a template), deep-copying config templates, and copy-on-write data structures. Kubernetes/IaC "copy this manifest and change the name" is the same instinct.Object.create/structuredClone, Java चं Cloneable/clone(), C# चं MemberwiseClone/ICloneable, game engines मधली prototype registries, config templates deep-copy करणं, आणि copy-on-write data structures. Kubernetes/IaC 'हा manifest copy करून नाव बदल' तीच वृत्ती.✅ Do
- Clone when construction is expensive or configuration is elaborate
- Keep a registry of prototype instances to copy from
- Be explicit about deep vs shallow copy
✅ हे करा
- construction महाग किंवा configuration विस्तृत असेल तेव्हा clone करा
- copy करायला prototype instances चं registry ठेवा
- deep vs shallow copy बद्दल स्पष्ट राहा
❌ Don't
- Clone when a simple constructor call is cheaper and clearer
- Shallow-copy an object with nested mutable state (aliasing bugs)
❌ हे टाळा
- साधा constructor call स्वस्त आणि स्पष्ट असेल तेव्हा clone करू नका
- nested mutable state असलेला object shallow-copy करू नका (aliasing bugs)
copy.headers also mutates the prototype's headers, corrupting every future clone. Use structuredClone/deep copy for anything with nested mutable state, and remember that clones skip the constructor, so any invariant checks or side effects in your constructor won't run — validate cloned objects if that matters.copy.headers mutate केल्याने prototype चे headers ही mutate होतात, प्रत्येक भावी clone बिघडवून. nested mutable state साठी structuredClone/deep copy वापरा, आणि लक्षात ठेवा clones constructor वगळतात, म्हणून constructor मधले invariant checks चालणार नाहीत.07Object Pool — reuse expensive objectsObject Pool — महाग objects पुनर्वापरा▶
Scenario: opening a database connection takes 50–100ms (TCP + TLS + auth). If every query opened a fresh one, your API would crawl and the DB would drown in connection churn. An object pool keeps a set of ready connections and lends them out — the single most important reason your app can serve thousands of requests per second.
परिस्थिती: database connection उघडायला 50–100ms लागतात (TCP + TLS + auth). प्रत्येक query ने नवीन उघडली, तर तुमचा API रांगेल आणि DB connection churn मध्ये बुडेल. एक object pool तयार connections चा संच ठेवतो आणि उसने देतो — तुमचा app हजारो requests-per-second सेवू शकण्याचं सर्वात महत्त्वाचं कारण.
What
काय
Object Pool: keep a set of reusable, expensive-to-create objects (connections, threads, buffers) and lend them to clients, who return them instead of destroying them.
Object Pool: पुनर्वापर करण्याजोगे, बांधायला महाग objects (connections, threads, buffers) चा संच ठेवा आणि clients ला उसने द्या, जे ते नष्ट करण्याऐवजी परत करतात.
Why
का
It amortizes expensive setup (TCP/TLS handshakes, thread creation) across many uses and caps resource usage (max connections) so you don't exhaust the DB or OS.
ते महाग setup (TCP/TLS handshakes, thread creation) अनेक वापरांत विभागतं आणि resource वापर मर्यादित करतं (max connections) म्हणून तुम्ही DB किंवा OS संपवत नाही.
How
कसं
A pool tracks idle + in-use objects; acquire() hands out (or creates/blocks up to a max), release() returns it for reuse. Health-check on return.
pool idle + in-use objects track करतो; acquire() देतो (किंवा max पर्यंत बनवतो/block करतो), release() पुनर्वापरासाठी परत करतो. परत आल्यावर health-check.
┌──── Connection Pool (max 20) ────┐ │ idle: [c1 c2 c3] in-use: [c4…] │ └──────────────────────────────────┘ acquire() ─► hands out c1 (or creates if under max, or WAITS if maxed) ...query... release(c1) ─► c1 goes back to idle (health-checked), ready for the next caller 50ms handshake paid ONCE per connection, not once per query
class ConnectionPool {
private idle: Conn[] = []
private inUse = 0
constructor(private max: number, private make: () => Conn) {}
async acquire(): Promise<Conn> {
if (this.idle.length) return this.idle.pop()! // reuse a warm one
if (this.inUse < this.max) { this.inUse++; return this.make() } // or create
return this.waitForRelease() // or WAIT (backpressure)
}
release(c: Conn){ if (c.healthy()) this.idle.push(c); else this.inUse-- } // return, checked
}
// the 50ms TCP+TLS+auth handshake is amortized across thousands of queriesPool, ActiveRecord/SQLAlchemy pools), HTTP keep-alive connection pools (OkHttp, undici), thread pools/executors (Java ExecutorService, .NET ThreadPool), byte-buffer pools (Netty), and game-engine object pools for bullets/particles. It's foundational to the scaling story in the Distributed Systems playbook.Pool, ActiveRecord/SQLAlchemy pools), HTTP keep-alive connection pools (OkHttp, undici), thread pools/executors (Java ExecutorService, .NET ThreadPool), byte-buffer pools (Netty), आणि game-engine object pools. Distributed Systems playbook मधल्या scaling कथेचा पाया.✅ Do
- Pool genuinely expensive resources (connections, threads, big buffers)
- Cap the pool size to protect the downstream resource; apply backpressure when maxed
- Health-check objects on return and reset their state before reuse
✅ हे करा
- खरोखर महाग resources pool करा (connections, threads, मोठे buffers)
- downstream resource रक्षायला pool आकार मर्यादित करा; maxed झाल्यावर backpressure लावा
- परत आल्यावर objects health-check करा आणि पुनर्वापराआधी state reset करा
❌ Don't
- Pool cheap objects — modern allocators/GC make that a pessimization
- Forget to
release()(a leak that exhausts the pool and hangs the app)
❌ हे टाळा
- स्वस्त objects pool करू नका — modern allocators/GC ते pessimization करतात
release()विसरू नका (pool संपवणारा आणि app hang करणारा leak)
release() (e.g. on an error path without try/finally) and the pool drains until every request blocks forever waiting for a connection — a nasty production hang. And a returned object carries its old state: a DB connection left mid-transaction, a buffer with stale bytes. Always reset/health-check on return, always release in a finally, and size the pool deliberately — too small starves throughput, too large overwhelms the DB (topic: Distributed Systems). Don't pool trivially cheap objects; you'll add complexity and often lose to the GC.release() विसरा (उदा. try/finally शिवाय error path वर) आणि pool आटतो जोपर्यंत प्रत्येक request connection ची वाट पाहत block होत नाही — एक वाईट production hang. आणि परत आलेला object त्याचं जुनं state वाहतो: mid-transaction सोडलेली DB connection. परत आल्यावर नेहमी reset/health-check करा, नेहमी finally मध्ये release करा, आणि pool मुद्दाम आकारा — लहान throughput उपाशी ठेवतं, मोठं DB बुडवतं.08Adapter — make an incompatible interface fitAdapter — विसंगत interface जुळवा★ core★ गाभा▶
Scenario: your app talks to a PaymentGateway interface (topic: abstraction), but Stripe's SDK has its own method names and shapes (stripe.charges.create({amount, source})). An adapter is the thin translation layer that makes Stripe's API look like your PaymentGateway — without changing either side.
परिस्थिती: तुमचा app PaymentGateway interface शी बोलतो, पण Stripe चा SDK त्याची स्वतःची method-नावं आणि आकार वापरतो (stripe.charges.create({amount, source})). एक adapter म्हणजे तो पातळ भाषांतर-स्तर जो Stripe चा API तुमच्या PaymentGateway सारखा दिसवतो — दोन्ही बाजू न बदलता.
What
काय
Adapter: wrap an object with an incompatible interface in a class that translates calls to the interface your code expects. (a.k.a. Wrapper.)
Adapter: विसंगत interface असलेला object एका class मध्ये गुंडाळा जी calls तुमच्या अपेक्षित interface मध्ये भाषांतरित करते. (उर्फ Wrapper.)
Why
का
It lets you integrate third-party or legacy code that you can't change, and keeps vendor-specific shapes out of your business logic (one translation point).
ते तुम्हाला न बदलता येणारा third-party किंवा legacy code एकत्र करू देतं, आणि vendor-विशिष्ट आकार तुमच्या business logic बाहेर ठेवतं (एक भाषांतर-बिंदू).
How
कसं
Implement your target interface; inside, delegate to the adaptee, mapping arguments and return values (and errors) between the two vocabularies.
तुमचं target interface राबवा; आत, adaptee ला delegate करा, दोन शब्दसंग्रहांमध्ये arguments आणि return values (आणि errors) map करून.
your code ──► «PaymentGateway.charge(Money, token)» (what you want)
│ implemented by
StripeAdapter
│ translates → stripe.charges.create({amount, source})
▼
Stripe SDK (the adaptee — unchanged, incompatible shape)PostgresAdapter, MySQLAdapter, SQLiteAdapter each make a specific driver satisfy the ORM's uniform interface — so the ORM core speaks one language while the drivers differ.PostgresAdapter, MySQLAdapter, SQLiteAdapter प्रत्येक विशिष्ट driver ला ORM च्या एकसमान interface शी जुळवतो — म्हणून ORM core एक भाषा बोलतो तर drivers वेगळे.interface PaymentGateway { charge(amount: Money, token: string): Promise<Receipt> }
class StripeAdapter implements PaymentGateway { // YOUR interface
constructor(private stripe: Stripe) {}
async charge(amount: Money, token: string): Promise<Receipt> {
const res = await this.stripe.charges.create({ // THEIR shape
amount: amount.cents, currency: amount.currency, source: token,
})
return { id: res.id, status: res.status } // translate back to YOURS
}
}
// business code depends only on PaymentGateway; Stripe's vocabulary stops herejava.io.InputStreamReader (bytes→chars), Rack/WSGI adapting web servers to apps, wrapping any third-party/legacy SDK behind your own interface, and React wrappers around non-React widgets. Any "*Adapter"/"*Wrapper" class translating one API to another is this.java.io.InputStreamReader (bytes→chars), Rack/WSGI, कुठलाही third-party/legacy SDK तुमच्या interface मागे गुंडाळणं, आणि non-React widgets भोवतीचे React wrappers. एक API दुसऱ्यात भाषांतरित करणारी कुठलीही "*Adapter"/"*Wrapper" class हीच.✅ Do
- Use adapters to keep third-party/vendor shapes at the edges
- Translate arguments, return values, and errors into your vocabulary
- Keep one adapter per external system you integrate
✅ हे करा
- third-party/vendor आकार edges ला ठेवायला adapters वापरा
- arguments, return values, आणि errors तुमच्या शब्दसंग्रहात भाषांतरित करा
- integrate करता त्या प्रति external system एक adapter ठेवा
❌ Don't
- Let the adapter leak the adaptee's types (that defeats it — a leaky abstraction)
- Add an adapter when you actually control both sides and could just align them
❌ हे टाळा
- adapter ला adaptee चे types गळू देऊ नका (ते व्यर्थ — leaky abstraction)
- तुम्ही दोन्ही बाजू नियंत्रित करता आणि जुळवू शकता तेव्हा adapter जोडू नका
09Decorator — add behavior by wrappingDecorator — गुंडाळून behavior जोडा★ very common★ खूप सामान्य▶
Scenario: you have an HttpClient, and you want to add retries, then caching, then auth headers, then request logging — composably, so you can mix and match per environment. A decorator wraps the client in another object with the same interface that adds one behavior and delegates the rest. Stack them like layers.
परिस्थिती: तुमच्याकडे HttpClient आहे, आणि तुम्हाला retries, मग caching, मग auth headers, मग request logging जोडायचं आहे — composably, म्हणजे प्रति environment mix-and-match. एक decorator client ला तोच interface असलेल्या दुसऱ्या object मध्ये गुंडाळतो जो एक behavior जोडतो आणि बाकी delegate करतो. त्यांना थरांसारखं रचा.
What
काय
Decorator: wrap an object in another object implementing the same interface, adding behavior before/after delegating. Decorators nest, so behaviors stack.
Decorator: object ला तोच interface राबवणाऱ्या दुसऱ्या object मध्ये गुंडाळा, delegate करण्याआधी/नंतर behavior जोडून. Decorators nest होतात, म्हणून behaviors रचली जातात.
Why
का
It adds responsibilities at runtime without subclass explosion (the composition-over-inheritance win): retry + cache + log = three small wrappers, any combination.
ते subclass-स्फोट न करता runtime ला जबाबदाऱ्या जोडतं (composition-over-inheritance फायदा): retry + cache + log = तीन लहान wrappers, कुठलाही संयोग.
How
कसं
Each decorator takes the wrapped object, implements the interface, does its extra bit, and calls through. Compose by nesting: log(cache(retry(client))).
प्रत्येक decorator गुंडाळलेला object घेतो, interface राबवतो, त्याचं अतिरिक्त करतो, आणि पुढे call करतो. Nest करून compose करा: log(cache(retry(client))).
request ─► LoggingClient ─► CachingClient ─► RetryClient ─► HttpClient
│ log │ hit? return │ try 3× │ real fetch
▼ delegate ▼ else delegate ▼ delegate ▼
every layer is an HttpClient; caller can't tell how many wrappers are stacked
mix per env: prod = log(cache(retry(c))) test = c (no wrappers)new BufferedReader(new InputStreamReader(new FileInputStream(f))) — each wrapper adds buffering / decoding while keeping the Reader/InputStream interface. Python's @decorator syntax wraps functions the same way.new BufferedReader(new InputStreamReader(new FileInputStream(f))) — प्रत्येक wrapper buffering/decoding जोडतो, Reader/InputStream interface ठेवून. Python चं @decorator syntax functions ला तसंच गुंडाळतं.interface HttpClient { get(url: string): Promise<Response> }
class RetryClient implements HttpClient { // adds retries
constructor(private inner: HttpClient, private times = 3) {}
async get(url: string) {
for (let i = 0; i < this.times; i++) {
try { return await this.inner.get(url) } catch (e) { if (i === this.times-1) throw e }
}
throw new Error('unreachable')
}
}
class CachingClient implements HttpClient { // adds caching
private cache = new Map<string, Response>()
constructor(private inner: HttpClient) {}
async get(url: string){ return this.cache.get(url) ?? this.set(url) }
private async set(u: string){ const r = await this.inner.get(u); this.cache.set(u,r); return r }
}
// compose: const client = new CachingClient(new RetryClient(new RealHttpClient()))BufferedReader, GZIPInputStream), Python function @decorators, React Higher-Order Components and hooks that wrap behavior, Redux middleware, and resilience wrappers (retry/cache/circuit-breaker layered around a client — see topic 27). It's composition-over-inheritance made concrete.BufferedReader, GZIPInputStream), Python function @decorators, React Higher-Order Components आणि behavior गुंडाळणारे hooks, Redux middleware, आणि resilience wrappers (client भोवती retry/cache/circuit-breaker — topic 27). Composition-over-inheritance प्रत्यक्षात.✅ Do
- Use decorators to add cross-cutting behavior (retry, cache, log, auth) composably
- Keep each decorator to one responsibility and delegate the rest
- Compose per environment (prod stacks wrappers; tests use the bare object)
✅ हे करा
- cross-cutting behavior (retry, cache, log, auth) composably जोडायला decorators वापरा
- प्रत्येक decorator एका जबाबदारीपुरता ठेवा आणि बाकी delegate करा
- प्रति environment compose करा (prod wrappers रचतं; tests bare object वापरतात)
❌ Don't
- Change the interface in a decorator (that would make it an Adapter, not a Decorator)
- Stack so many layers that debugging a call means peeling ten wrappers
❌ हे टाळा
- decorator मध्ये interface बदलू नका (ते Adapter करेल, Decorator नाही)
- इतके थर रचू नका की एका call चं debugging म्हणजे दहा wrappers सोलणं
@decorator syntax and Java annotations are related but not identical to the GoF object-wrapping Decorator — same spirit, different mechanism.@decorator syntax आणि GoF object-wrapping Decorator संबंधित पण एकसारखे नाहीत.10Facade — one simple door over a complex subsystemFacade — जटिल subsystem वर एक साधं दार▶
Scenario: placing an order really means: validate the cart, reserve inventory, charge payment, create a shipment, send a confirmation email, and emit analytics — six subsystems. Callers shouldn't orchestrate all that. A facade — checkout.placeOrder(cart, payment) — presents one simple method that coordinates the mess behind it.
परिस्थिती: order place करणं म्हणजे खरंतर: cart validate करणं, inventory राखणं, payment charge करणं, shipment बनवणं, confirmation email पाठवणं, आणि analytics emit करणं — सहा subsystems. Callers ने ते सगळं orchestrate करू नये. एक facade — checkout.placeOrder(cart, payment) — एक साधी method देतो जी मागचा गोंधळ समन्वयित करते.
What
काय
Facade: a single, simplified interface in front of a complex subsystem, hiding its many moving parts behind one coherent entry point.
Facade: जटिल subsystem समोर एक साधं, सरलीकृत interface, त्याचे अनेक हलणारे भाग एका सुसंगत entry-point मागे लपवून.
Why
का
It reduces the surface callers must understand, decouples them from subsystem details, and gives you one place to change the orchestration.
ते callers ना समजावं लागणारं surface कमी करतं, त्यांना subsystem तपशीलांपासून decouple करतं, आणि orchestration बदलायला एक जागा देतं.
How
कसं
Create a class whose methods coordinate the subsystems for common use cases. Callers use the facade; the subsystems stay usable directly for advanced needs.
अशी class बनवा जिच्या methods सामान्य use-cases साठी subsystems समन्वयित करतात. Callers facade वापरतात; subsystems advanced गरजांसाठी थेट वापरण्याजोगे राहतात.
caller ──► checkout.placeOrder(cart, payment) ← the ONE simple door
│ orchestrates:
┌───────┬───────┼────────┬─────────┬──────────┐
Inventory Pricing Payments Shipping Email Analytics
caller doesn't know or care about the six subsystems — just the facade methodstripe.charges.create(...) over a pile of HTTP, auth, retries, and serialization. jQuery was a famous facade over inconsistent DOM/AJAX APIs. You call one friendly method; it hides the machinery.stripe.charges.create(...) देतो. jQuery हा विसंगत DOM/AJAX APIs वरचा प्रसिद्ध facade होता. तुम्ही एक मैत्रीपूर्ण method call करता; ती यंत्रणा लपवते.class CheckoutFacade {
constructor(
private inventory: Inventory, private pricing: Pricing, private payments: Payments,
private shipping: Shipping, private email: Mailer, private analytics: Analytics,
) {}
async placeOrder(cart: Cart, payment: PaymentInfo): Promise<Order> { // the simple door
await this.inventory.reserve(cart)
const total = this.pricing.totalFor(cart)
const receipt = await this.payments.charge(total, payment)
const order = Order.create(cart, receipt)
await this.shipping.schedule(order)
await this.email.sendConfirmation(order)
this.analytics.track('order.placed', order)
return order
}
}
// callers: checkout.placeOrder(cart, payment) — that's the whole surfacerequests over urllib, and API gateways / backend-for-frontend endpoints that present one call over many microservices. Any "just call this one thing" wrapper over a tangle is a facade.requests सारख्या high-level libraries urllib वर, आणि अनेक microservices वर एक call देणारे API gateways / backend-for-frontend endpoints. गुंत्यावर कुठलाही 'फक्त हे एक call कर' wrapper हा facade.✅ Do
- Provide a facade for the common workflows over a complex subsystem
- Keep the subsystems usable directly for advanced/edge cases
- Centralize orchestration in the facade so it's changed in one place
✅ हे करा
- जटिल subsystem वरच्या सामान्य workflows साठी facade द्या
- subsystems advanced/edge cases साठी थेट वापरण्याजोगे ठेवा
- orchestration facade मध्ये केंद्रित करा म्हणजे ते एका ठिकाणी बदलतं
❌ Don't
- Let the facade swell into a God object owning every rule (topic 28)
- Force everything through the facade if power users need direct access
❌ हे टाळा
- facade ला प्रत्येक नियमाचा मालक God object होऊ देऊ नका (topic 28)
- power users ना थेट access हवा असेल तर सगळं facade मधून जबरदस्ती करू नका
11Proxy — a stand-in that controls accessProxy — access नियंत्रित करणारा stand-in▶
Scenario: loading a user's orders from the DB is expensive, and you often don't touch them. An ORM gives you a proxy: a stand-in that looks like the orders collection but only hits the database the moment you actually access it (lazy loading). A proxy has the same interface as the real object but controls access to it.
परिस्थिती: user चे orders DB मधून load करणं महाग आहे, आणि तुम्ही अनेकदा त्यांना स्पर्श करत नाही. ORM तुम्हाला proxy देतो: orders collection सारखा दिसणारा stand-in जो तुम्ही खरंच access करताच database ला hit करतो (lazy loading). Proxy ला real object सारखा तोच interface असतो पण तो त्याचा access नियंत्रित करतो.
What
काय
Proxy: a placeholder with the same interface as a real object that controls access to it — adding lazy loading, caching, access control, or remote calls, transparently.
Proxy: real object सारखा तोच interface असलेला placeholder जो त्याचा access नियंत्रित करतो — lazy loading, caching, access control, किंवा remote calls पारदर्शकपणे जोडून.
Why
का
Callers use the object normally while the proxy handles cross-cutting concerns — defer expensive work, cache results, enforce permissions, or make a remote object look local.
Callers object normally वापरतात तर proxy cross-cutting concerns हाताळतो — महाग काम पुढे ढकला, results cache करा, permissions लागू करा, किंवा remote object local सारखा दिसवा.
How
कसं
Implement the target interface; hold (or lazily create) the real subject; intercept calls to add the concern, then delegate.
target interface राबवा; real subject धरा (किंवा lazily बनवा); calls अडवून concern जोडा, मग delegate करा.
caller ──► «Orders» (interface)
│
OrdersProxy ── first access? ──► load from DB, cache, delegate
│ ── cached? ──► return without touching DB
▼ ── unauthorized?──► throw
RealOrders (the expensive/remote/protected subject)Proxy intercepts property reads/writes to add validation, logging, or reactivity (Vue's reactivity is built on it).Proxy property reads/writes अडवून validation, logging, किंवा reactivity जोडतो (Vue ची reactivity यावर बांधलेली).interface Orders { all(): Promise<Order[]> }
class OrdersProxy implements Orders { // same interface as the real thing
private real?: RealOrders
private cached?: Order[]
constructor(private userId: string, private db: Db) {}
async all(): Promise<Order[]> {
if (this.cached) return this.cached // caching concern
this.real ??= new RealOrders(this.userId, this.db) // lazy-create the subject
this.cached = await this.real.all() // defer the expensive query
return this.cached
}
}
// user.orders is an OrdersProxy — the DB is hit only when you call .all()Proxy (Vue/MobX reactivity, validation), Spring AOP dynamic proxies (transactions, @Cacheable, security), gRPC/RMI client stubs (remote proxy makes a network object look local), reverse proxies / API gateways (nginx, Envoy) for auth & caching, and CDN edge caches. Protection, virtual (lazy), caching, and remote proxies are the common flavors.Proxy (Vue/MobX reactivity, validation), Spring AOP dynamic proxies (transactions, @Cacheable, security), gRPC/RMI client stubs, reverse proxies / API gateways (nginx, Envoy), आणि CDN edge caches. Protection, virtual (lazy), caching, आणि remote proxies हे सामान्य प्रकार.✅ Do
- Use a proxy to add lazy loading, caching, access control, or remote access transparently
- Keep the proxy's interface identical to the real subject's
- Isolate the cross-cutting concern in the proxy, not the business object
✅ हे करा
- lazy loading, caching, access control, किंवा remote access पारदर्शकपणे जोडायला proxy वापरा
- proxy चं interface real subject सारखं एकसारखं ठेवा
- cross-cutting concern proxy मध्ये वेगळं करा, business object मध्ये नाही
❌ Don't
- Hide expensive/remote work so well that callers get surprise latency (the "N+1 via lazy loading" trap)
- Put business logic in a proxy — it should only control access
❌ हे टाळा
- महाग/remote काम इतकं लपवू नका की callers ना surprise latency मिळेल (lazy loading द्वारे N+1)
- proxy मध्ये business logic ठेवू नका — तो फक्त access नियंत्रित करावा
user.orders for 100 users silently fires 100 queries, because each access transparently hits the DB. Proxies also complicate ==/identity (a proxy isn't the real object) and debugging (the object isn't what it appears). Use them deliberately, and know when a proxy is doing expensive work behind an innocent-looking property access.user.orders touch करणारा loop शांतपणे 100 queries चालवतो, कारण प्रत्येक access पारदर्शकपणे DB hit करतो. Proxies ==/identity ही गुंतागुंतीचं करतात आणि debugging (object जो दिसतो तो नसतो). मुद्दाम वापरा.12Composite — treat trees and leaves uniformlyComposite — trees आणि leaves एकसारखे वागवा▶
Scenario: a UI is a tree — a Panel contains Buttons and other Panels, nested arbitrarily. You want to call render() (or size()) on the whole tree without special-casing "is this a container or a leaf?" The Composite pattern lets a group of objects be treated the same as a single object.
परिस्थिती: एक UI म्हणजे tree — एक Panel मध्ये Buttons आणि इतर Panels असतात, कितीही nested. तुम्हाला संपूर्ण tree वर render() (किंवा size()) call करायचं आहे 'हे container आहे की leaf?' special-case न करता. Composite pattern objects च्या गटाला एका object सारखं वागवू देतं.
What
काय
Composite: compose objects into tree structures and let clients treat individual objects (leaves) and compositions (branches) uniformly through one interface.
Composite: objects ला tree रचनांत compose करा आणि clients ना वैयक्तिक objects (leaves) आणि compositions (branches) एकसारखे एका interface द्वारे वागवू द्या.
Why
का
It removes "is this one thing or a collection?" branching everywhere. Operations recurse naturally, and the tree can nest to any depth.
ते सगळीकडचं 'ही एक गोष्ट आहे की collection?' branching काढतं. Operations नैसर्गिकपणे recurse होतात, आणि tree कितीही खोल nest होऊ शकतं.
How
कसं
Define one interface (render(), size()); leaves implement it directly; composites implement it by iterating their children and combining results.
एक interface ठरवा (render(), size()); leaves ते थेट राबवतात; composites ते त्यांच्या children वर iterate करून आणि results एकत्र करून राबवतात.
«Component: render()»
│
┌────────┴─────────┐
Panel (composite) Button (leaf)
│ render() = render each child
┌──┴───┐
Button Panel ──► Button, Input… call root.render(); it recurses.
client code never checks "leaf or container?" — same call works on both<div> holds elements and other divs; you render the root and it recurses. A file system is the same — a directory contains files and directories, and du -sh recurses uniformly over both.<div> मध्ये elements आणि इतर divs असतात; तुम्ही root render करता आणि ते recurse होतं. File system तसंच — directory मध्ये files आणि directories असतात, आणि du -sh दोन्हींवर एकसारखं recurse होतं.interface Component { render(): string }
class Button implements Component { // leaf
constructor(private label: string) {}
render(){ return `[${this.label}]` }
}
class Panel implements Component { // composite
private children: Component[] = []
add(c: Component){ this.children.push(c); return this }
render(){ return '(' + this.children.map(c => c.render()).join(' ') + ')' } // recurse
}
const ui = new Panel().add(new Button('OK')).add(new Panel().add(new Button('Cancel')))
ui.render() // works on the whole tree — no "leaf or branch?" checksViewGroup/View), abstract syntax trees (compilers/linters), org charts, nested menus, scene graphs in games/graphics, and grouped shapes in design tools (group a group). Anytime "a whole" and "a part" respond to the same operations, it's Composite.ViewGroup/View), abstract syntax trees (compilers/linters), org charts, nested menus, games/graphics मधले scene graphs, आणि design tools मधले grouped shapes. जेव्हा 'संपूर्ण' आणि 'भाग' एकाच operations ला प्रतिसाद देतात, तेव्हा तो Composite.✅ Do
- Use it for genuine part-whole tree hierarchies
- Put the recursive operation on the interface so branches and leaves share it
- Let composites combine their children's results
✅ हे करा
- खऱ्या part-whole tree hierarchies साठी वापरा
- recursive operation सामायिक interface वर ठेवा म्हणजे branches आणि leaves ती सामायिक करतात
- composites ना त्यांच्या children चे results एकत्र करू द्या
❌ Don't
- Force leaf objects to implement child-management methods (
add/remove) they can't support - Use it where the data isn't actually tree-shaped
❌ हे टाळा
- leaf objects ना child-management methods (
add/remove) राबवायला भाग पाडू नका ज्या ते समर्थत नाहीत - data खरंच tree-आकाराचा नसेल तिथे वापरू नका
add()/remove(). Put child-management on the shared Component interface and every leaf is forced to implement (or throw on) methods it can't support — an LSP smell (see OOP topic 15). Put it only on Composite and clients must type-check to add children — losing some uniformity. There's no free lunch; pick based on whether callers need to treat everything as a potential container. Also watch recursion on very deep or cyclic trees — guard against stack overflows and cycles.add()/remove() कुठे ठेवायचं. child-management सामायिक Component interface वर ठेवा आणि प्रत्येक leaf ला ती न-राबवता येणारी methods राबवायला (किंवा throw करायला) भाग पडतं — एक LSP smell (OOP topic 15). फक्त Composite वर ठेवा आणि clients ना children जोडायला type-check करावं लागतं. फुकट काही नाही; callers ना सगळं संभाव्य container म्हणून वागवायचं आहे का त्यानुसार निवडा. खूप खोल किंवा cyclic trees वर recursion ही सांभाळा.13Bridge — vary two dimensions independentlyBridge — दोन आयाम स्वतंत्रपणे बदला▶
Scenario: you send notifications of different kinds (alert, reminder, receipt) over different channels (email, SMS, push). If you subclass one per combination you get 3×3 = 9 classes, and adding a channel means adding three more. Bridge splits the two axes — Notification (kind) and Channel (delivery) — so they vary independently: 3 + 3 classes, not 9.
परिस्थिती: तुम्ही वेगवेगळ्या प्रकारच्या (alert, reminder, receipt) notifications वेगवेगळ्या channels (email, SMS, push) वर पाठवता. प्रति संयोग एक subclass केलं तर 3×3 = 9 classes, आणि channel जोडणं म्हणजे आणखी तीन. Bridge दोन अक्ष वेगळे करतं — Notification (प्रकार) आणि Channel (delivery) — म्हणून ते स्वतंत्रपणे बदलतात: 3 + 3 classes, 9 नाही.
What
काय
Bridge: separate an abstraction from its implementation so the two can vary independently, connecting them by composition instead of a combinatorial class hierarchy.
Bridge: एका abstraction ला त्याच्या implementation पासून वेगळं करा म्हणजे दोन्ही स्वतंत्रपणे बदलू शकतात, त्यांना combinatorial class hierarchy ऐवजी composition ने जोडून.
Why
का
It prevents the M×N subclass explosion when two dimensions both change. Add a kind or a channel and it's +1 class, not +N.
दोन आयाम दोन्ही बदलतात तेव्हा ते M×N subclass स्फोट रोखतं. एक प्रकार किंवा channel जोडा आणि तो +1 class, +N नाही.
How
कसं
The abstraction holds a reference to an implementor interface and delegates the varying part to it. Compose a kind with a channel at runtime.
abstraction एका implementor interface चा reference धरतं आणि बदलणारा भाग त्याला delegate करतं. प्रकार आणि channel runtime ला compose करा.
❌ M×N subclasses: ✅ bridge (M + N):
AlertEmail AlertSms AlertPush «Notification» ──has-a──► «Channel»
ReminderEmail ReminderSms … Alert EmailChannel
ReceiptEmail … (9, then 12…) Reminder SmsChannel
Receipt PushChannel
add a channel → +3 classes add a channel → +1 class. axes are independent.interface Channel { deliver(to: string, text: string): Promise<void> } // implementor
class EmailChannel implements Channel { async deliver(to,t){ /* SES */ } }
class SmsChannel implements Channel { async deliver(to,t){ /* Twilio */ } }
abstract class Notification { // abstraction, holds a Channel
constructor(protected channel: Channel) {} // the "bridge"
abstract send(user: User): Promise<void>
}
class Alert extends Notification {
async send(user: User){ await this.channel.deliver(user.contact, '⚠ Alert: …') }
}
class Receipt extends Notification {
async send(user: User){ await this.channel.deliver(user.contact, 'Your receipt: …') }
}
// compose freely: new Alert(new SmsChannel()); new Receipt(new EmailChannel())
// add a channel → one class; add a notification kind → one class. no M×N.✅ Do
- Use it when two dimensions genuinely vary independently
- Connect abstraction and implementor by composition, injected at runtime
- Reach for it when you see an M×N subclass explosion forming
✅ हे करा
- दोन आयाम खरोखर स्वतंत्रपणे बदलतात तेव्हा वापरा
- abstraction आणि implementor ला composition ने जोडा, runtime ला injected
- M×N subclass स्फोट तयार होताना दिसतो तेव्हा घ्या
❌ Don't
- Introduce a bridge for one dimension that varies (that's over-engineering)
- Confuse it with Adapter — Bridge is designed up front; Adapter retrofits an existing mismatch
❌ हे टाळा
- एकच बदलणाऱ्या आयामासाठी bridge आणू नका (ते over-engineering)
- Adapter शी गोंधळू नका — Bridge आधीच design केलेला; Adapter अस्तित्वात असलेली विसंगती retrofit करतो
14Flyweight — share to save memoryFlyweight — memory वाचवायला सामायिक करा▶
Scenario: a text editor rendering a million characters. Giving every character its own object with font, size, and glyph metrics would blow up memory. The Flyweight pattern shares the intrinsic state (the glyph "A" at 12pt Arial is one shared object) and passes the extrinsic state (its position on the page) from outside — so a million characters reuse a few dozen glyph objects.
परिस्थिती: एक लाख अक्षरं render करणारा text editor. प्रत्येक अक्षराला font, size, glyph metrics असलेला स्वतःचा object दिल्यास memory फुटेल. Flyweight pattern intrinsic state सामायिक करतं (12pt Arial मधलं glyph "A" म्हणजे एक सामायिक object) आणि extrinsic state (पानावरची त्याची जागा) बाहेरून पास करतं — म्हणून एक लाख अक्षरं काही डझन glyph objects पुनर्वापरतात.
What
काय
Flyweight: minimize memory by sharing the common, immutable part (intrinsic state) of many similar objects, and supplying the variable part (extrinsic state) externally per use.
Flyweight: अनेक सारख्या objects चा सामायिक, immutable भाग (intrinsic state) सामायिक करून आणि बदलता भाग (extrinsic state) प्रति-वापर बाहेरून देऊन memory कमी करा.
Why
का
When you have huge numbers of similar objects, sharing the identical portion turns millions of objects into a small pool plus lightweight references.
तुमच्याकडे प्रचंड संख्येने सारखे objects असतात, तेव्हा एकसारखा भाग सामायिक करणं लाखो objects ना एका लहान pool + हलक्या references मध्ये बदलतं.
How
कसं
Split state into intrinsic (shared, cached in a factory) and extrinsic (passed in by the caller). A factory returns the shared flyweight for a given key.
state intrinsic (सामायिक, factory मध्ये cached) आणि extrinsic (caller ने पास केलेलं) मध्ये विभागा. एक factory दिलेल्या key साठी सामायिक flyweight परत करतो.
1,000,000 characters on screen
│ each needs: glyph + font (intrinsic) + x,y position (extrinsic)
▼
Glyph('A',12pt,Arial) ← ONE shared object, reused everywhere 'A' appears
used at (0,0), (14,0), (28,0)… ← positions passed in, not stored per-char
memory: ~dozens of glyph objects, not a millionclass Glyph { // intrinsic (shared, immutable) state
constructor(readonly char: string, readonly font: string, readonly size: number) {}
drawAt(x: number, y: number){ /* render this glyph at an EXTRINSIC position */ }
}
class GlyphFactory {
private pool = new Map<string, Glyph>()
get(char: string, font: string, size: number): Glyph {
const key = `${char}|${font}|${size}`
let g = this.pool.get(key)
if (!g){ g = new Glyph(char, font, size); this.pool.set(key, g) } // create once
return g // reuse forever
}
}
// a million 'A's → one Glyph('A',...) object; positions passed to drawAt(x,y)String.intern(), Python small-int/string caching, JS Symbol), browser glyph/font caches and text rendering, game engines sharing meshes/textures/tiles, immutable shared config, Integer.valueOf's cached small integers, and icon/sprite atlases. Anywhere "millions of nearly-identical objects" would otherwise blow memory, flyweight sharing appears.String.intern(), Python small-int/string caching, JS Symbol), browser glyph/font caches आणि text rendering, meshes/textures/tiles सामायिक करणारे game engines, immutable सामायिक config, Integer.valueOf ची cached small integers, आणि icon/sprite atlases. जिथे 'लाखो जवळपास-एकसारखे objects' memory फोडतील, तिथे flyweight sharing दिसतं.✅ Do
- Use it when object count is huge and much of the state is shared & immutable
- Cleanly separate intrinsic (shared) from extrinsic (per-use) state
- Keep flyweights immutable so sharing is safe
✅ हे करा
- object count प्रचंड आणि बरंचसं state सामायिक आणि immutable असेल तेव्हा वापरा
- intrinsic (सामायिक) आणि extrinsic (प्रति-वापर) state स्वच्छपणे वेगळं करा
- flyweights immutable ठेवा म्हणजे sharing सुरक्षित
❌ Don't
- Apply it when you have few objects (the factory/complexity isn't worth it)
- Store mutable per-instance data in a shared flyweight (corrupts every user)
❌ हे टाळा
- थोडे objects असताना लावू नका (factory/गुंतागुंत योग्य नाही)
- सामायिक flyweight मध्ये mutable प्रति-instance data साठवू नका (प्रत्येक user दूषित होतो)
15Strategy — swap the algorithm at runtimeStrategy — runtime ला algorithm बदला★ core★ गाभा▶
Scenario: shipping cost is computed differently per carrier (flat, weight-based, zone-based). Baking all three into one method with a switch means editing it for every new carrier. Strategy makes each algorithm a separate interchangeable object, injected into the context: new Checkout(new WeightBasedShipping()).
परिस्थिती: shipping खर्च प्रति carrier वेगळा मोजला जातो (flat, weight-based, zone-based). तिन्ही एका method मध्ये switch ने कोंबणं म्हणजे प्रत्येक नव्या carrier साठी ती edit करणं. Strategy प्रत्येक algorithm ला वेगळा interchangeable object करतं, context मध्ये injected: new Checkout(new WeightBasedShipping()).
What
काय
Strategy: define a family of interchangeable algorithms behind one interface and let the client pick/swap which one to use at runtime.
Strategy: एका interface मागे interchangeable algorithms चं कुटुंब ठरवा आणि client ला runtime ला कोणता वापरायचा/बदलायचा ते निवडू द्या.
Why
का
It removes algorithm-selection if/switch ladders, makes each algorithm independently testable, and lets you add a new one without touching the others (Open/Closed).
ते algorithm-निवडीच्या if/switch शिड्या काढतं, प्रत्येक algorithm स्वतंत्रपणे testable करतं, आणि इतरांना हात न लावता नवीन जोडू देतं (Open/Closed).
How
कसं
Define a strategy interface; implement one class per algorithm; the "context" holds a strategy and delegates the varying step to it. Inject the choice.
एक strategy interface ठरवा; प्रति algorithm एक class राबवा; 'context' एक strategy धरतो आणि बदलणारा step त्याला delegate करतो. निवड inject करा.
Checkout(context) ──► «ShippingStrategy.cost(order)»
▲ ▲ ▲
FlatRate WeightBased ZoneBased
swap at runtime: checkout.setStrategy(new ZoneBased())
add a carrier → new class implementing the interface. context unchanged.arr.sort(byPrice) vs arr.sort(byName) — same sort algorithm, pluggable comparison function. In languages with first-class functions, a strategy is often just a function you pass in.arr.sort(byPrice) vs arr.sort(byName) — तोच sort algorithm, pluggable comparison function. first-class functions असलेल्या languages मध्ये strategy अनेकदा फक्त एक function जो तुम्ही पास करता.interface ShippingStrategy { cost(order: Order): Money }
class FlatRate implements ShippingStrategy { cost(o){ return Money.of(5,'USD') } }
class WeightBased implements ShippingStrategy { cost(o){ return Money.of(o.weightKg*2,'USD') } }
class ZoneBased implements ShippingStrategy { cost(o){ /* by destination */ } }
class Checkout {
constructor(private shipping: ShippingStrategy) {} // the strategy is injected
quote(order: Order){ return order.total().add(this.shipping.cost(order)) }
}
new Checkout(new WeightBased()).quote(order) // pick per carrier at runtime
// in JS you'd often just pass a function: (order) => Money.of(order.weightKg*2,'USD')Comparator, sort(key=…)), Passport.js auth strategies, pricing/tax/discount/shipping rule engines, compression/encryption algorithm selection, retry/backoff policies, and ML model selection. In functional languages Strategy collapses into "pass a function," which is why higher-order functions are said to subsume it.Comparator, sort(key=…)), Passport.js auth strategies, pricing/tax/discount/shipping rule engines, compression/encryption algorithm निवड, retry/backoff policies, आणि ML model निवड. functional languages मध्ये Strategy 'एक function पास करा' मध्ये कोसळतं, म्हणून higher-order functions ते सामावतात.✅ Do
- Use it when one step has several interchangeable algorithms
- Inject the strategy so it's swappable and testable
- In FP-friendly languages, pass a function instead of a whole class
✅ हे करा
- एका step ला अनेक interchangeable algorithms असतील तेव्हा वापरा
- strategy inject करा म्हणजे ती swappable आणि testable
- FP-मैत्रीपूर्ण languages मध्ये पूर्ण class ऐवजी function पास करा
❌ Don't
- Create a strategy hierarchy for a single algorithm that never varies
- Re-introduce a
switchto pick the strategy in the context itself
❌ हे टाळा
- कधीच न बदलणाऱ्या एकाच algorithm साठी strategy hierarchy बनवू नका
- strategy निवडायला context मध्येच
switchपरत आणू नका
16Observer — publish changes to many listenersObserver — बदल अनेक listeners ना publish करा★ everywhere★ सर्वत्र▶
Scenario: when an order ships, several things must react — email the customer, update analytics, notify the warehouse — and you'll keep adding reactions. Hard-coding all of them into ship() couples it to everything. Observer lets the order publish an event and any number of subscribers react, without the publisher knowing who's listening.
परिस्थिती: order ship होताच अनेक गोष्टींनी प्रतिक्रिया द्यावी — customer ला email, analytics update, warehouse ला सूचना — आणि तुम्ही आणखी प्रतिक्रिया जोडत राहाल. त्या सगळ्या ship() मध्ये hardcode करणं त्याला सगळ्याशी जोडतं. Observer order ला एक event publish करू देतं आणि कितीही subscribers प्रतिक्रिया देतात, publisher ला कोण ऐकतंय हे न कळता.
What
काय
Observer: a subject maintains a list of observers and notifies them automatically when its state changes, so many objects react to one event without tight coupling.
Observer: एक subject observers ची यादी ठेवतो आणि त्याचं state बदलताच त्यांना आपोआप सूचना देतो, म्हणून अनेक objects घट्ट coupling शिवाय एका event ला प्रतिक्रिया देतात.
Why
का
The publisher stays decoupled from reacters — add/remove listeners freely. It's the backbone of event-driven UIs and reactive systems.
publisher प्रतिक्रिया देणाऱ्यांपासून decoupled राहतो — listeners मोकळेपणी जोडा/काढा. Event-driven UIs आणि reactive systems चा कणा.
How
कसं
Subject exposes subscribe/unsubscribe; on change it iterates observers and calls their handler. Observers register interest and react.
Subject subscribe/unsubscribe उघड करतो; बदलावर तो observers वर iterate करून त्यांचा handler call करतो. Observers रस register करतात आणि प्रतिक्रिया देतात.
Order.ship() ──emit('shipped')──► observers notified:
├─ EmailObserver.on(e) → send email
├─ AnalyticsObserver.on(e)→ track
└─ WarehouseObserver.on(e)→ update stock
the Order doesn't know who's listening. add/remove observers without touching it.element.addEventListener('click', handler) — many handlers subscribe; the element notifies them all on click. Node's EventEmitter, RxJS Observables, and Redux store.subscribe are the same subscribe-and-get-notified shape.element.addEventListener('click', handler) — अनेक handlers subscribe करतात; element त्या सगळ्यांना click वर सूचना देतो. Node चं EventEmitter, RxJS Observables, आणि Redux store.subscribe तोच subscribe-आणि-सूचना आकार.type Handler<T> = (event: T) => void
class Subject<T> {
private observers = new Set<Handler<T>>()
subscribe(h: Handler<T>){ this.observers.add(h); return () => this.observers.delete(h) } // returns unsubscribe
notify(event: T){ this.observers.forEach(h => h(event)) }
}
const shipped = new Subject<Order>()
shipped.subscribe(o => mailer.sendShipped(o)) // add reactions freely,
shipped.subscribe(o => analytics.track('shipped', o)) // without editing the publisher
// on ship: shipped.notify(order)addEventListener, Node EventEmitter, RxJS/Reactive Extensions, Redux/Vuex subscribe, React state + useEffect (components observe state), MobX/Vue reactivity, model→view notifications in MVC, and database change streams / websockets. It's the in-process cousin of Pub/Sub (topic 26), which distributes the same idea across services.addEventListener, Node EventEmitter, RxJS/Reactive Extensions, Redux/Vuex subscribe, React state + useEffect, MobX/Vue reactivity, MVC मधली model→view सूचना, आणि database change streams / websockets. हे Pub/Sub (topic 26) चा in-process चुलतभाऊ, जो तीच कल्पना services ओलांडून वितरतो.✅ Do
- Use it when many objects must react to one object's changes
- Return/track an unsubscribe handle to avoid leaks
- Keep observer handlers small and side-effect-focused
✅ हे करा
- अनेक objects एका object च्या बदलांना प्रतिक्रिया द्यावी लागते तेव्हा वापरा
- leaks टाळायला unsubscribe handle परत/track करा
- observer handlers लहान आणि side-effect-केंद्रित ठेवा
❌ Don't
- Forget to unsubscribe (the #1 memory-leak source in UIs)
- Build long chains where observers trigger observers trigger observers (hard to trace)
❌ हे टाळा
- unsubscribe विसरू नका (UIs मधला #1 memory-leak स्रोत)
- observers observers trigger करणाऱ्या लांब साखळ्या बांधू नका (अनुसरायला कठीण)
useEffect cleanup exists precisely for this); and when observers themselves emit events that trigger more observers, you get cascading updates that are hard to trace and can even loop infinitely. Notification order is also usually undefined — don't rely on observer A running before B. For cross-service fan-out, don't hand-roll Observer; use a real Pub/Sub broker (topic 26) with delivery guarantees.useEffect cleanup नेमकं यासाठी); आणि observers स्वतः events emit करून आणखी observers trigger करतात, तेव्हा अनुसरायला कठीण, अगदी अनंत लूप होणारे cascading updates मिळतात. सूचनेचा क्रम ही सहसा अपरिभाषित. Cross-service fan-out साठी Observer हाताने बांधू नका; delivery guarantees असलेला खरा Pub/Sub broker (topic 26) वापरा.17Command — actions as objectsCommand — क्रिया objects म्हणून★ powerful★ शक्तिशाली▶
Scenario: you need undo/redo in an editor, and a background job queue where work runs later on another machine. Both need the same thing: an action turned into an object you can store, pass around, queue, log, and replay. That's the Command pattern — encapsulate "do this" (and often "undo this") as a first-class object.
परिस्थिती: तुम्हाला editor मध्ये undo/redo हवं, आणि background job queue जिथे काम नंतर दुसऱ्या machine वर चालतं. दोघांना तीच गोष्ट लागते: object मध्ये बदललेली क्रिया जी तुम्ही साठवू, पास करू, queue करू, log करू, आणि replay करू शकता. हाच Command pattern — 'हे कर' (आणि अनेकदा 'हे undo कर') एक first-class object म्हणून encapsulate करा.
What
काय
Command: wrap a request/action as an object with an execute() (and often undo()), decoupling the thing that invokes an action from the thing that performs it.
Command: एका request/क्रियेला execute() (आणि अनेकदा undo()) असलेला object म्हणून गुंडाळा, क्रिया invoke करणारं आणि ती करणारं decouple करून.
Why
का
Actions become storable and manipulable: queue them, log them, schedule them, retry them, and undo them. It's the basis of job queues, undo stacks, and CQRS commands.
क्रिया साठवण्याजोग्या आणि हाताळण्याजोग्या होतात: त्यांना queue, log, schedule, retry, आणि undo करा. Job queues, undo stacks, आणि CQRS commands चा पाया.
How
कसं
Each action is a class with execute() capturing its parameters/receiver. An invoker runs (or queues/logs) commands without knowing their concrete type.
प्रत्येक क्रिया एक class जिच्या execute() मध्ये तिचे parameters/receiver असतात. एक invoker commands चालवतो (किंवा queue/log करतो) त्यांचा concrete type न जाणता.
«Command { execute() undo() }»
├─ ChargeCard(order, amount) ─► queue → run on a worker later
├─ AddItem(cart, product) ─► push on undo-stack; undo() reverses it
└─ SendEmail(to, template) ─► log → retry on failure
invoker just calls cmd.execute(); it doesn't know WHAT the command doesundo() pops and reverses the last one. A restaurant order ticket is a command: it captures the request, gets queued, and the kitchen (receiver) executes it — decoupled from the waiter (invoker).undo() शेवटचा pop करून उलटतो. रेस्टॉरंट order ticket म्हणजे command: तो request पकडतो, queue होतो, आणि kitchen (receiver) चालवते — waiter (invoker) पासून decoupled.interface Command { execute(): void; undo(): void }
class AddItem implements Command { // an action as an object
constructor(private cart: Cart, private product: Product) {}
execute(){ this.cart.add(this.product) }
undo(){ this.cart.remove(this.product) }
}
class History { // invoker + undo stack
private done: Command[] = []
run(c: Command){ c.execute(); this.done.push(c) }
undo(){ this.done.pop()?.undo() }
}
// queue variant: serialize commands and run them on a worker later (job queue)✅ Do
- Use it for undo/redo, queuing, scheduling, logging, and retry of actions
- Capture everything the action needs so it can run later/elsewhere
- Add
undo()when you need reversibility
✅ हे करा
- undo/redo, queuing, scheduling, logging, आणि retry साठी वापरा
- क्रियेला लागणारं सगळं पकडा म्हणजे ती नंतर/इतरत्र चालेल
- reversibility लागल्यास
undo()जोडा
❌ Don't
- Wrap a plain immediate function call in a Command for no reason
- Store un-serializable references in a command you intend to queue/persist
❌ हे टाळा
- साधा तात्काळ function call विनाकारण Command मध्ये गुंडाळू नका
- queue/persist करायच्या command मध्ये un-serializable references साठवू नका
18Chain of Responsibility — pass it down the lineChain of Responsibility — रांगेत पुढे द्या▶
Scenario: an incoming HTTP request must pass through auth, rate-limiting, logging, and body-parsing before reaching your handler — and each step may pass it on or stop it. Chain of Responsibility links handlers so a request flows down the chain until one handles it (or all get a turn). This is web middleware.
परिस्थिती: येणाऱ्या HTTP request ने तुमच्या handler पर्यंत पोहोचण्याआधी auth, rate-limiting, logging, आणि body-parsing मधून जायला हवं — आणि प्रत्येक step ते पुढे देऊ किंवा थांबवू शकतो. Chain of Responsibility handlers जोडतं म्हणजे request रांगेतून खाली वाहते जोपर्यंत कुणी हाताळत नाही (किंवा सगळ्यांना संधी मिळते). हेच web middleware.
What
काय
Chain of Responsibility: pass a request along a chain of handlers; each decides to handle it, transform it, and/or forward it to the next — decoupling sender from the specific receiver.
Chain of Responsibility: request handlers च्या साखळीवरून पास करा; प्रत्येक ते हाताळायचं, बदलायचं, आणि/किंवा पुढच्याला forward करायचं ठरवतो — sender ला विशिष्ट receiver पासून decouple करून.
Why
का
You can add, remove, or reorder handling steps without touching the others, and no single place needs to know the whole pipeline.
तुम्ही इतरांना हात न लावता handling steps जोडू, काढू, किंवा पुनर्क्रमित करू शकता, आणि एकाही जागेला संपूर्ण pipeline जाणायची गरज नाही.
How
कसं
Each handler holds a reference to the next and calls it (or short-circuits). Assemble the chain in order; feed the request to the head.
प्रत्येक handler next चा reference धरतो आणि तो call करतो (किंवा short-circuit करतो). साखळी क्रमाने जुळवा; request head ला द्या.
request ─► Auth ─► RateLimit ─► Logging ─► BodyParser ─► Handler
│ │ (429? │ next() │ │
next() stop!) next() next() respond
any link can handle-and-stop or transform-and-forward. reorder freely.(req, res, next) and either responds or calls next() to pass control down the chain. It's the everyday face of Chain of Responsibility.(req, res, next) मिळतं आणि ते एकतर प्रतिसाद देतं किंवा साखळीत खाली नियंत्रण द्यायला next() call करतं. Chain of Responsibility चा रोजचा चेहरा.type Ctx = { req: Request; res: Response }
type Handler = (ctx: Ctx, next: () => void) => void
function chain(handlers: Handler[]) {
return (ctx: Ctx) => {
let i = -1
const dispatch = () => { i++; if (i < handlers.length) handlers[i](ctx, dispatch) }
dispatch()
}
}
const auth: Handler = (c, next) => c.req.user ? next() : c.res.status(401) // stop
const rateLimit: Handler = (c, next) => underLimit(c) ? next() : c.res.status(429)
const app = chain([auth, rateLimit, /* logging, */ handler]) // reorder freely✅ Do
- Use it for pipelines where steps can handle, transform, or forward
- Keep each handler single-purpose and order the chain explicitly
- Allow short-circuiting (a handler that stops the chain)
✅ हे करा
- steps हाताळू, बदलू, किंवा forward करू शकतात अशा pipelines साठी वापरा
- प्रत्येक handler एका-हेतूचा ठेवा आणि साखळी स्पष्टपणे क्रमवा
- short-circuiting ला परवानगी द्या (साखळी थांबवणारा handler)
❌ Don't
- Build a chain where no handler ever handles the request (it falls off the end silently)
- Hide critical control flow across so many links it's impossible to follow
❌ हे टाळा
- कुणीही request हाताळत नाही अशी साखळी बांधू नका (ती शेवटातून silently पडते)
- इतक्या links मध्ये critical control flow लपवू नका की अनुसरणं अशक्य
next() or to respond hangs the request). Second, order is load-bearing and implicit: put logging before auth and you log unauthenticated noise; put body-parsing after the handler and it's too late. Make the chain's order explicit and tested, ensure a terminal handler always exists, and guarantee each link either forwards or completes.next() call करणं किंवा प्रतिसाद देणं विसरणं request hang करतं). दुसरा, क्रम भार-वाहक आणि अंतर्भूत: auth आधी logging ठेवा आणि unauthenticated गोंधळ log होतो. साखळीचा क्रम स्पष्ट आणि tested करा, terminal handler नेहमी असल्याची खात्री करा.19State — behavior that changes with stateState — state सोबत बदलणारं behavior★ core★ गाभा▶
Scenario: an Order behaves differently depending on its status — you can pay a pending order, cancel a paid one, but not cancel a shipped one. Encoding this as if (status === …) checks scattered across every method is bug-prone. The State pattern makes each state an object that defines what's allowed and where you can transition.
परिस्थिती: एक Order त्याच्या status नुसार वेगळं वागतो — तुम्ही pending order pay करू शकता, paid cancel करू शकता, पण shipped cancel करू शकत नाही. हे प्रत्येक method मध्ये if (status === …) तपासण्या म्हणून encode करणं bug-प्रवण. State pattern प्रत्येक state ला असा object करतं जो काय परवानगी आहे आणि कुठे transition करता येईल हे ठरवतो.
What
काय
State: let an object alter its behavior when its internal state changes, by delegating to a state object — so it "appears to change class." Each state encapsulates its allowed actions and transitions.
State: object ला त्याचं internal state बदलताच त्याचं behavior बदलू द्या, एका state object ला delegate करून — म्हणून तो 'class बदलल्यासारखा दिसतो.' प्रत्येक state त्याच्या परवानगी असलेल्या क्रिया आणि transitions encapsulate करतो.
Why
का
It replaces sprawling status-conditionals with explicit states, makes illegal transitions impossible, and puts each state's rules in one place — a real state machine.
ते पसरलेल्या status-conditionals ना स्पष्ट states ने बदलतं, बेकायदा transitions अशक्य करतं, आणि प्रत्येक state चे नियम एका ठिकाणी ठेवतं — एक खरं state machine.
How
कसं
Model each state as a class/object implementing the same interface; the context delegates actions to its current state, which may switch the context to a new state.
प्रत्येक state तोच interface राबवणारी class/object म्हणून मॉडेल करा; context क्रिया त्याच्या सध्याच्या state ला delegate करतो, जो context ला नव्या state वर switch करू शकतो.
Pending ──pay()──► Paid ──ship()──► Shipped ──deliver()──► Delivered
│ cancel() → Cancelled │ cancel() → refund+Cancelled │ cancel()? ❌ blocked
each state OBJECT knows: which actions are legal + where they lead.
context.cancel() delegates to current state — no scattered status ifs..then behaves per state. Workflow engines and game AI use explicit state objects..then प्रति state वागतं. Workflow engines आणि game AI स्पष्ट state objects वापरतात.if-soup state logic is so error-prone.if-soup state logic इतकं bug-प्रवण.interface OrderState { pay(o: Order): void; cancel(o: Order): void }
class Pending implements OrderState {
pay(o){ o.setState(new Paid()) }
cancel(o){ o.setState(new Cancelled()) }
}
class Paid implements OrderState {
pay(o){ throw new Error('already paid') }
cancel(o){ o.refund(); o.setState(new Cancelled()) }
}
class Shipped implements OrderState {
pay(o){ throw new Error('already paid') }
cancel(o){ throw new Error('cannot cancel a shipped order') } // illegal transition
}
class Order { constructor(private state: OrderState = new Pending()){}
setState(s: OrderState){ this.state = s }
pay(){ this.state.pay(this) } cancel(){ this.state.cancel(this) } }✅ Do
- Use it when behavior and allowed actions depend on a lifecycle status
- Make each state own its legal actions and transitions
- Reach for a state-machine library when transitions get complex
✅ हे करा
- behavior आणि परवानगी क्रिया lifecycle status वर अवलंबून असतील तेव्हा वापरा
- प्रत्येक state ला त्याच्या कायदेशीर क्रिया आणि transitions चा मालक करा
- transitions जटिल झाल्यास state-machine library घ्या
❌ Don't
- Scatter
if (status === …)checks across many methods - Model a boolean flag with two behaviors as a heavyweight state hierarchy
❌ हे टाळा
- अनेक methods ओलांडून
if (status === …)तपासण्या विखरू नका - दोन behaviors असलेल्या boolean flag ला जड state hierarchy म्हणून मॉडेल करू नका
20Template Method — fixed skeleton, pluggable stepsTemplate Method — निश्चित कणा, pluggable steps▶
Scenario: every data importer does the same dance — open source, read rows, parse (CSV vs JSON vs XML differs), validate, write to DB, close. The skeleton is identical; only a step or two vary. Template Method defines the skeleton in a base class and lets subclasses fill in specific steps (hook methods).
परिस्थिती: प्रत्येक data importer तेच नृत्य करतो — source उघड, rows वाच, parse (CSV vs JSON vs XML वेगळं), validate, DB मध्ये लिही, बंद कर. कणा एकसारखा; फक्त एक-दोन steps बदलतात. Template Method कणा एका base class मध्ये ठरवतं आणि subclasses ना विशिष्ट steps भरू देतं (hook methods).
What
काय
Template Method: define the skeleton of an algorithm in a method, deferring some steps to subclasses via overridable hook methods — the overall structure is fixed, specific steps vary.
Template Method: एका method मध्ये algorithm चा कणा ठरवा, काही steps overridable hook methods द्वारे subclasses कडे सोपवून — एकंदर रचना निश्चित, विशिष्ट steps बदलतात.
Why
का
It removes duplicated boilerplate around the varying bits and enforces a consistent overall flow (the base controls the sequence; subclasses only fill blanks).
ते बदलणाऱ्या भागांभोवतीचा नक्कल boilerplate काढतं आणि सुसंगत एकंदर flow लागू करतो (base क्रम नियंत्रित करतो; subclasses फक्त blanks भरतात).
How
कसं
A concrete base method calls a sequence of steps, some abstract/overridable. Subclasses override just the steps that differ; they don't touch the sequence.
एक concrete base method steps ची मालिका call करते, काही abstract/overridable. Subclasses फक्त वेगळे steps override करतात; ते मालिका बदलत नाहीत.
Importer.run() (template method — fixed skeleton)
open() ← shared
rows = read() ← shared
parse(rows) ← ABSTRACT hook ► CsvImporter / JsonImporter override
validate() ← shared
write() ← shared (or hook)
close() ← shared
subclass changes parse(); it CANNOT reorder or skip the skeleton stepsrender()/onCreate(); test frameworks call setUp()/your test/tearDown() in a fixed order. Rails callbacks (before_save) hook into a fixed persistence flow.render()/onCreate() invoke करतात; test frameworks निश्चित क्रमाने setUp()/तुमचा test/tearDown() call करतात. Rails callbacks (before_save) निश्चित persistence flow मध्ये hook होतात.abstract class Importer {
run(source: string) { // the TEMPLATE METHOD (fixed flow)
const raw = this.open(source)
const rows = this.parse(raw) // ← the varying step (hook)
const valid = rows.filter(r => this.validate(r))
this.write(valid)
this.close()
}
protected abstract parse(raw: string): Row[] // subclasses fill this in
protected open(s: string){ /* shared */ return read(s) }
protected validate(r: Row){ return r.id != null } // shared default (overridable)
protected write(rows: Row[]){ /* shared */ }
protected close(){ /* shared */ }
}
class CsvImporter extends Importer { parse(raw){ return csv(raw) } } // only the diff
class JsonImporter extends Importer { parse(raw){ return JSON.parse(raw) } }setUp/tearDown, Rails/Django model callbacks, Spring's JdbcTemplate/RestTemplate (they own the resource dance; you supply the query/mapper), sorting frameworks that call your comparator, and ETL/build-pipeline skeletons. It's the inheritance-based cousin of Strategy (which composes the varying step instead of overriding it).setUp/tearDown, Rails/Django model callbacks, Spring चं JdbcTemplate/RestTemplate (ते resource-नृत्याचे मालक; तुम्ही query/mapper देता), तुमचा comparator call करणारे sorting frameworks, आणि ETL/build-pipeline कणे. हे Strategy चा inheritance-आधारित चुलतभाऊ.✅ Do
- Use it when many variants share one algorithm skeleton with a few varying steps
- Keep the sequence in the base; expose only the intended hook points
- Provide sensible default hooks subclasses can optionally override
✅ हे करा
- अनेक variants एक algorithm कणा + काही बदलणारे steps सामायिक करतात तेव्हा वापरा
- मालिका base मध्ये ठेवा; फक्त हेतूचे hook points उघड करा
- subclasses पर्यायाने override करू शकतील असे समजूतदार default hooks द्या
❌ Don't
- Expose so many hooks that subclasses effectively rewrite the algorithm
- Reach for inheritance here if composition (Strategy) fits better and is more flexible
❌ हे टाळा
- इतके hooks उघड करू नका की subclasses प्रभावीपणे algorithm पुन्हा लिहितील
- composition (Strategy) चांगलं बसत असेल तर इथे inheritance घेऊ नका
importer(source, parseFn)) — which is more flexible and testable. Use Template Method when the shared flow is genuinely stable and the variation is naturally expressed by subtyping; otherwise inject the step.importer(source, parseFn)) — जे अधिक लवचिक आणि testable. सामायिक flow खरोखर स्थिर असेल तेव्हाच Template Method वापरा.21Iterator — traverse without exposing internalsIterator — internals न उघडता traverse करा▶
Scenario: you want to loop over a collection — an array, a tree, a paginated API, a database cursor — without caring how it stores its elements. The Iterator pattern provides a uniform way to walk a collection one element at a time, hiding whether it's backed by an array, a linked list, or lazy pages.
परिस्थिती: तुम्हाला एका collection वर loop करायचं आहे — array, tree, paginated API, database cursor — त्याचे elements कसे साठवले याची पर्वा न करता. Iterator pattern collection एक-एक element ने चालायचा एकसमान मार्ग देतो, ती array, linked list, की lazy pages यांनी backed आहे हे लपवून.
What
काय
Iterator: provide a standard way to access elements of a collection sequentially without exposing its underlying representation.
Iterator: collection चे elements क्रमाने access करायचा standard मार्ग द्या, त्याची underlying representation न उघडता.
Why
का
Client code uses one loop shape (for…of) over any collection, and you can iterate lazily (generators, streams, paginated data) without loading everything at once.
Client code कुठल्याही collection वर एक loop-आकार (for…of) वापरतो, आणि तुम्ही सगळं एकदम न load करता lazily iterate करू शकता (generators, streams, paginated data).
How
कसं
Expose an iterator with next() (returning value + done), or implement the language's iterable protocol / a generator, so standard loops work.
next() (value + done परत करणारा) असलेला iterator उघड करा, किंवा language चा iterable protocol / generator राबवा, म्हणजे standard loops चालतात.
for (const x of collection) { … }
│ works the same whether collection is:
┌──────────┼───────────┬───────────────┬──────────────┐
Array LinkedList Tree (in-order) DB cursor Paginated API
the iterator hides HOW elements are stored/fetched; caller just walks themSymbol.iterator + for…of, Python __iter__/generators, Java Iterator/Iterable, C# IEnumerable/yield. Database cursors and Node streams iterate huge/lazy data without loading it all.Symbol.iterator + for…of, Python __iter__/generators, Java Iterator/Iterable, C# IEnumerable/yield. Database cursors आणि Node streams प्रचंड/lazy data सगळं न load करता iterate करतात.function* yields values on demand — perfect for infinite sequences or streaming a million rows one at a time, so you never hold the whole thing in memory.function* मागणीनुसार values yield करतो — infinite sequences किंवा एक लाख rows एक-एक stream करायला योग्य, म्हणून तुम्ही संपूर्ण गोष्ट memory मध्ये कधीच धरत नाही.class Tree {
constructor(public value: number, public children: Tree[] = []) {}
*[Symbol.iterator](): Iterator<number> { // implement the iterable protocol
yield this.value
for (const child of this.children) yield* child // recurse lazily
}
}
for (const v of new Tree(1, [new Tree(2), new Tree(3)])) console.log(v) // 1,2,3
// lazy pagination — walk an API without loading every page up front
async function* pages(url: string){
let next = url
while (next){ const r = await fetch(next).then(x=>x.json()); yield* r.items; next = r.next }
}for…of/foreach, generators (function*, Python yield), database cursors (stream rows without loading the table), Node/Java streams, paginated API iteration, RxJS/reactive sequences, and tree/graph traversals. Iterator is so fundamental it's a built-in language protocol almost everywhere — you implement it, you don't reinvent it.for…of/foreach, generators (function*, Python yield), database cursors (table न load करता rows stream), Node/Java streams, paginated API iteration, RxJS/reactive sequences, आणि tree/graph traversals. Iterator इतकं मूलभूत की ते जवळपास सगळीकडे built-in language protocol.✅ Do
- Implement your language's iterable protocol so standard loops work
- Use lazy iterators/generators for large or streaming data
- Hide the backing structure behind the iterator
✅ हे करा
- तुमच्या language चा iterable protocol राबवा म्हणजे standard loops चालतात
- मोठ्या किंवा streaming data साठी lazy iterators/generators वापरा
- backing रचना iterator मागे लपवा
❌ Don't
- Expose internal arrays/nodes for callers to traverse manually
- Mutate a collection while iterating it (a classic invalidation bug)
❌ हे टाळा
- callers ना manually traverse करायला internal arrays/nodes उघड करू नका
- iterate करताना collection mutate करू नका (classic invalidation bug)
ConcurrentModificationException or produce skipped/duplicated elements (JS iterating and splice-ing an array, Python mutating a dict mid-loop). Collect changes and apply them after, or iterate a copy. Second gotcha: lazy iterators are single-pass and stateful — a generator, DB cursor, or stream is consumed once; iterating it again yields nothing. If you need multiple passes, materialize it (to an array) or create a fresh iterator each time.ConcurrentModificationException throw करतात किंवा skipped/duplicated elements देतात (JS array iterate करून splice, Python dict mid-loop बदलणं). बदल जमवून नंतर लावा, किंवा copy iterate करा. दुसरा सापळा: lazy iterators single-pass आणि stateful — generator, DB cursor, किंवा stream एकदा वापरला जातो; पुन्हा iterate केल्यास काहीच मिळत नाही. अनेक passes लागल्यास materialize करा.22Mediator — centralize many-to-many chatterMediator — अनेक-ते-अनेक बडबड केंद्रित करा▶
Scenario: a complex form where changing the country dropdown updates the state list, toggles a tax field, and enables/disables Submit. If every widget talks directly to every other widget, you get an N×N tangle. A Mediator puts one coordinator in the middle: widgets report to it, and it orchestrates who updates whom.
परिस्थिती: एक जटिल form जिथे country dropdown बदलल्याने state-list update होते, tax field toggle होतं, आणि Submit enable/disable होतं. प्रत्येक widget प्रत्येक इतर widget शी थेट बोलला, तर N×N गुंता होतो. एक Mediator मध्ये एक coordinator ठेवतो: widgets त्याला कळवतात, आणि तो कोण कोणाला update करावं ते orchestrate करतो.
What
काय
Mediator: define an object that encapsulates how a set of objects interact, so they refer to the mediator instead of each other — turning many-to-many coupling into a hub-and-spoke.
Mediator: objects च्या संचाचा परस्परसंवाद encapsulate करणारा object ठरवा, म्हणून ते एकमेकांऐवजी mediator ला संदर्भतात — अनेक-ते-अनेक coupling ला hub-and-spoke मध्ये बदलून.
Why
का
It untangles N objects each knowing about the others (N² links) into N objects knowing one mediator. Interaction logic lives in one place, not smeared across components.
ते N objects प्रत्येकाला इतरांबद्दल माहीत असणं (N² links) N objects एका mediator ला जाणणं मध्ये सोडवतं. Interaction logic एका ठिकाणी राहतं, components ओलांडून पसरत नाही.
How
कसं
Components notify the mediator of events; the mediator holds the coordination rules and tells the relevant components what to do. Components don't reference each other.
Components mediator ला events कळवतात; mediator coordination नियम धरतो आणि संबंधित components ना काय करायचं ते सांगतो. Components एकमेकांना संदर्भत नाहीत.
❌ every widget ↔ every widget: ✅ mediator hub:
Country ── State Country ─┐
│ ╲ ╱ │ State ──┤
Tax ─── Submit (tangled mesh) Tax ──┼─► Mediator (coordination rules)
Submit ──┘
N² connections → N connections to one coordinator; rules centralizedclass FormMediator {
constructor(private country: Select, private state: Select,
private tax: Field, private submit: Button) {}
notify(sender: string, event: string) { // components report here
if (sender === 'country' && event === 'change') {
this.state.loadFor(this.country.value) // mediator decides the ripple
this.tax.setVisible(this.country.value === 'US')
this.submit.setEnabled(this.country.value !== '')
}
}
}
// widgets call mediator.notify('country','change') — they never touch each other
✅ Do
- Use it when many components interact in complex ways
- Centralize the interaction rules in the mediator
- Keep components referencing only the mediator, not each other
✅ हे करा
- अनेक components जटिल पद्धतीने संवाद करतात तेव्हा वापरा
- interaction नियम mediator मध्ये केंद्रित करा
- components फक्त mediator ला संदर्भू द्या, एकमेकांना नाही
❌ Don't
- Let the mediator absorb all logic and become a God object (topic 28)
- Use it for simple one-to-one interactions that don't need coordination
❌ हे टाळा
- mediator ला सगळं logic शोषून God object होऊ देऊ नका (topic 28)
- समन्वय न लागणाऱ्या साध्या one-to-one interactions साठी वापरू नका
23Memento — capture & restore state (undo)Memento — state पकडा आणि पुनर्संचयित करा (undo)▶
Scenario: a document editor needs undo. You must snapshot the document's state before each change and restore it later — without exposing or breaking the document's encapsulation. The Memento pattern captures an object's internal state into an opaque token you can stash and later use to roll back.
परिस्थिती: एका document editor ला undo लागतं. तुम्ही प्रत्येक बदलाआधी document चं state snapshot घ्यावं आणि नंतर पुनर्संचयित करावं — document ची encapsulation न उघडता किंवा न मोडता. Memento pattern object चं internal state एका opaque token मध्ये पकडतं जो तुम्ही साठवू आणि नंतर roll back करायला वापरू शकता.
What
काय
Memento: capture an object's internal state in a token (the memento) that can be stored externally and used to restore the object later, without violating its encapsulation.
Memento: object चं internal state एका token (memento) मध्ये पकडा जो बाहेर साठवता येतो आणि नंतर object पुनर्संचयित करायला वापरता येतो, त्याची encapsulation न मोडता.
Why
का
It's how you implement undo/redo, checkpoints, and rollbacks: save known-good snapshots and return to them, while the object keeps its internals private.
undo/redo, checkpoints, आणि rollbacks असे राबवता: known-good snapshots साठवा आणि त्यांकडे परत जा, तर object त्याचे internals private ठेवतो.
How
कसं
The object creates a memento of its state and can restore from one; a "caretaker" (e.g. an undo stack) holds mementos but treats them as opaque.
object त्याच्या state चं memento बनवतो आणि एका मधून पुनर्संचयित करू शकतो; एक 'caretaker' (उदा. undo stack) mementos धरतो पण त्यांना opaque मानतो.
Editor.save() ──► Memento{ text, cursor } ──► pushed onto undo-stack (caretaker)
...user edits...
Editor.undo() ◄── pop Memento ◄── restore(state)
caretaker holds snapshots but can't read/alter them — encapsulation preservedundo() can restore it. React's state snapshots and form "reset to saved" work this way.undo() पुनर्संचयित करू शकतो. React चे state snapshots आणि form 'reset to saved' असंच चालतं.class EditorMemento { constructor(readonly text: string, readonly cursor: number) {} }
class Editor {
private text = ''; private cursor = 0
type(s: string){ this.text += s; this.cursor += s.length }
save(): EditorMemento { return new EditorMemento(this.text, this.cursor) } // snapshot
restore(m: EditorMemento){ this.text = m.text; this.cursor = m.cursor } // roll back
}
class History { // caretaker — holds opaque mementos
private stack: EditorMemento[] = []
backup(e: Editor){ this.stack.push(e.save()) }
undo(e: Editor){ const m = this.stack.pop(); if (m) e.restore(m) }
}✅ Do
- Use it for undo/redo, checkpoints, and rollback
- Keep the memento opaque to the caretaker (only the originator reads it)
- Snapshot only the state you actually need to restore
✅ हे करा
- undo/redo, checkpoints, आणि rollback साठी वापरा
- memento caretaker ला opaque ठेवा (फक्त originator ते वाचतो)
- फक्त पुनर्संचयित करायला लागणारं state snapshot करा
❌ Don't
- Snapshot huge state on every keystroke without limits (memory blowup)
- Let external code depend on the memento's internal shape
❌ हे टाळा
- प्रत्येक keystroke वर मोठं state मर्यादेशिवाय snapshot करू नका (memory स्फोट)
- external code ला memento च्या internal आकारावर अवलंबून राहू देऊ नका
24Visitor — add operations to a type hierarchyVisitor — type hierarchy वर operations जोडा▶
Scenario: you have an AST (abstract syntax tree) of node types — NumberLit, BinaryOp, FunctionCall — and you keep needing new operations over it: evaluate, pretty-print, type-check, minify. Adding each as a method on every node type bloats the nodes. Visitor lets you add new operations without touching the node classes — each operation is a separate visitor.
परिस्थिती: तुमच्याकडे node types चं AST आहे — NumberLit, BinaryOp, FunctionCall — आणि तुम्हाला त्यावर सतत नवीन operations लागतात: evaluate, pretty-print, type-check, minify. प्रत्येक node type वर method म्हणून जोडणं nodes फुगवतं. Visitor तुम्हाला node classes ला हात न लावता नवीन operations जोडू देतं — प्रत्येक operation एक वेगळा visitor.
What
काय
Visitor: represent an operation to perform on the elements of an object structure as a separate visitor object, so you can add new operations without changing the element classes.
Visitor: object रचनेच्या elements वर करायचं operation एका वेगळ्या visitor object म्हणून दर्शवा, म्हणून तुम्ही element classes न बदलता नवीन operations जोडू शकता.
Why
का
When the set of types is stable but you add operations often (compilers, linters), Visitor keeps each operation in one cohesive place instead of scattering it across every type.
types चा संच स्थिर पण तुम्ही operations अनेकदा जोडता (compilers, linters) तेव्हा Visitor प्रत्येक operation प्रत्येक type मध्ये विखुरण्याऐवजी एका सुसंगत ठिकाणी ठेवतो.
How
कसं
Each element has accept(visitor) that calls the visitor's method for its type (double dispatch). Each operation is one visitor implementing a method per element type.
प्रत्येक element ला accept(visitor) असतो जो त्याच्या type साठी visitor ची method call करतो (double dispatch). प्रत्येक operation म्हणजे प्रति element type एक method राबवणारा एक visitor.
AST nodes (stable): NumberLit BinaryOp FunctionCall
each has accept(v) ─► calls v.visitNumberLit(this) etc. (double dispatch)
operations (grow often), each a visitor:
EvalVisitor visitNumberLit visitBinaryOp visitFunctionCall
PrintVisitor " "
TypeCheckVisitor " "
add an operation → new visitor class. node classes never change.interface Visitor { number(n: NumberLit): any; binary(b: BinaryOp): any }
interface Node { accept(v: Visitor): any }
class NumberLit implements Node { constructor(readonly value: number){}
accept(v: Visitor){ return v.number(this) } } // double dispatch
class BinaryOp implements Node { constructor(readonly op: string, readonly l: Node, readonly r: Node){}
accept(v: Visitor){ return v.binary(this) } }
class EvalVisitor implements Visitor { // one operation = one visitor
number(n){ return n.value }
binary(b){ const l=b.l.accept(this), r=b.r.accept(this); return b.op==='+'? l+r : l*r }
}
// add PrintVisitor / TypeCheckVisitor later — NumberLit/BinaryOp never change✅ Do
- Use it when the type set is stable but operations grow (ASTs, document trees)
- Keep each operation cohesive in one visitor
- Use it to run many passes over the same structure
✅ हे करा
- type संच स्थिर पण operations वाढतात तेव्हा वापरा (ASTs, document trees)
- प्रत्येक operation एका visitor मध्ये सुसंगत ठेवा
- एकाच रचनेवर अनेक passes चालवायला वापरा
❌ Don't
- Use it when you frequently add new element types (every visitor must then change)
- Reach for it in languages with pattern matching / sum types — a match is simpler
❌ हे टाळा
- तुम्ही वारंवार नवीन element types जोडता तेव्हा वापरू नका (मग प्रत्येक visitor बदलावा लागतो)
- pattern matching / sum types असलेल्या languages मध्ये घेऊ नका — match सोपं
switch/match on node type is simpler and does the same job — reserve classic Visitor for OO languages lacking that.match सोपं आणि तेच काम करतं.25Repository — a collection-like API over storageRepository — storage वर collection-सारखं API★ backbone★ कणा▶
Scenario: your business logic needs to load and save Orders, but you don't want SQL (or Mongo queries, or HTTP calls) smeared through it. A Repository gives your domain a collection-like interface — orders.byId(id), orders.save(order) — and hides whether that's Postgres, an API, or an in-memory list (crucial for tests).
परिस्थिती: तुमच्या business logic ला Orders load आणि save करायचे आहेत, पण तुम्हाला SQL (किंवा Mongo queries, किंवा HTTP calls) त्यात विखुरलेले नकोत. एक Repository तुमच्या domain ला collection-सारखं interface देतं — orders.byId(id), orders.save(order) — आणि ते Postgres आहे, API आहे, की in-memory list आहे हे लपवतं (tests साठी महत्त्वाचं).
What
काय
Repository: mediate between the domain and data-mapping layers with a collection-like interface for accessing domain objects, hiding the persistence mechanism.
Repository: domain आणि data-mapping layers मध्ये domain objects access करायला collection-सारखं interface देतं, persistence यंत्रणा लपवून.
Why
का
It keeps persistence details out of business logic, centralizes queries, and lets you swap the real database for an in-memory fake in tests (Dependency Inversion, OOP topic 17).
ते persistence तपशील business logic बाहेर ठेवतं, queries केंद्रित करतं, आणि tests मध्ये खरा database in-memory fake ने बदलू देतं (Dependency Inversion, OOP topic 17).
How
कसं
Define a repository interface in the domain (save, byId, findBy…); implement it per datastore in the infrastructure layer; inject it into services.
domain मध्ये repository interface ठरवा (save, byId, findBy…); प्रति datastore infrastructure layer मध्ये राबवा; services मध्ये inject करा.
OrderService ──► «OrderRepository { byId save findOpenFor(customer) }»
▲ ▲
PostgresOrderRepo InMemoryOrderRepo (tests)
business logic never writes SQL; swap the implementation without touching itorders.findOpenFor(customer) reads like operating on a collection — you don't think about tables, joins, or connection pools; the repository handles the mapping.orders.findOpenFor(customer) collection वर operate केल्यासारखं वाचतं — तुम्ही tables, joins, किंवा connection pools विचारत नाही; repository mapping सांभाळतं.interface OrderRepository { // defined in the DOMAIN
byId(id: string): Promise<Order | null>
save(order: Order): Promise<void>
findOpenFor(customerId: string): Promise<Order[]>
}
class PostgresOrderRepo implements OrderRepository { /* SQL + row↔Order mapping */ }
class InMemoryOrderRepo implements OrderRepository { // for fast, DB-free tests
private store = new Map<string, Order>()
async byId(id){ return this.store.get(id) ?? null }
async save(o){ this.store.set(o.id, o) }
async findOpenFor(c){ return [...this.store.values()].filter(o => o.customerId===c && o.isOpen()) }
}
// OrderService(new PostgresOrderRepo()) in prod; OrderService(new InMemoryOrderRepo()) in tests✅ Do
- Define the repository interface in the domain; implement per datastore
- Keep query logic in the repository, not scattered in services
- Provide an in-memory implementation for fast tests
✅ हे करा
- repository interface domain मध्ये ठरवा; प्रति store राबवा
- query logic repository मध्ये ठेवा, services मध्ये विखुरून नाही
- जलद tests साठी in-memory implementation द्या
❌ Don't
- Leak the ORM's query builder / SQL types through the interface
- Add a repository over a trivial CRUD app where the ORM already suffices
❌ हे टाळा
- interface मधून ORM चा query builder / SQL types गळू देऊ नका
- ORM आधीच पुरेसा असलेल्या क्षुल्लक CRUD app वर repository जोडू नका
IQueryable/lazy relations pushes persistence concerns right back into your domain — and reintroduces the N+1 problem when callers trigger lazy loads (Proxy, topic 11). Keep the interface in your terms, returning fully-loaded domain objects. Second, over a simple CRUD app the ORM's own models are often already a repository-ish abstraction — wrapping them in another layer is ceremony (see anti-patterns, topic 28). Add the pattern when you have real domain logic to protect from persistence detail, not reflexively.IQueryable/lazy relations उघड करतो, तो persistence concerns तुमच्या domain मध्ये परत ढकलतो — आणि callers lazy loads trigger करताच N+1 problem पुन्हा आणतो (Proxy, topic 11). interface तुमच्या भाषेत ठेवा, पूर्ण-loaded domain objects परत करत. दुसरा, साध्या CRUD app वर ORM चे स्वतःचे models आधीच repository-ish abstraction — त्यांना आणखी एका layer मध्ये गुंडाळणं सोपस्कार (topic 28).26Pub/Sub & the event bus — decouple across servicesPub/Sub आणि event bus — services ओलांडून decouple करा▶
Scenario: when an order is placed, billing, shipping, analytics, and email must all react — and you want to add reactions later without redeploying the order service. Publish/Subscribe broadcasts an OrderPlaced event to a broker; any number of independent services subscribe. It's Observer (topic 16) scaled across processes and machines.
परिस्थिती: order place होताच billing, shipping, analytics, आणि email सगळ्यांनी प्रतिक्रिया द्यावी — आणि तुम्हाला order service redeploy न करता नंतर प्रतिक्रिया जोडायच्या आहेत. Publish/Subscribe एक OrderPlaced event एका broker ला broadcast करतं; कितीही स्वतंत्र services subscribe करतात. हे Observer (topic 16) processes आणि machines ओलांडून scaled.
What
काय
Pub/Sub: publishers emit events to a broker/topic without knowing who consumes them; subscribers register interest and receive events independently. An event bus is the in-app version.
Pub/Sub: publishers events एका broker/topic ला emit करतात कोण consume करतं हे न जाणता; subscribers रस register करतात आणि events स्वतंत्रपणे मिळवतात. event bus ही in-app आवृत्ती.
Why
का
It fully decouples producers from consumers (in time and space): add/remove/scale consumers without touching producers, absorb load spikes via the broker, and enable event-driven architectures.
ते producers ना consumers पासून पूर्ण decouple करतं (वेळ आणि जागेत): producers ला हात न लावता consumers जोडा/काढा/scale करा, broker द्वारे load spikes शोषा, आणि event-driven architectures शक्य करा.
How
कसं
Publish events to a topic on a broker (Kafka, RabbitMQ, Redis, SNS/SQS); consumers subscribe and process asynchronously, each at its own pace, with the broker handling delivery/retries.
events एका broker वरच्या topic ला publish करा (Kafka, RabbitMQ, Redis, SNS/SQS); consumers subscribe करतात आणि asynchronously process करतात, प्रत्येक स्वतःच्या वेगाने, broker delivery/retries हाताळतो.
OrderService ──publish(OrderPlaced)──► [ topic on broker ]
├─► Billing consumer
├─► Shipping consumer
├─► Analytics consumer
└─► Email consumer
producer doesn't know consumers exist. add one → subscribe, no producer change.// producer — knows only the broker + topic, not the consumers
await broker.publish('order.placed', { orderId: order.id, total: order.total().cents })
// each consumer subscribes independently, processes asynchronously, at its own pace
broker.subscribe('order.placed', async (evt) => { await billing.invoice(evt.orderId) })
broker.subscribe('order.placed', async (evt) => { await shipping.schedule(evt.orderId) })
broker.subscribe('order.placed', async (evt) => { analytics.track('order', evt) })
// add a fraud-check consumer later → just subscribe. producer never changes.EventEmitter, Spring events, MediatR notifications) are the same shape without the network. Covered in depth in the Event-Driven Architecture playbook.EventEmitter, Spring events, MediatR notifications) तोच आकार network शिवाय. Event-Driven Architecture playbook मध्ये सविस्तर.✅ Do
- Use it to decouple producers from many independent, scalable consumers
- Make consumers idempotent (brokers deliver at-least-once → duplicates happen)
- Plan for dead-letter queues and retries for poison messages
✅ हे करा
- producers ना अनेक स्वतंत्र, scalable consumers पासून decouple करायला वापरा
- consumers idempotent करा (brokers at-least-once देतात → duplicates होतात)
- poison messages साठी dead-letter queues आणि retries योजा
❌ Don't
- Assume exactly-once delivery or global ordering (usually you get neither for free)
- Use async events where you need a synchronous, immediately-consistent answer
❌ हे टाळा
- exactly-once delivery किंवा global ordering गृहीत धरू नका (सहसा फुकट मिळत नाही)
- तात्काळ, consistent result लागतो तिथे async events वापरू नका
27Circuit Breaker & Retry — resilience against failureCircuit Breaker आणि Retry — अपयशाविरुद्ध resilience★ production★ production▶
Scenario: a downstream service you call gets slow/unhealthy. Naïve retries pile on and make it worse; every request hangs on a timeout, threads exhaust, and the failure cascades until your whole app is down. A Circuit Breaker detects the failures and "trips" — failing fast for a while instead of hammering a dying dependency — while Retry with backoff handles transient blips.
परिस्थिती: तुम्ही call करत असलेली downstream service मंद/अस्वस्थ होते. भोळे retries अधिक ढीग लावतात आणि गोष्टी बिघडवतात; प्रत्येक request timeout वर hang होते, threads संपतात, आणि अपयश cascade होतं जोपर्यंत तुमचा संपूर्ण app down होत नाही. एक Circuit Breaker अपयश ओळखून 'trip' होतो — मरणाऱ्या dependency वर ठोकण्याऐवजी थोडा वेळ जलद-अपयशी होतो — तर backoff सह Retry तात्पुरत्या blips हाताळतो.
What
काय
Circuit Breaker: wrap a risky call so that after N failures it "opens" and fails fast (skipping the call) for a cooldown, then "half-opens" to test recovery. Retry: re-attempt transient failures, ideally with exponential backoff + jitter.
Circuit Breaker: एका जोखमीच्या call ला गुंडाळा म्हणजे N अपयशांनंतर तो 'open' होतो आणि cooldown पर्यंत जलद-अपयशी होतो (call वगळून), मग recovery तपासायला 'half-open' होतो. Retry: तात्पुरत्या अपयशांचा पुन्हा-प्रयत्न, आदर्शतः exponential backoff + jitter सह.
Why
का
They stop one slow/broken dependency from cascading into a total outage: fail fast to shed load, give the dependency room to recover, and smooth over momentary glitches.
ते एका मंद/तुटलेल्या dependency ला संपूर्ण outage मध्ये cascade होण्यापासून थांबवतात: load टाकायला जलद-अपयशी व्हा, dependency ला recover व्हायला जागा द्या, आणि क्षणिक glitches वर smooth करा.
How
कसं
Track failures; on threshold, open the circuit (return an error/fallback immediately); after a timeout, half-open and probe. Combine with bounded retries, timeouts, and bulkheads.
अपयश track करा; threshold वर circuit open करा (लगेच error/fallback परत करा); timeout नंतर half-open करून probe करा. bounded retries, timeouts, आणि bulkheads सोबत जोडा.
failures ≥ threshold
CLOSED ──────────────────► OPEN ── fail fast (return fallback), skip the call
▲ (calls flow through) │ after cooldown
│ success ▼
└──────────────────── HALF-OPEN ── let ONE probe through:
success → CLOSED, failure → OPEN
retry (bounded, exp. backoff + jitter) handles TRANSIENT blips underneathclass CircuitBreaker {
private failures = 0; private openUntil = 0
constructor(private threshold = 5, private cooldownMs = 30000) {}
async call<T>(fn: () => Promise<T>, fallback: () => T): Promise<T> {
if (Date.now() < this.openUntil) return fallback() // OPEN → fail fast
try {
const r = await fn(); this.failures = 0; return r // success → reset
} catch (e) {
if (++this.failures >= this.threshold)
this.openUntil = Date.now() + this.cooldownMs // trip OPEN
return fallback()
}
}
}
// often composed as a Decorator (topic 9): breaker(retry(timeout(httpClient)))✅ Do
- Add timeouts + bounded retries with exponential backoff and jitter
- Trip a circuit breaker on a failing dependency and serve a fallback
- Only retry idempotent/safe operations
✅ हे करा
- timeouts + bounded retries exponential backoff आणि jitter सह जोडा
- अपयशी dependency वर circuit breaker trip करा आणि fallback द्या
- फक्त idempotent/safe operations retry करा
❌ Don't
- Retry without backoff/jitter (you create a thundering-herd retry storm)
- Retry non-idempotent calls (a "failed" charge may have succeeded → double charge)
❌ हे टाळा
- backoff/jitter शिवाय retry करू नका (retry storm तयार करता)
- non-idempotent calls retry करू नका ('अपयशी' charge यशस्वी झालेलं असू शकतं → दुहेरी charge)
28Anti-patterns — patterns misused, and pattern feverAnti-patterns — गैरवापरलेले patterns, आणि pattern-ताप★ judgment★ सारासार विवेक▶
Scenario: a codebase where everything is a *Manager or *Factory, there are five layers of interfaces with one implementation each, a 3,000-line ApplicationService God object, and Singletons everywhere. Every pattern was applied — and the result is harder to change than plain code. Knowing the patterns includes knowing how they go wrong.
परिस्थिती: अशी codebase जिथे सगळं *Manager किंवा *Factory आहे, एकच-implementation असलेल्या पाच interface-स्तर आहेत, एक 3,000-ओळींची ApplicationService God object आहे, आणि Singletons सगळीकडे. प्रत्येक pattern लावलं — आणि निकाल साध्या code पेक्षा बदलायला कठीण. Patterns जाणण्यात ते कसे बिघडतात हे जाणणंही येतं.
What
काय
Anti-patterns are common "solutions" that look reasonable but backfire — including overusing the very patterns in this playbook. The senior skill is restraint and fit.
Anti-patterns म्हणजे वाजवी वाटणारे पण उलटे परिणाम देणारे सामान्य 'उपाय' — या playbook मधले patterns अति-वापरणं यासह. senior कौशल्य म्हणजे संयम आणि योग्यता.
Why
का
Patterns are tools with costs (indirection, ceremony, cognitive load). Applied where their problem doesn't exist, they add complexity without benefit — the opposite of their purpose.
Patterns खर्चासह साधनं (indirection, सोपस्कार, cognitive load). त्यांची समस्या नसताना लावले, तर ते फायद्याशिवाय गुंतागुंत जोडतात — त्यांच्या हेतूचं उलट.
How
कसं
Reach for a pattern only when you have evidence of its problem (repetition, real variation, a genuine boundary). Prefer the simplest thing that works; refactor toward a pattern when the need appears.
एक pattern तेव्हाच घ्या जेव्हा त्याच्या समस्येचा पुरावा असेल (पुनरावृत्ती, खरी variation, खरी सीमा). चालणारी सर्वात साधी गोष्ट ला झुकतं द्या; गरज दिसताच एका pattern कडे refactor करा.
God Object / Blob one class does everything; every change touches it
Singleton abuse global mutable state disguised as a pattern (topic 5)
Pattern fever / forcing Factory/Visitor/Strategy where a function/if/enum
"Golden Hammer" would do — complexity for its own sake
Premature abstraction interfaces & layers for variation that never comes (YAGNI)
Spaghetti (no structure) ↔ Lasagna (too many thin indirection layers)
Poltergeist classes that just pass calls through, adding nothing// ❌ pattern fever: a Strategy + Factory + Singleton to add two numbers
const adder = AdderStrategyFactory.getInstance().create('sum')
adder.execute(a, b)
// ✅ it's a function.
const sum = (a: number, b: number) => a + b
// ❌ premature abstraction: 1 interface, 1 impl, no test double, no 2nd impl in sight
interface IUserNameFormatter { format(u: User): string }
class UserNameFormatterImpl implements IUserNameFormatter { /* ... */ }
// ✅ just a function until a real second case or a test seam actually appears
const formatName = (u: User) => `${u.first} ${u.last}`AbstractSingletonProxyFactoryBean became a meme for over-abstraction; "FizzBuzz Enterprise Edition" satirizes pattern fever; "distributed monolith" is microservices misapplied; and countless codebases carry God *Service/*Manager classes and interface-per-class layers no one needed. The reaction — Go's simplicity ethos, "YAGNI," "rule of three" before abstracting — is the industry course-correcting against pattern overuse.AbstractSingletonProxyFactoryBean over-abstraction चं meme झालं; 'FizzBuzz Enterprise Edition' pattern-ताप वर व्यंग करतं; 'distributed monolith' म्हणजे गैरलावलेले microservices; आणि असंख्य codebases God *Service/*Manager classes आणि interface-per-class स्तर वाहतात. प्रतिक्रिया — Go चं simplicity ethos, 'YAGNI,' abstract करण्याआधी 'rule of three' — म्हणजे उद्योग pattern अति-वापराविरुद्ध मार्ग सुधारतोय.✅ Do
- Start simple; refactor toward a pattern when the problem actually appears
- Apply the "rule of three" — abstract after the third real repetition, not the first
- Split God objects; delete pass-through layers that add nothing
✅ हे करा
- साधं सुरू करा; समस्या खरंच आली तेव्हा एका pattern कडे refactor करा
- 'rule of three' लावा — पहिल्या नव्हे, तिसऱ्या खऱ्या पुनरावृत्तीनंतर abstract करा
- God objects तोडा; काहीच न जोडणारे pass-through स्तर काढा
❌ Don't
- Add a pattern to show you know it, or "in case we need it later" (YAGNI)
- Wrap every class in an interface it will never have a second implementation of
- Let one class or one mediator/facade become the God object again
❌ हे टाळा
- तुम्हाला माहीत आहे दाखवायला किंवा 'नंतर लागेल म्हणून' pattern जोडू नका (YAGNI)
- कधीच दुसरी implementation न येणाऱ्या प्रत्येक class ला interface मध्ये गुंडाळू नका
- एका class किंवा mediator/facade ला पुन्हा God object होऊ देऊ नका