Vector DB

Vector DB అంటే ఏంటి?

సింపుల్‌గా చెప్పాలి అంటే, Vector DB అనేది meaning-based search కోసం use చేసే database. Normal database లేదా keyword search exact words మీద ఎక్కువ depend అవుతుంది. కానీ Vector DB words/sentences/documents యొక్క meaning ని numbers రూపంలో store చేసి, similar meaning ఉన్న content ని find చేస్తుంది.

Normal database ని ఇలా ఊహించుకో: నీ phone contacts లో “Ravi” అని search చేస్తే exact “Ravi” ఉంటే వస్తుంది. “Ravindra” లేదా “Ramesh” ఉంటే exact match లేకపోతే miss అవ్వచ్చు. కానీ Vector DB లో search exact word మీద మాత్రమే కాదు; meaning దగ్గరగా ఉందా లేదా అన్నది చూస్తుంది.

Example:

  • User search: “money back rules”
  • Document text: “Refund eligibility”
  • Exact words match కావు
  • But meaning same area లో ఉంది
  • Vector DB దీనిని similar గా identify చేయగలదు

అందుకే Vector DB ని semantic search engine లాగా కూడా చూడొచ్చు.


Traditional search problem ఏంటి?

Traditional keyword search లో user exact words type చేస్తే results బాగుంటాయి. But real users exact terminology use చేయరు.

Example company document లో ఇలా ఉంది:

Refund eligibility: Customers can request a full refund within 30 days of purchase if the product is unused.

User ఇలా అడగొచ్చు:

  • “money back rules ఏంటి?”
  • “product నచ్చకపోతే cash తిరిగి వస్తుందా?”
  • “కొన్న తర్వాత refund ఎలా తీసుకోవాలి?”
  • “return policy explain చేయి”

Keyword search “refund eligibility” అనే exact words కోసం చూస్తుంది. User “money back” అని type చేస్తే relevant document miss అవ్వచ్చు. ఈ gap ని solve చేయడానికి Vector DB useful.


Embeddings అంటే ఏంటి?

Embedding అంటే text meaning ని numbers list గా convert చేయడం. Machine కి text direct గా అర్థం కాదు. Machine numbers మీద operate చేస్తుంది. So text ని numerical representation గా మార్చాలి.

Example:

  • “dog” → [0.2, 0.8, 0.1, …]
  • “puppy” → [0.21, 0.79, 0.12, …]
  • “car” → [0.9, 0.1, 0.5, …]

ఇక్కడ “dog” and “puppy” meaning దగ్గరగా ఉంది కాబట్టి vectors కూడా close గా ఉంటాయి. “car” meaning different కాబట్టి vector space లో దూరంగా ఉంటుంది.

Important point:
ఈ numbers random కాదు. Embedding model text లోని meaning, context, relationships ని capture చేయడానికి train అయి ఉంటుంది.

Embedding models examples:

  • OpenAI embedding models
  • HuggingFace sentence-transformer models
  • Google embedding models
  • Cohere embeddings
  • Local embedding models

Vector అంటే actual గా ఏంటి?

Vector అంటే numbers list. Example:

[0.12, -0.45, 0.89, 0.03, ...]

Real embedding vectors లో hundreds లేదా thousands dimensions ఉండొచ్చు. For example 384, 768, 1536 dimensions లాంటి sizes common.

ప్రతి dimension directly “color”, “animal”, “refund” అని simple meaning represent చేయదు. Instead, model learned hidden features represent చేస్తుంది. మనం వాటిని human-readable గా interpret చేయలేము, but similarity calculation కి చాలా useful.


Vector DB లో ఏమి store చేస్తారు?

Usually Vector DB లో only vector మాత్రమే store చేయరు. Practical system లో ఈ items store చేస్తారు:

  1. Embedding vector — text meaning numbers రూపంలో
  2. Original text chunk — actual paragraph/sentence
  3. Source — document URL, page ID, file name, record ID
  4. Metadata — category, author, department, page number, tags
  5. Permissions — ఎవరు చూడగలరు, access level ఏంటి
  6. Timestamps — created date, updated date
  7. Version info — document latest version ఏది

Example record:

FieldExample
vector[0.12, 0.88, …]
text“Customers can request a refund within 30 days…”
sourcerefund_policy.pdf
metadatacategory: billing, page: 2
permissionsupport_team_only

Text chunks ఎందుకు చేస్తారు?

Full document ని ఒకే vector గా convert చేస్తే details dilute అవుతాయి. So document ని smaller pieces గా split చేస్తారు. వాటిని chunks అంటారు.

Example:

ఒక 20-page policy document ఉంటే, దాన్ని paragraphs లేదా sections గా split చేస్తారు:

  • Chunk 1: Refund eligibility
  • Chunk 2: Refund process
  • Chunk 3: Exceptions
  • Chunk 4: Contact support

ప్రతి chunk కి separate embedding create చేస్తారు. Search సమయంలో relevant chunk మాత్రమే retrieve అవుతుంది.

Chunking good గా చేయకపోతే problem:

  • Chunk too small అయితే context miss అవుతుంది
  • Chunk too large అయితే irrelevant text ఎక్కువ వస్తుంది
  • Overlap లేకపోతే sentence continuity break అవ్వచ్చు
  • Wrong section boundaries ఉంటే answer quality పడిపోతుంది

So RAG systems లో chunking strategy చాలా important.


Search ఎలా జరుగుతుంది? Step by step

User query: “money back rules ఏంటి?”

Step-by-step process:

  1. User query application కి వస్తుంది
  2. Same embedding model తో query ని vector గా convert చేస్తారు
  3. Vector DB లో stored vectors తో compare చేస్తారు
  4. Similarity score calculate చేస్తారు
  5. Top K closest chunks retrieve చేస్తారు
  6. Retrieved chunks ని LLM కి context గా ఇస్తారు
  7. LLM ఆ context ఆధారంగా final answer generate చేస్తుంది

ఇది RAG process లో retrieval part.


Similarity ఎలా calculate చేస్తారు?

Vector DB nearest vectors find చేయడానికి similarity metrics use చేస్తుంది.

Common methods:

  • Cosine similarity — direction similarity check చేస్తుంది
  • Euclidean distance — distance between points check చేస్తుంది
  • Dot product — vector alignment measure చేస్తుంది

Beginner level లో ఇలా గుర్తుపెట్టుకో:

Vectors దగ్గరగా ఉంటే meaning similar. Vectors దూరంగా ఉంటే meaning different.


RAG తో Vector DB relation ఏంటి?

RAG అంటే Retrieval-Augmented Generation.

Vector DB ఇందులో retrieval engine లాగా పని చేస్తుంది.

RAG flow:

  1. User question అడుగుతాడు
  2. Question embedding అవుతుంది
  3. Vector DB relevant chunks retrieve చేస్తుంది
  4. LLM కి question + chunks పంపుతారు
  5. LLM grounded answer generate చేస్తుంది

Without Vector DB, LLM guess చేయవచ్చు. With Vector DB, LLM trusted context ఆధారంగా answer ఇవ్వగలదు.

Example:

User: “Company లో maternity leave policy ఏంటి?”

  • LLM alone: guess చేయొచ్చు
  • RAG + Vector DB: actual HR policy document నుంచి relevant section retrieve చేసి answer ఇస్తుంది

Traditional DB vs Vector DB

AspectTraditional DBVector DB
Search typeExact match, filters, joinsSemantic similarity
Best forStructured dataUnstructured text/images/audio
Query exampleprice = 100“cheap laptop for students”
Data formatRows and columnsVectors + metadata
StrengthAccuracy for exact dataMeaning-based matching
WeaknessNatural language meaning weakExact transactional logic weak

Important: Vector DB traditional DB replacement కాదు. రెండింటి use cases different. Real applications లో రెండూ కలిసి work అవుతాయి.

Example:

  • SQL DB: user orders, payments, inventory
  • Vector DB: product docs, support articles, FAQs meaning search

Vector DB ఎందుకు suddenly popular అయింది?

LLMs popular అయిన తర్వాత, companies కి ఒక major problem వచ్చింది:

“మా private documents మీద ChatGPT లాగా answer ఇవ్వాలి. కానీ మా data model training లో లేదు.”

ఈ problem solve చేయడానికి RAG pattern popular అయింది. RAG కి semantic retrieval కావాలి. Semantic retrieval కి Vector DB useful. అందుకే Pinecone, Weaviate, Milvus, Chroma, Qdrant, FAISS వంటి tools popular అయ్యాయి.


Real use cases

1. Internal knowledge assistant

Company లో thousands of docs ఉంటాయి. Employee “expense reimbursement rules ఏంటి?” అని అడిగితే relevant finance policy retrieve చేసి answer ఇవ్వచ్చు.

2. Product documentation search

Software product help docs లో user natural language question అడుగుతాడు. Vector search relevant documentation page తీసుకొస్తుంది.

3. Customer support assistant

New support ticket వచ్చినప్పుడు, past similar tickets and solutions retrieve చేసి support agent కి suggest చేయచ్చు.

4. Legal document Q&A

Lawyers large contracts/case laws లో similar clauses search చేయచ్చు. Exact words different ఉన్నా meaning similar documents find చేయచ్చు.

5. Course recommendation

User: “Python beginner కి practical course కావాలి”
Vector DB course descriptions లో similar meaning search చేసి relevant courses suggest చేయచ్చు.

6. Resume matching

Job description embedding and candidate resume embeddings compare చేసి best matching candidates find చేయచ్చు.

7. Image/audio search

Vector DB text మాత్రమే కాదు. Images/audio కూడా embeddings గా convert చేస్తే similarity search చేయచ్చు. Example: similar product images, similar songs.


Vector DB tools examples

Popular options:

  • Pinecone — managed vector database
  • Weaviate — open-source + managed
  • Milvus — scalable open-source vector DB
  • Qdrant — high-performance vector search
  • Chroma — local/simple RAG projects కి beginner-friendly
  • FAISS — Facebook AI Similarity Search library
  • Postgres pgvector — Postgres లో vector search extension

Learning stage లో Chroma/FAISS/pgvector simple. Production scale లో Pinecone/Qdrant/Weaviate/Milvus consider చేయొచ్చు.


Common architecture

Typical RAG architecture ఇలా ఉంటుంది:

  1. Documents collect చేయడం
  2. Text extract and clean చేయడం
  3. Chunking చేయడం
  4. Embeddings create చేయడం
  5. Vector DB లో store చేయడం
  6. User query receive చేయడం
  7. Query embedding create చేయడం
  8. Vector DB నుంచి similar chunks retrieve చేయడం
  9. Prompt build చేయడం
  10. LLM answer generate చేయడం
  11. Answer with citations return చేయడం

Common mistakes

Mistake 1: Wrong chunk size

Too small chunks context miss చేస్తాయి. Too large chunks irrelevant content తీసుకువస్తాయి.

Mistake 2: Metadata ignore చేయడం

Metadata లేకపోతే filtering కష్టం. Example: latest policy only search చేయాలి అంటే date metadata కావాలి.

Mistake 3: Permissions ignore చేయడం

User కి access లేని document chunks retrieve అయితే security issue. Vector DB search లో permissions filtering must.

Mistake 4: Embedding model change చేసి re-index చేయకపోవడం

Documents ఒక embedding model తో store చేసి, queries another model తో embed చేస్తే similarity quality bad అవుతుంది.

Mistake 5: LLM కి too many chunks పంపడం

Too much context cost పెంచుతుంది and answer confuse చేయొచ్చు.


Limitations

Vector DB powerful అయినా perfect కాదు.

  • Similar meaning retrieve చేయగలదు, but truth verify చేయదు
  • Old documents ఉంటే wrong answer రావచ్చు
  • Bad embeddings అయితే bad retrieval
  • Ambiguous query అయితే irrelevant chunks రావచ్చు
  • Numerical filters and exact joins కి SQL DB better
  • Access control handle చేయకపోతే data leak risk
  • Large scale లో indexing, latency, cost manage చేయాలి

So Vector DB is one component only. Good RAG system కి data quality, chunking, metadata, permissions, prompts, evaluation అన్నీ అవసరం.


Simple analogy

Traditional search = dictionary లో exact word వెతకడం
Vector search = meaning అర్థం చేసుకుని related concept వెతకడం

ఇంకో analogy:

Traditional DB librarian కి “Refund Policy page number 2 ఇవ్వు” అని అడిగినట్టు.
Vector DB librarian కి “డబ్బులు తిరిగి వచ్చే rules ఎక్కడ ఉన్నాయి?” అని natural language లో అడిగితే, wording different అయినా correct section తీసుకురావడం.


Final mental model

Vector DB ని ఇలా గుర్తుపెట్టుకో:

Text/image/audio meaning ని numbers గా store చేసి, similar meaning ఉన్న content ని fast గా search చేసే database.

AI applications లో Vector DB mostly ఈ purpose కి use అవుతుంది:

  • LLM hallucination reduce చేయడం
  • Company/private data మీద answers ఇవ్వడం
  • Natural language search improve చేయడం
  • RAG systems build చేయడం
  • Similar documents/tickets/products find చేయడం

Simple గా చెప్పాలి అంటే: Vector DB అనేది AI కి “meaning-based memory search” లాంటిది.

Agentic AI Foundations

1. AI application vs AI model — difference ఏంటి?

సింపుల్‌గా చెప్పాలి అంటే, Gemini/ChatGPT లాంటి products are AI applications. వాటి లోపల ఒకటి లేదా ఎక్కువ AI models ఉంటాయి. Model answer generate చేయగలదు, కానీ complete application అవ్వడానికి UI, backend, login, payment, database, monitoring, rate limits, security, user history, file uploads, APIs — ఇవన్నీ కావాలి.

Model ని “brain” అనుకుంటే, application అనేది “brain + body + workflow + rules”. Brain ఒక్కటే ఉంటే user కి usable product రాదు.

ఎందుకు ఈ separation వచ్చింది?
Early software mostly rule-based. Developer every behavior code లో రాస్తారు. కానీ natural language questions కి fixed rules సరిపోవు. అందుకే AI model ని application లో ఒక intelligent component లాగా integrate చేయడం మొదలైంది.

ఎలా build చేస్తారు?

  1. Frontend user question తీసుకుంటుంది
  2. Backend ఆ request ని receive చేస్తుంది
  3. అవసరమైతే database/tools/RAG నుంచి context తెస్తుంది
  4. AI model కి prompt పంపుతుంది
  5. Model response ఇస్తుంది
  6. Application response ని format చేసి user కి చూపిస్తుంది

Use case:
Telusko chatbot లో user “AI course ఏది start చేయాలి?” అని అడిగితే, application user question తీసుకుని, Telusko course data fetch చేసి, model ద్వారా natural answer generate చేయాలి. Model alone random answer ఇవ్వచ్చు; application logic దాన్ని controlled answer గా మార్చుతుంది.

Real-world point:
Production AI app లో model quality మాత్రమే కాదు — latency, cost, privacy, fallback, logging, evaluation ఇవన్నీ equally important.

2. LLM prediction చేస్తుంది, database lookup కాదు

LLM అంటే Large Language Model. ఇది normal database లాగా exact row fetch చేయదు. ఇది text patterns నేర్చుకుని next token prediction చేస్తుంది. మన keyboard suggestions చిన్న scale లో ఎలా work అవుతాయో, LLM huge scale లో అలాగే but much more advanced గా work చేస్తుంది.

ఎందుకు develop చేయాల్సి వచ్చింది?
Human language చాలా flexible. “Python explain చేయి”, “Python అంటే ఏంటి?”, “Python beginner కి ఎలా చెప్తావు?” — meaning దగ్గరగా ఉన్నా wording different. ప్రతి possible wording కి code రాయడం impossible. అందుకే model ని huge examples మీద train చేసి language patterns నేర్పిస్తారు.

ఎలా చేస్తారు?

  1. Huge text corpus collect చేస్తారు
  2. Text ని tokens గా split చేస్తారు
  3. Model కి “given previous tokens, next token ఏమై ఉండొచ్చు?” అనే task మీద train చేస్తారు
  4. Training లో billions/trillions parameters adjust అవుతాయి
  5. Inference time లో user prompt చూసి next tokens generate చేస్తుంది

Important reasoning:
Model “understanding” అనేది human understanding లాంటిదా కాదా అనేది philosophical debate. Practical engineering view లో మాత్రం model patterns capture చేసి useful outputs ఇస్తుంది.

Use case:
User “resume summary improve చేయి” అంటే model grammar, tone, professional wording patterns use చేసి better summary generate చేస్తుంది.

3. Hallucination — confident గా wrong answer ఇవ్వడం

Hallucination అంటే AI confident గా wrong/fake answer ఇవ్వడం. ఇది “model bad” అని మాత్రమే కాదు; model design nature వల్ల వచ్చే limitation. Model truth verify చేయకుండా likely text generate చేస్తుంది.

ఎందుకు జరుగుతుంది?

  • Prompt లో enough context లేకపోవడం
  • Model training data లో fact లేకపోవడం
  • User question ambiguous గా ఉండడం
  • Model కి “answer తప్పనిసరిగా ఇవ్వాలి” అన్న pressure ఉండడం
  • External verification లేకపోవడం

ఎందుకు dangerous?

  • Medical/legal/finance domains లో wrong answer big risk
  • Company policy chatbot wrong policy చెప్పొచ్చు
  • Developer కి fake library/function suggest చేయొచ్చు
  • Student కి wrong concept confidently నేర్పొచ్చు

ఎలా handle చేస్తారు?

  • RAG: trusted documents నుంచి context ఇచ్చి answer చేయించడం
  • Tool calling: live API/database నుంచి facts తెప్పించడం
  • Prompt constraints: “Context లో లేకపోతే తెలియదు అని చెప్పు”
  • Citations: answer ఏ document మీద based ఉందో చూపించడం
  • Human review: high-risk outputs manual review చేయించడం
  • Validation: output schema, business rules, factual checks apply చేయడం

Use case:
Company HR bot “paid leave policy” గురించి answer ఇవ్వాలి. Base LLM మీద depend అయితే hallucination risk. HR policy PDF నుంచి RAG context retrieve చేసి answer చేస్తే grounded response వస్తుంది.

4. Knowledge cutoff and tool calling

Model ఒక time వరకు train అవుతుంది. ఆ తర్వాత జరిగిన events model memory లో ఉండవు. దీనినే knowledge cutoff అంటారు. కానీ tools ఉంటే model live information తీసుకోగలదు.

Tool calling అంటే ఏంటి?
LLM కి outside systems ని call చేసే ability ఇవ్వడం. Model “నాకు current date కావాలి”, “calendar check చేయాలి”, “database query చేయాలి”, “calculator use చేయాలి” అని decide చేసి tool call చేస్తుంది.

ఎందుకు develop చేయాల్సి వచ్చింది?

  • World data changes every second
  • Model retrain చేయడం costly and slow
  • Company private data model training లో ఉండదు
  • Some tasks require action, not just answer

ఎలా works?

  1. User request వస్తుంది
  2. Model intent అర్థం చేసుకుంటుంది
  3. Available tools list చూస్తుంది
  4. సరైన tool choose చేస్తుంది
  5. Tool కి structured input ఇస్తుంది
  6. Tool result తిరిగి model కి వస్తుంది
  7. Model final answer/action చేస్తుంది

Use cases:

  • “నా next meeting ఎప్పుడు?” → Calendar tool
  • “ఈ order status ఏంటి?” → Database/API tool
  • “ఈ calculation correct ఆ?” → Calculator tool
  • “GitHub లో issue create చేయి” → GitHub tool

Important point:
Tool calling వల్ల model “knowledge source” కాకుండా “reasoning + orchestration layer” అవుతుంది.

5. Tokens, token IDs, vectors/embeddings — language ని numbers గా మార్చడం

LLM text ని direct గా process చేయదు. Text first tokens గా split అవుతుంది. Token word కావచ్చు, word part కావచ్చు, punctuation కూడా కావచ్చు.

Example:

  • “unbelievable” ఒక token కావచ్చు లేదా “un”, “believ”, “able” లా multiple tokens కావచ్చు
  • “Explain Java collections” multiple tokens అవుతుంది

Flow:

  1. Text → tokens
  2. Tokens → token IDs
  3. Token IDs → embeddings/vectors
  4. Vectors → Transformer layers
  5. Output tokens → final text

Embedding అంటే ఏంటి?
Embedding అంటే word/sentence meaning ని numbers list గా represent చేయడం. Similar meaning ఉన్న words vectors space లో దగ్గరగా ఉంటాయి.

Example:

  • “car” and “automobile” meanings similar, vectors close
  • “king” and “queen” relation meaningful గా capture అవుతుంది
  • “bank” meaning context బట్టి river/finance వైపు shift అవుతుంది

ఎందుకు develop చేయాల్సి వచ్చింది?
Computers numbers మీద operate చేస్తాయి. Language meaning ని mathematical form లోకి convert చేస్తే machine similarity, context, relationship analyze చేయగలదు.

Use case:
Search system లో user exact keyword use చేయకపోయినా similar meaning documents retrieve చేయగలము. ఇది RAG కి base.

6. Transformer architecture — modern LLMs కి core engine

Transformer architecture 2017 “Attention is All You Need” paper వల్ల popular అయింది. ChatGPT, Gemini, Claude లాంటి systems Transformer family మీద based.

Old problem ఏంటి?
Older sequence models long context handle చేయడం లో weak. Sentence లో earlier word later meaning ని affect చేస్తే capture చేయడం difficult.

Example:

“The trophy doesn’t fit in the suitcase because it is too small.”
ఇక్కడ “it” suitcase ని refer చేస్తుంది. Context understand చేయాలి.

Attention mechanism అంటే?
Model ప్రతి token processing సమయంలో, sentence లోని other tokens లో ఏవి important అనేది weight చేస్తుంది. దీనివల్ల context relationships capture అవుతాయి.

ఎలా high level లో work చేస్తుంది?

  1. Tokens embeddings గా convert అవుతాయి
  2. Positional information add చేస్తారు — word order తెలియాలి కాబట్టి
  3. Attention layers relevant tokens మీద focus చేస్తాయి
  4. Feed-forward layers meaning refine చేస్తాయి
  5. Multiple layers repeat అవుతాయి
  6. Final layer next token probabilities ఇస్తుంది

Use case:
Long prompt లో “answer Telugu-English mix లో ఇవ్వు” అని beginning లో చెప్పినా, later answer అంతా ఆ style follow చేయడానికి context tracking ఉపయోగపడుతుంది.

7. Token cost — AI app economics

LLM APIs tokens basis లో charge చేస్తాయి. Input tokens + output tokens cost. Long conversation, large documents, verbose outputs — ఇవన్నీ cost increase చేస్తాయి.

ఎందుకు important?
Prototype లో cost small గా కనిపిస్తుంది. But 10,000 users daily use చేస్తే cost suddenly huge అవుతుంది.

Cost పెరగడానికి reasons:

  • Long chat history repeatedly send చేయడం
  • Unnecessary full documents prompt లో పెట్టడం
  • Very large output generate చేయించడం
  • Expensive model simple tasks కి use చేయడం
  • RAG retrieval too many chunks పంపించడం

How to manage?

  • Conversation summary maintain చేయడం
  • Relevant chunks only send చేయడం
  • Small model for simple tasks, big model for complex tasks
  • Max tokens limit పెట్టడం
  • Caching use చేయడం
  • Prompt concise గా design చేయడం
  • Evaluation ద్వారా unnecessary steps remove చేయడం

Use case:
Customer support bot లో greeting, simple FAQ కి cheaper model. Complex escalation కి stronger model. ఇలా routing చేస్తే cost control అవుతుంది.

8. RAG — company/private data తో grounded answers

RAG full form Retrieval-Augmented Generation. ఇది LLM ని company knowledge తో connect చేసే practical pattern.

Problem:
Base LLM కి company internal docs తెలియవు. Also model guess చేస్తే hallucination. Company-specific answers కి trusted source కావాలి.

RAG pipeline:

  1. Documents collect చేస్తారు — PDFs, Notion pages, website docs, DB records
  2. Text clean చేస్తారు
  3. Text ని chunks గా split చేస్తారు
  4. ప్రతి chunk కి embedding generate చేస్తారు
  5. Vector DB లో embeddings + metadata store చేస్తారు
  6. User query embedding generate చేస్తారు
  7. Similar chunks retrieve చేస్తారు
  8. Retrieved context prompt లో model కి ఇస్తారు
  9. Model context ఆధారంగా answer generate చేస్తుంది

Chunking ఎందుకు important?
Full document model కి పంపితే costly and noisy. చిన్న meaningful chunks అయితే relevant information retrieve అవుతుంది.

Metadata ఎందుకు?
Document title, date, department, access permissions వంటి metadata filtering కి ఉపయోగపడుతుంది.

Use case:
Telusko chatbot: “Agentic AI course లో MCP ఎప్పుడు వస్తుంది?” అని అడిగితే, course notes/docs నుంచి MCP related chunks retrieve చేసి answer ఇవ్వాలి.

Limitation:
RAG కూడా perfect కాదు. Bad chunking, old documents, wrong retrieval, missing permissions ఉంటే answer quality పడిపోతుంది.

9. Vector DB — meaning-based search engine

Vector DB embeddings store చేసి similarity search చేస్తుంది. Traditional DB exact match కి strong; Vector DB semantic match కి strong.

ఎందుకు కావాలి?
User language and document language same ఉండకపోవచ్చు.

Example:

  • Document: “refund eligibility”
  • User: “money back rules”
  • Keyword search miss అవ్వచ్చు
  • Vector search meaning match చేయగలదు

Vector DB లో usually ఏమి store చేస్తారు?

  • embedding vector
  • original text chunk
  • source document URL/id
  • metadata
  • permissions info
  • timestamps/version info

Search ఎలా జరుగుతుంది?

  1. User query embedding అవుతుంది
  2. Vector DB nearest vectors find చేస్తుంది
  3. Top K chunks return చేస్తుంది
  4. Model వాటిని context గా use చేస్తుంది

Use cases:

  • Internal knowledge assistant
  • Product documentation search
  • Legal document Q&A
  • Course recommendation bot
  • Support ticket similarity search

10. MCP — tools integration కి common standard

MCP అంటే Model Context Protocol. ఇది agents/tools మధ్య communication కి standard interface ఇవ్వడానికి useful.

Problem before MCP:
ప్రతి tool integration custom. GitHub కి ఒక style, Slack కి ఒక style, database కి ఇంకొక style. Agent developers కి integration maintenance burden.

MCP idea:
Tools ని standardized way లో expose చేయాలి. Agent tools list తెలుసుకుని, required tool call చేయాలి.

ఎలా use చేస్తారు?

  1. MCP server tools provide చేస్తుంది
  2. Agent available tools discover చేస్తుంది
  3. User intent బట్టి right tool select చేస్తుంది
  4. Tool input structured గా పంపుతుంది
  5. Tool output తీసుకుని reasoning continue చేస్తుంది

Use cases:

  • GitHub repo read/write
  • Slack messages search/post
  • Database records query/update
  • File system read/write
  • CRM data fetch
  • Calendar scheduling

Important caution:
Tool calling actions dangerous కావచ్చు. Delete, send email, update database వంటి actions కి permissions, confirmations, audit logs అవసరం.

11. Prompt engineering — model behavior ని shape చేయడం

Prompt engineering అంటే model కి task, role, constraints, context, output format clearly చెప్పడం.

Bad prompt:
“Explain RAG.”

Better prompt:
“You are teaching a beginner. Explain RAG in Telugu-English mix, include why it was developed, how it works, one real company chatbot use case, and limitations. Use bullets.”

ఎందుకు important?
LLM flexible. Good prompts లేకపోతే output inconsistent. Application లో consistency కావాలి.

Good prompt components:

  • Role: “You are a senior AI engineer”
  • Audience: “Beginner software developers”
  • Context: “Based on these course notes”
  • Task: “Explain concepts”
  • Constraints: “Don’t invent facts”
  • Format: “Use headings and bullets”
  • Examples: one-shot/few-shot
  • Tone: “Colloquial Telugu-English mix”

Prompt techniques:

  • Zero-shot: example లేకుండా direct task
  • One-shot: ఒక example తో
  • Few-shot: multiple examples తో
  • Structured prompting: fixed JSON/table/bullets format
  • Constraint prompting: do/don’t rules
  • Contextual prompting: extra background ఇవ్వడం

Use case:
YouTube script generator లో title generation, title selection, script generation, script evaluation — ప్రతి step కి different prompt and role ఇవ్వడం వల్ల pipeline controlled అవుతుంది.

12. Agents — chatbot కంటే next level

Agent అంటే LLM + reasoning + memory/context + tools + goal-oriented workflow.

Chatbot vs Agent:

  • Chatbot mostly answer ఇస్తుంది
  • Agent task plan చేసి tools use చేసి action complete చేస్తుంది

Agent high-level loop:

  1. User goal understand చేస్తుంది
  2. Task ని steps గా break చేస్తుంది
  3. అవసరమైన data/tools identify చేస్తుంది
  4. Tool calls చేస్తుంది
  5. Results evaluate చేస్తుంది
  6. అవసరమైతే next step decide చేస్తుంది
  7. Final answer/action complete చేస్తుంది

Use case:
“నా meeting transcript నుంచి action items తీసుకుని task tracker లో create చేయి.”
Agent transcript read చేస్తుంది, action items extract చేస్తుంది, assignee/due date infer లేదా ask చేస్తుంది, task database లో rows create చేస్తుంది.

Important caution:
Agents autonomous కాబట్టి guardrails must: permissions, confirmations, rate limits, logging, rollback, user approval.

Programme Tools List

This is a reference list of tools and libraries. Some of these may not be used or referred to during the course.

 

Category Tool / Library Mapped Module(s) Alternatives Web Link
Frameworks & Orchestration LangChain 6, 7, 9–17, 19–21 langchain.comLinks to an external site.
Autogen & Autogen Studio 2, 8, 9, 10, 11, 12, 17, 20 microsoft.github.io/autogenLinks to an external site.
Flowise 1, 8, 9, 15, 19, 20, 21 flowiseai.comLinks to an external site.
CrewAI 9, 10, 11, 19, 21 Guardrails AI, LangGraph crewai.comLinks to an external site.
LangFlow 4, 9, 20 langflow.orgLinks to an external site.
OpenAgents 9, 19, 20 github.com/xlang-ai/OpenAgentsLinks to an external site.
AgentVerse 10, 14 Autogen, CrewAI github.com/OpenBMB/AgentVerseLinks to an external site.
N8N 9 n8n.ioLinks to an external site.
Guardrails AI 18 guardrailsai.comLinks to an external site.
AI Models & APIs OpenAI (All services) 1, 2, 5, 6, 7, 9, 12, 13, 14, 18, 21 Hugging Face Transformers, Cohere API openai.comLinks to an external site.
Hugging Face 5, 6, 15, 16, 21 huggingface.coLinks to an external site.
Google SDK / ADK 7, 9 ai.google.devLinks to an external site.
Claude 12 claude.aiLinks to an external site.
Groq 7 groq.comLinks to an external site.
Vector Databases ChromaDB 6, 12, 15, 17, 21 FAISS trychroma.comLinks to an external site.
Pinecone 12, 15, 18, 21 ChromaDB, FAISS pinecone.ioLinks to an external site.
FAISS 6, 12, 20 faiss.aiLinks to an external site.
Development & Deployment Python 1, 3, 2007 python.orgLinks to an external site.
Jupyter 1, 3 jupyter.orgLinks to an external site.
Replit 2, 16 GitHub Pages, Local dev replit.comLinks to an external site.
Pydantic 7, 9 pydantic.devLinks to an external site.
Streamlit 16, 19, 21 Gradio streamlit.ioLinks to an external site.
FastAPI 16 Flask fastapi.tiangolo.comLinks to an external site.
Render 16 GitHub Pages, Streamlit Cloud render.comLinks to an external site.
Gradio 17 Streamlit gradio.appLinks to an external site.
Monitoring & Debugging LangSmith 1, 16, 17 LangFuse, Custom logging langchain.com/langsmithLinks to an external site.
PromptLayer 2, 13, 17 LangSmith, LangFuse, Manual tracing promptlayer.comLinks to an external site.
AgentOps 16, 17 LangSmith, Manual instrumentation agentops.aiLinks to an external site.
LangFuse 1 LangSmith langfuse.comLinks to an external site.
Machine Learning & Visualization Google Teachable Machine 3, 4 teachablemachine.withgoogle.comLinks to an external site.
Scikit-learn 4, 7 scikit-learn.orgLinks to an external site.
TensorFlow Playground 4, 8 playground.tensorflow.orgLinks to an external site.
Python Tutor 2 pythontutor.comLinks to an external site.
Tokenizer Visualizers 5, 10 tiktokenizer.vercel.appLinks to an external site.
CleanRL 14 Stable Baselines3, PettingZoo github.com/vwxyzjn/cleanrlLinks to an external site.
t-SNE tools 6 sklearn.manifold.TSNELinks to an external site.
Ethics & Resources AI Fairness Checklist 18 Google AI – Advancing AI safely and responsiblyLinks to an external site.
Ethics Cards Toolkit 18 Ethics CardsLinks to an external site.

Tools and practices

18 Responsible AI Tools and Practices | Microsoft AI

My Journey to the course Agentic AI and Applications

I have decided to take up this course :  https://iitmpravartak.emeritus.org/professional-certificate-programme-in-agentic-ai-and-applications

 

Why I’m Taking This Course: A Developer’s Perspective

1. Evolution from Prompting to Autonomous AI

After years of building systems, web apps, and integrations, I’ve witnessed AI evolve into something more dynamic. Traditional models respond; Agentic AI acts autonomously, pursues multi-step objectives, adapts, and reasons—unlocking a new tier of intelligent systems.

2. Hands-On Tool Experience

The programme emphasizes practical learning via tools like LangChain, CrewAI, Flowise, ChromaDB, FAISS, and LLM pipelines—far beyond theory.

3. Real-World Learning Through Projects & Capstone

The 20+ graded assignments and the culminating capstone project offer real, applied experience—an ideal fit for professionals who value learning by doing.

4. Certification for Credibility

Graduating participants earn three IBM certifications (AI Agent Building, Generative AI for Business, Responsible AI), alongside an IITM Pravartak certificate—a powerful combination of academic and industry recognition.

5. Designed for Professionals Like Me

The schedule blends live online sessions with self-paced learning, ideal for someone balancing full-time work commitments.

6. Networking with Thought-Leadership

From guest masterclasses by IITM faculty to an optional immersion at the IITM Research Park, this is a unique chance to interact with peers and mentors

Programme Modules

 

Run test cases in order

My project uses CI/CD pipeline. This builds the projects, run test cases and deploy to Stage/Dev/QA environments.

For CI/CD pipeline, my project uses GitHub actions.

Most of the times, when test cases are running, they run in random order and the order is not deterministic.

Maven Surefire is basically Maven’s test runner engine for your build lifecycle.

Primary Purpose

  • Discover your test classes (based on naming patterns like *Test, *Tests, Test*, etc.).

  • Run them using a supported testing framework (JUnit, TestNG, etc.).

  • Report the results in the console and generate HTML/XML reports.

Without Surefire, Maven would build your project but wouldn’t automatically execute your unit tests during the test phase.

Where it fits in Maven lifecycle

  • Maven has phases: validate → compile → test → package → verify → install → deploy.

  • Surefire is tied to the test phase.

  • So when you run:

mvn test

Maven:

  1. Compiles your main code (src/main/java).

  2. Compiles your test code (src/test/java).

  3. Hands over execution to Surefire to actually run the tests.

What it can do

  • Framework support: Runs JUnit (3, 4, 5), TestNG, Spock, Cucumber, etc.

  • Parallel execution: Run tests in parallel threads.

  • Filtering: Include/exclude specific test classes or methods.

  • Ordering: Control test run order (runOrder).

  • Reporting: Creates target/surefire-reports with plain text, XML, and sometimes HTML results.

  • Fail fast: Stop at the first failing test.

  • System property injection: Pass environment/config values to tests via Maven.

Why it matters

  • Ensures your unit tests are automatically executed every build.

  • Integrates seamlessly with CI/CD tools (Jenkins, GitHub Actions, GitLab CI, etc.).

  • Provides consistent test behavior across machines — no “works on my machine” excuses.

In a Maven/Gradle + JUnit + Mockito + Spring Boot project, there’s no built-in guarantee that one test class runs before another—JUnit treats tests as independent units and (by design) doesn’t define a default execution order.

Configure Maven Surefire Plugin

You can explicitly specify test run order in pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.5</version>
    <configuration>
        <runOrder>
            alphabetical
        </runOrder>
    </configuration>
</plugin>

For the Maven Surefire Plugin, the default runOrder is:

filesystem

Meaning of filesystem

  • Tests are run in the order the operating system’s filesystem returns them.

  • This is not guaranteed to be alphabetical — it depends on the filesystem and OS.

  • On some machines it looks alphabetical, but on others (especially Linux with ext4) it can be seemingly random.

Other runOrder options you can set in Surefire:

  • alphabetical – strictly by class name in ascending order.

  • reversealphabetical – reverse order by class name.

  • random – shuffles the test class order.

  • hourly – changes the order every hour to help catch order-dependent tests.

  • balanced – orders tests based on previous execution time (slowest first).

  • filesystem – OS filesystem order (default).

 

Prototype Design Pattern

What is a Prototype Design Pattern

In software development, the Prototype design pattern provides a mechanism for creating new objects by cloning existing ones. It allows the creation of object instances without depending on complex initialization logic, resulting in improved performance and flexibility.

The Prototype pattern is a creational design pattern in Java that allows you to create copies (clones) of objects without making the code dependent on their concrete classes. It provides a way to copy existing objects instead of creating new instances from scratch. This can be useful when object creation is expensive or complex, and you want to create variations of objects with different configurations.

Prototype pattern is used when the Object creation is a costly affair and requires a lot of time and resources and you have a similar object already existing. So this pattern provides a mechanism to copy the original object to a new object and then modify it according to our needs. This pattern uses java cloning to copy the object.

Explanation

The pattern defines a prototype interface or abstract class that declares a method for cloning objects, and concrete classes implement this interface to provide their cloning logic.

It would be easy to understand this pattern with an example, suppose we have an Object that loads data from database. Now we need to modify this data in our program multiple times, so it’s not a good idea to create the Object using new keyword and load all the data again from database. So the better approach is to clone the existing object into a new object and then do the data manipulation.

** Prototype design pattern mandates that the Object which you are copying should provide the copying feature. It should not be done by any other class. However whether to use shallow or deep copy of the Object properties depends on the requirements and it’s a design decision.

  • One example of how this can be useful is if an original object is created with a resource such as a data stream that may not be available at the time that a clone of the object is needed. Another example is if the original object creation involves a significant time commitment, such as reading data from a database or over a network.

Key Features of the Prototype Design Pattern:

  1. Prototype Interface/Abstract Class: The Prototype design pattern defines a prototype interface or abstract class with a clone method like doClone() that enables the creation of new objects by cloning existing ones. The doClone() method returns the object of same type.
  2. Concrete Prototypes: Concrete prototype classes implement the prototype interface/abstract class and provide their cloning logic. They create new instances by copying the data from an existing object.
  3. Client: The client class is responsible for requesting new object instances from the prototypes by invoking the clone method like doClone().

Benefits

  1. Reduces object creation overhead: The Prototype pattern avoids costly object initialization processes by creating new objects through cloning.
  2. Improves performance: Creating new objects by cloning is typically faster than invoking constructors and initializing object properties.
  3. Enhances flexibility: Prototypes allow dynamic creation of objects at runtime based on existing instances, providing flexibility in object creation.
  4. Supports object customization: Cloned objects can be modified individually without affecting the original prototype

Usage in JDK : java.lang.Object#clone ()


Implementing the Prototype Design Pattern in Java

Let’s demonstrate the implementation of the Prototype design pattern using Java code. We’ll create an example of a Shape prototype that allows the creation of different shapes by cloning existing ones.

// Prototype Interface - Shape
public interface Shape extends Cloneable {
    void draw();
    Shape doClone();
}

// Concrete Prototype - Rectangle
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }

    @Override
    public Shape doClone() {
        try {
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

// Concrete Prototype - Circle
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }

    @Override
    public Shape doClone() {
        try {
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

public class Application {
    public static void main(String[] args) {
        Shape rectangle = new Rectangle();
        Shape clonedRectangle = rectangle.doClone();
        rectangle.draw(); // Output: Drawing a rectangle.
        clonedRectangle.draw(); // Output: Drawing a rectangle.

        Shape circle = new Circle();
        Shape clonedCircle = circle.doClone();
        circle.draw(); // Output: Drawing a circle.
        clonedCircle.draw(); // Output: Drawing a circle.
    }
}

The Shape interface serves as the prototype interface, declaring the draw method for drawing shapes and the clone method for creating new instances.

The Rectangle and Circle classes are concrete prototype implementations that implement the Shape interface. They define their own cloning logic by overriding the clone method and provide the implementation for the draw method.

In the above example, we create instances of the Rectangle and Circle shapes. We then clone these objects using their doClone method, resulting in new instances that are independent but identical to the originals. Finally, we call the draw method on both the original and cloned objects, verifying their functionality.


Use cases

  • When the classes to instantiate are specified at run-time, for example, by dynamic
    loading; or
  • To avoid building a class hierarchy of factories that parallels the class hierarchy of
    products; or
  • When instances of a class can have one of only a few different combinations of state. It
    may be more convenient to install a corresponding number of prototypes and clone them rather than instantiating the class manually, each time with the appropriate state.

Implementation of Prototype pattern – real example

package com.rndayala.designpatterns.prototype;

//Product interface
public interface Product extends Cloneable {
	void setName(String name);
	String getName();
	void setPrice(double price);
	double getPrice();
	Product clone();
}


//Concrete product class: Shirt
public class Shirt implements Product {
	private String name;
	private double price;

	public Shirt(String name, double price) {
		this.name = name;
		this.price = price;
	}

	@Override
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void setPrice(double price) {
		this.price = price;
	}

	@Override
	public double getPrice() {
		return price;
	}

	@Override
	public Product clone() {
		return new Shirt(this.name, this.price);
	}
}


//Concrete product class: Pants
public class Pants implements Product {
	private String name;
	private double price;

	public Pants(String name, double price) {
		this.name = name;
		this.price = price;
	}

	@Override
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void setPrice(double price) {
		this.price = price;
	}

	@Override
	public double getPrice() {
		return price;
	}

	@Override
	public Product clone() {
		return new Pants(this.name, this.price);
	}
}


//Concrete product class: Shoes
public class Shoes implements Product {
	private String name;
	private double price;

	public Shoes(String name, double price) {
		this.name = name;
		this.price = price;
	}

	@Override
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void setPrice(double price) {
		this.price = price;
	}

	@Override
	public double getPrice() {
		return price;
	}

	@Override
	public Product clone() {
		return new Shoes(this.name, this.price);
	}
}


// Client code

public class Demo {

	public static void main(String[] args) {

		Product shirt1 = new Shirt("Basic Shirt", 25.0);
		Product pant1 = new Pants("Casual Pants", 40.0);
		Product shoe1 = new Shoes("Sports Shoes", 60.0);
		
		Product shirt2 = shirt1.clone();
		shirt2.setName("Fancy Shirt");
		
		Product pant2 = pant1.clone();
		pant2.setPrice(50.0);
		
		System.out.println("Shirt: " + shirt2.getName() + ", Price: $" + shirt2.getPrice());
        System.out.println("Pants: " + pant2.getName() + ", Price: $" + pant2.getPrice());
        System.out.println("Shoes: " + shoe1.getName() + ", Price: $" + shoe1.getPrice());

	}

}

In this example, we have implemented the Prototype pattern in the context of an e-commerce application. The Product interface acts as the prototype, and the concrete product classes (Shirt, Pants, and Shoes) implement the cloning functionality. This allows for easy creation of variations of products with different configurations without the need to create new objects from scratch.


The Prototype design pattern offers an efficient way to create new objects by cloning existing ones, eliminating the need for complex initialization processes. By utilizing the prototype interface and concrete prototype classes, the pattern enables the creation of new instances while preserving their characteristics. In Java, the Prototype pattern is commonly used when object creation is expensive, or when objects need to be customized based on existing instances.

Template Method Design Pattern

What is a Template Method Design Pattern

The Template Method design pattern is a behavioral design pattern that defines the outline or skeleton of an algorithm in a method but allows some steps of the algorithm to be implemented by subclasses. It promotes code reuse by providing a common structure for related algorithms while allowing specific steps to be customized in the subclasses.

It defines the skeleton of an algorithm in a base class, allowing subclasses to provide specific implementations for certain steps. It enables subclasses to customize specific parts of the algorithm while preserving the overall structure.

The Template Method pattern can be used in situations when there is an algorithm, some steps of which could be implemented in multiple different ways. In such scenarios, the Template Method pattern suggests keeping the outline of the algorithm in a separate method referred to as a template method inside a class, which may be referred to as a template class, leaving out the specific implementations of the variant portions (steps that can be implemented in multiple different ways) of the algorithm to different subclasses of this class.

Template Method lets subclasses to override/redefine certain steps of an algorithm without changing the algorithm’s structure.

The template method pattern provides a basic outline, but it allows you to customize and add your own variations to the solution.

Explanation

The Template Method design pattern is a way to create a standardized procedure for solving a problem.

It provides a set of steps that must be followed in a specific order to solve the problem. It lets subclasses to redefine certain steps of an algorithm without changing the algorithm’s structure.

The Template Method pattern is useful when you have a common algorithm with several variations, and you want to avoid code duplication among these variations. Instead of duplicating the common code in each subclass, you define the common algorithm in a base class (or abstract class) as a template method. The template method contains fixed steps of the algorithm that should not be modified and calls abstract or hook methods that the subclasses can override to provide their own implementation.

Key components of the Template Method pattern:

  1. Abstract Class (or Base Class): The abstract class defines the skeleton of the algorithm by providing a template method that orchestrates the steps of the algorithm. It may also include default implementations for some steps. It contains fixed steps of the algorithm and may include abstract methods or hook methods that can be overridden by subclasses.
  2. Concrete Classes (Subclasses): These classes inherit from the abstract class and provide concrete implementations for the abstract or hook methods. Each subclass can customize specific steps of the algorithm without changing the overall structure.
  3. Template Method: The template method is the main method in the abstract class that defines the structure of the algorithm. It calls the individual steps, including both the common steps and the ones to be overridden by subclasses.
  4. Hook Methods: Hook methods are optional methods in the abstract class that subclasses can choose to override if they need additional customization points within the algorithm.

Implementation details

The Template Method design pattern in Java can be implemented by defining a base class with a template method that implements the algorithm, and providing hooks or abstract methods for the subclasses to override. The subclasses then provide their own implementation for the hooks, if necessary to customize the behavior.

** The Template class does not necessarily have to leave the implementation to subclasses in its entirety. Instead, as part of providing the outline of the algorithm, the Template class can also provide some amount of implementation that can be considered as invariant across different implementations. It can even provide default implementation for the variant parts, if appropriate. Only specific details will be implemented inside different subclasses. This type of implementation eliminates the need for duplicate code, which means a minimum amount of code to be written.

  • Template method should consist of certain steps whose order is fixed and for some of
    the methods; implementation differs from base class to subclass. Template method should be final.
// Abstract Class (Template)
abstract class Beverage {


    // template method - that specifies the steps that define the algorithm
    public final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    // some of the steps common
    protected void boilWater() {
        System.out.println("Boiling water");
    }

    // some of the steps, the subclasses can provide specific implementation
    protected abstract void brew();

    protected void pourInCup() {
        System.out.println("Pouring into cup");
    }

    protected abstract void addCondiments();
}

// Concrete Class 1
class Coffee extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Brewing coffee");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar");
    }
}

// Concrete Class 2
class Tea extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Steeping tea bag");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon");
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Beverage coffee = new Coffee();
        Beverage tea = new Tea();

        System.out.println("Preparing Coffee:");
        coffee.prepareBeverage();

        System.out.println("\nPreparing Tea:");
        tea.prepareBeverage();
    }
}

Output :

Preparing Coffee:
Boiling water
Brewing coffee
Pouring into cup
Adding milk and sugar

Preparing Tea:
Boiling water
Steeping tea bag
Pouring into cup
Adding lemon

In this example, the Beverage class is the abstract class that defines the template method prepareBeverage(), which outlines the beverage preparation process. It includes fixed steps (boilWater() and pourInCup()) and abstract methods (brew() and addCondiments()). The Coffee and Tea classes are concrete subclasses that extend Beverage and provide their specific implementations for brew() and addCondiments().

The Template Method pattern allows the common steps of the beverage preparation process to be shared among the different beverage types while allowing each type to define its unique way of brewing and adding condiments. This leads to better code organization, reusability, and maintainability.

The Template Method pattern is useful in situations where you want to provide a default implementation for a certain algorithm, while allowing subclasses to provide their own specific implementation details for certain steps.


Use cases

  • To implement the invariant parts of an algorithm once and leave it up to subclasses to
    implement the behavior that can vary.
  • When common behavior among subclasses should be factored and localized in a
    common class to avoid code duplication. You first identify the differences in the existing
    code and then separate the differences into new operations. Finally, you replace the
    differing code with a template method that calls one of these new operations.

Template Method Design Pattern – Simple implementation

Abstract Base class – Game

package com.rndayala.designpatterns.templatemethod;

public abstract class Game {
        // this is common method to initalize the video game
	public void initialize() {
		System.out.println("Welcome to EA Sports. Game Initialized..");
	}
	abstract void startPlay();
	abstract void endPlay();
	
	// template method - we mark it as final
	public final void play() {
		initialize();
		startPlay();
		endPlay();
	}

}

Concrete Class – Cricket

package com.rndayala.designpatterns.templatemethod;

public class Cricket extends Game {
	
	@Override
	void startPlay() {
		System.out.println("Cricket Game started. Enjoy the Game!");
	}

	@Override
	void endPlay() {
		System.out.println("Cricket Game Finished.");
	}
}

Concrete Class – Football

package com.rndayala.designpatterns.templatemethod;

public class Football extends Game {
	
	@Override
	void startPlay() {
		System.out.println("Football Game started. Enjoy the Game!");
	}

	@Override
	void endPlay() {
		System.out.println("Football Game Finished.");
	}
}

Client code / Demo

package com.rndayala.designpatterns.templatemethod;

public class Demo {
	public static void main(String[] args) {
		Game game = new Cricket();
		game.play();
		
		System.out.println();
		
		game = new Football();
		game.play();
	}
}

Output :

Welcome to EA Sports. Game Initialized..
Cricket Game started. Enjoy the Game!
Cricket Game Finished.

Welcome to EA Sports. Game Initialized..
Football Game started. Enjoy the Game!
Football Game Finished.

The Template Method design pattern is used in situations where you want to define the skeleton of an algorithm, but allow subclasses to provide the implementation for some of the steps.

When to use the template method design pattern is when you are implementing a common task that has multiple steps, and some of the steps may change based on specific requirements. By using template method pattern, you can define the basic steps and let subclasses implement the specific details for each step. This way, you can maintain the common interface, but still provide the flexibility to change the algorithm as needed.


In Java, the Template Method pattern is widely used to define the structure of algorithms while allowing subclasses to provide specific implementations for certain steps.

Observer Design Pattern

What is an Observer Design Pattern

The Observer design pattern is a behavioral software design pattern that is used to establish a one-to-many dependency between objects. In this pattern, when one object (known as the subject) changes its state, all its dependents (known as observers) are automatically notified and updated accordingly.

It allows multiple objects to be notified of changes in the state of another object without requiring them to know the specifics of the subject.

The Observer Design Pattern is a way for one object, known as the subject, to send updates to multiple other objects, known as observers, when it changes.

An example of this pattern in real life could be a weather service sending updates to different weather apps when the weather changes. The weather service is the subject and the weather apps are the observers.


Explanation

The Observer Design Pattern is a way for one object, known as the subject, to notify multiple other objects, known as observers, about changes in its state. The subject maintains a list of its observers and notifies them when its state changes.

In observer design pattern multiple observer objects registers with a subject for change notification. When the state of subject changes, it notifies the observers.

Objects that listen or watch for change are called observers and the object that is being watched for is called subject.

Pattern involved is also called as publish-subscribe pattern.

  • Subject provides interface for observers to register and unregister themselves with the
    subject.
  • Subject knows who its subscribers are.
  • Multiple observers can subscribe for notifications.
  • Subject publishes the notifications.
  • Subject just sends the notification saying the state has changed. It does not pass any state information.
  • Once the notification is received from subject, observers call the subject and get data
    that is changed.

In some implementations, along with the notification, state is also passed so that the observer need not query back to know the status. It is better not to do this way.

There are 4 participants in the Observer pattern:

  • Subject, which is used to register observers. Objects use this interface to register as
    observers and also to remove themselves from being observers.
  • Observer defines an updating interface for objects that should be notified of changes in a subject. All observers need to implement the Observer interface. This interface has a method update (), which gets called when the Subject’s state changes.
  • ConcreteSubject, stores the state of interest to ConcreteObserver objects. It sends a
    notification to its observers when its state changes. A concrete subject always implements the Subject interface. The notifyObservers () method is used to update all the current observers whenever the state changes.
  • ConcreateObserver maintains a reference to a ConcreteSubject object and implements the Observer interface. Each observer registers with a concrete subject to receive updates.

This pattern can be useful in situations where multiple objects need to stay updated with the state of a single object, and the objects do not need to interact directly with each other.

This pattern is widely used in many different applications, such as GUI applications, event-driven systems, and reactive programming. It is a fundamental pattern that can help you to design more flexible and scalable systems.

Benefits

The Observer design pattern offers several benefits, making it a valuable tool in software development. Here are some of the key benefits of using the Observer pattern:

  1. Loose coupling: The Observer pattern promotes loose coupling between the subject and its observers. Observers don’t need to know the specifics of the subject’s implementation; they only rely on the common Observer interface. This reduces the dependencies between classes, making the code more maintainable and flexible.
  2. Extensibility: Introducing new observers becomes easy. You can create new observer classes without modifying the subject. This makes it simple to add new functionalities to a system without affecting existing code.
  3. Reusability: Observers can be reused in different contexts with different subjects. This reusability is possible because of the separation of concerns provided by the Observer pattern.
  4. Event handling: The Observer pattern is commonly used in event-driven systems. When an event occurs, the subject notifies its observers, and they can respond to the event accordingly. This facilitates a clean and efficient way of handling events in the application.
  5. Decoupled UI components: In graphical user interfaces (GUIs), the Observer pattern is often used to ensure that the UI components are decoupled from the underlying data. UI components can register themselves as observers to receive updates when the data changes, allowing for a responsive and synchronized user interface.
  6. Real-time updates: The Observer pattern is useful in scenarios where real-time updates are needed. For example, in chat applications or stock market monitoring systems, observers can be notified immediately when new messages or stock prices arrive.
  7. Maintainability: By separating the concerns of the subject and its observers, the codebase becomes easier to maintain. Changes to one part of the system are less likely to affect other parts, reducing the risk of introducing bugs and making it easier to refactor or add new features.
  8. Scalability: The Observer pattern enables a scalable architecture by allowing multiple observers to be added or removed dynamically at runtime. This is particularly valuable in large applications where different components need to react to changes in a subject independently.

Overall, the Observer design pattern provides a powerful mechanism for building flexible and decoupled systems, enabling better code organization and easier maintenance. It is widely used in various domains, including user interfaces, event handling, and real-time applications.

Implementation

Let us take a blog and subscriber example for observer design pattern sample implementation. Assume that there is a blog and users register to that blog for update.
When a new article is posted in the blog, it will send update to the registered users saying a new article is posted. Then the user will access the blog and read the new article posted. In this example, blog is the subject and user is the observer.

Subject interface

package com.rndayala.designpatterns.observable;

// Subject interface
public interface Subject {
	void registerObserver(Observer observer);
	void unregisterObserver(Observer observer);
	void notifyObservers();
	Object getUpdate();
}

Observer interface

package com.rndayala.designpatterns.observable;

// Observer interface
public interface Observer {
	void update(Subject subject);

}

Concrete Subject implementation – Blog class

package com.rndayala.designpatterns.observable;

import java.util.ArrayList;
import java.util.List;

// Concrete Subject class 
public class Blog implements Subject {
	// Concrete Subject maintains list of observers
	private List<Observer> observers = null;
	// this instance variable maintains the state of Concrete subject
	private String blogContent;
	
	public Blog() {
		System.out.println("Initializing subject(blog)..");
		this.observers = new ArrayList<Observer>();
		blogContent = "";
	}
	
	@Override
	public void registerObserver(Observer observer) {
		System.out.println("registering an observer!");
		observers.add(observer);		
	}
	
	@Override
	public void unregisterObserver(Observer observer) {
		System.out.println("un-registering an observer!");
		observers.remove(observer);
	}

        // when the state of subject changes, we need to notify observers
	public void postNewArticle(String data) {
		blogContent = data;
		notifyObservers();
	}
	
	@Override
	public void notifyObservers() {
                // for each observer, call the update() method allowing observer to react to the change in subject state		
		for (Observer observer : observers) {
			observer.update(this);
			System.out.println("Observer notified!!");
		}
	}
	
	@Override
	public Object getUpdate() {
		return blogContent;
	}
	
        // method to return the list of observers registered with the subject
	public List<Observer> getObserversList() {
		return observers;
	}
}

Concrete observer implementation – User class

package com.rndayala.designpatterns.observable;

public class User implements Observer {
	private Object article;


        // on invocation of update() method, the observer will update its own state.
	@Override
	public void update(Subject subject) {
		article = subject.getUpdate();		
	}

	public Object getArticle() {
		return article;
	}

}

Client code – Demo program

package com.rndayala.designpatterns.observable;

import java.util.List;

public class Demo {
	public static void main(String[] args) {
		Blog blog = new Blog();
		User user1 = new User();
		User user2 = new User();
		List<Observer> list = null;
		
		blog.registerObserver(user1);
		blog.registerObserver(user2);
		
                // change the state of subject by posting a new article
		blog.postNewArticle("Observer pattern Explained!");

		list = blog.getObserversList();
		
		for(Observer observer : list) {
			System.out.println("Get content : " + ((User)observer).getArticle());
		}		
		
                // remove an observer		
		blog.unregisterObserver(user2);

		blog.postNewArticle("Singleton pattern Explained!");

		list = blog.getObserversList();
		
		for(Observer observer : list) {
			System.out.println("Get content : " + ((User)observer).getArticle());
		}

	}
}

Output :

Initializing subject(blog)..
registering an observer!
registering an observer!
Observer notified!!
Observer notified!!
Get content : Observer pattern Explained!
Get content : Observer pattern Explained!
un-registering an observer!
Observer notified!!
Get content : Singleton pattern Explained!

When the state of the subject changes, it calls the notifyObservers() method which in turn calls the update method on each of its observers, allowing them to react to the change in subject’s state.


Use cases

The Observer design pattern is typically used in situations where there is a one-to-many relationship between objects and when changes in one object need to be reflected in other objects.

Some common use cases of the Observer pattern are:

1. Implementing a model-view-controller architecture where changes in the model are notified to the views.

2. Implementing event-driven systems, such as user interfaces, where changes in one component trigger updates in other components.

3. Implementing a publish-subscribe system where events are published to multiple subscribers.

4. Implementing a logging system, where changes in the log data need to be notified to multiple log listeners.

5. Implementing a stock ticker system, where changes in the stock prices need to be notified to multiple subscribers.

In all these use cases, the Observer pattern allows the objects to be loosely coupled, so that changes in one object don’t affect the other objects directly. Instead, the changes are notified to the objects that need to be updated.


Observer Design Pattern implementation using Weather station scenario

Here’s a Java code example of the Observer design pattern using the weather station scenario:

When the weather station’s temperature changes, it notifies all its attached observers (TemperatureDisplay and Fan). The TemperatureDisplay then prints the updated temperature, while the Fan turns on or off based on the temperature threshold.

import java.util.ArrayList;
import java.util.List;

// Observer interface
interface Observer {
    void update(int temperature);
}

// Subject
class WeatherStation {
    private List<Observer> observers = new ArrayList<>();
    private int temperature;

    public void attachObserver(Observer observer) {
        observers.add(observer);
    }

    public void detachObserver(Observer observer) {
        observers.remove(observer);
    }

    public void setTemperature(int temperature) {
        this.temperature = temperature;
        notifyObservers();
    }

    // here, while notifying observer, we are sending the state also
    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature);
        }
    }
}

// Concrete Observer
class TemperatureDisplay implements Observer {
    @Override
    public void update(int temperature) {
        System.out.println("Temperature Display: " + temperature + " degrees Celsius");
    }
}

// Concrete Observer
class Fan implements Observer {
    @Override
    public void update(int temperature) {
        if (temperature > 25) {
            System.out.println("Fan: Turning on the fan.");
        } else {
            System.out.println("Fan: Turning off the fan.");
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();
        TemperatureDisplay tempDisplay = new TemperatureDisplay();
        Fan fan = new Fan();

        weatherStation.attachObserver(tempDisplay);
        weatherStation.attachObserver(fan);

        weatherStation.setTemperature(20);
        weatherStation.setTemperature(30);
    }
}

Output :

Temperature Display: 20 degrees Celsius
Fan: Turning off the fan.
Temperature Display: 30 degrees Celsius
Fan: Turning on the fan.

This example demonstrates how the WeatherStation subject notifies its attached observers (TemperatureDisplay and Fan) about changes in the temperature, and each observer reacts accordingly.

Builder Design Pattern

What is a Builder Design Pattern

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

It decouples the construction process from the object representation, allowing for the step-by-step creation of objects with different configurations.

The need ?

Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters. Or even worse: scattered all over the client cod

In general, the details of object construction – the constructors, such as instantiating and initializing the components that make up the object, are kept within the object, often as part of its constructor. This type of design closely ties the object construction process with the components that make up the object. This approach is suitable as long as the object under construction is simple and the object construction process is definite and always produces the same representation of the object.

However, this design may not be effective when the object being created is complex and the series of steps constituting the object creation process can be implemented in different ways, thus producing different representations of the object.

If we try to keep all such instantiation steps within the object, the object can become bulky (construction bloat) and less modular. Subsequently, adding a new implementation or making changes to an existing implementation requires changes to the existing code.

The Idea / Intent

The Builder pattern suggests that you extract the object construction code out of its own class and move it to separate objects called builders.

The Builder pattern suggests moving the construction logic out of the object class to a separate class referred to as a builder class. There can be more than one such builder classes, each with different implementations for the series of steps to construct the object. Each builder implementation results in a different representation of the object.

The intent of the Builder Pattern is to separate the construction of a complex object
from its representation
, so that the same construction process can create different
representations.

This type of separation reduces the object size.

Adding a new implementation (i.e., adding a new builder) becomes easier. The object construction process becomes independent of the components that make up the object. This provides more control over the object construction process.

Explanation

Builder doesn’t require products to have a common interface. That makes it possible to produce different products using the same construction process.

Builder pattern allows you to create different configurations of an object step by step, providing a more flexible and readable way to construct objects with many optional parameters.

The main components of the Builder Design Pattern are:

  1. Director (optional) : The Director is responsible for directing the construction of the complex object using the Builder. It controls the order and sequence of the steps required to build the object using the Builder. It is not always necessary to have a Director.
  2. Builder Interface / Abstract class : The Builder is an interface or an abstract class that declares the construction steps and methods for creating a complex object. It typically includes methods for setting various attributes and returning the final product.
  3. Concrete Builder: Concrete Builders are implementations of the Builder interface that provide specific implementation details for constructing different parts of the complex object. Each Concrete Builder is responsible for building a particular variant of the object. It also, Provides an interface for retrieving the product.
  4. Product: The Product is the complex object being constructed. It typically contains multiple attributes and configurations. It is the final object resulting from the Builder’s construction process.

The Builder pattern suggests using a dedicated object referred to as a Director, which is responsible for invoking different builder methods required for the construction of the final object.

–> Once the object is constructed, the client object can directly request from the builder the fully constructed object. To facilitate this process, a new method getObject() can be declared in the common Builder interface to be implemented by different concrete builders.

Ref – https://refactoring.guru/design-patterns/builder

Benefits

The Builder pattern can be applied when construction of various representations of the product involves similar steps that differ only in the details.

The same construction process can create different representations.

Examples

Usage examples: The Builder pattern is a well-known pattern in Java world. It’s especially useful when you need to create an object with lots of possible configuration options.

Builder is widely used in Java core libraries:

Identification: The Builder pattern can be recognized in a class, which has a single creation method and several methods to configure the resulting object. Builder methods often support chaining (for example, someBuilder.setValueA(1).setValueB(2).create()).

Implementation

CarType

package com.rndayala.designpatterns.builder;

// Enum that sepcifies the type of Car
public enum CarType {
    CITY_CAR, SPORTS_CAR, SUV
}

Product feature 1 : Engine

package com.rndayala.designpatterns.builder;

/**
 * Just another feature of a Car product.
 */
public class Engine {
    private final double volume;
    private double mileage;
    private boolean started;

    public Engine(double volume, double mileage) {
        this.volume = volume;
        this.mileage = mileage;
    }

    public void on() {
        started = true;
    }

    public void off() {
        started = false;
    }

    public boolean isStarted() {
        return started;
    }

    public void go(double mileage) {
        if (started) {
            this.mileage += mileage;
        } else {
            System.err.println("Cannot go(), you must start engine first!");
        }
    }

    public double getVolume() {
        return volume;
    }

    public double getMileage() {
        return mileage;
    }
}

Product feature 2 : Transmission

/**
 * Just another feature Car product that specifies the type of Transmission.
 */
public enum Transmission {
    SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}

Product feature 3 : TripComputer

package com.rndayala.designpatterns.builder;

/**
 * Just another feature of Car product.
 */
public class TripComputer {

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    public void showFuelLevel() {
        System.out.println("Fuel level: " + car.getFuel());
    }

    public void showStatus() {
        if (this.car.getEngine().isStarted()) {
            System.out.println("Car is started");
        } else {
            System.out.println("Car isn't started");
        }
    }
}

Product feature 4 : GPSNavigator

package com.rndayala.designpatterns.builder;

/**
 * Just another feature of a car.
 */
public class GPSNavigator {
    private String route;

    public GPSNavigator() {
        this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";
    }

    public GPSNavigator(String manualRoute) {
        this.route = manualRoute;
    }

    public String getRoute() {
        return route;
    }
}

Concrete Product : Car

package com.rndayala.designpatterns.builder;

/**
 * Car is a product class.
 * Product is made up of different components which vary in details for different Product class types.
 */
public class Car {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;
    private double fuel = 0;

    public Car(CarType carType, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        if (this.tripComputer != null) {
            this.tripComputer.setCar(this);
        }
        this.gpsNavigator = gpsNavigator;
    }

    public CarType getCarType() {
        return carType;
    }

    public double getFuel() {
        return fuel;
    }

    public void setFuel(double fuel) {
        this.fuel = fuel;
    }

    public int getSeats() {
        return seats;
    }

    public Engine getEngine() {
        return engine;
    }

    public Transmission getTransmission() {
        return transmission;
    }

    public TripComputer getTripComputer() {
        return tripComputer;
    }

    public GPSNavigator getGpsNavigator() {
        return gpsNavigator;
    }
}

Concrete Product : Manual

package com.rndayala.designpatterns.builder;

/**
 * Car manual is another product. Note that it does not have the same ancestor
 * as a Car. They are not related.
 * 
 * Builder doesn’t require products to have a common interface.
 * That makes it possible to produce different products using the same construction process.
 */
public class Manual {
    private final CarType carType;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;

    public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
                  TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.carType = carType;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public String print() {
        String info = "";
        info += "Type of car: " + carType + "\n";
        info += "Count of seats: " + seats + "\n";
        info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n";
        info += "Transmission: " + transmission + "\n";
        if (this.tripComputer != null) {
            info += "Trip Computer: Functional" + "\n";
        } else {
            info += "Trip Computer: N/A" + "\n";
        }
        if (this.gpsNavigator != null) {
            info += "GPS Navigator: Functional" + "\n";
        } else {
            info += "GPS Navigator: N/A" + "\n";
        }
        return info;
    }
}

Builder Interface

package com.rndayala.designpatterns.builder;

/**
 * Builder interface defines all possible ways to configure a product.
 * The interface declares all the methods to construct the complex object.
 */
public interface Builder {
    void setCarType(CarType type);
    void setSeats(int seats);
    void setEngine(Engine engine);
    void setTransmission(Transmission transmission);
    void setTripComputer(TripComputer tripComputer);
    void setGPSNavigator(GPSNavigator gpsNavigator);
}

Concrete Builder class : CarBuilder

package com.rndayala.designpatterns.builder;

/**
 * Concrete builders implements all steps defined in the common interface.
 * It provides specific implementations for constructing different parts of the complex object.
 */
public class CarBuilder implements Builder {
    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    // Concrete Builder - provides a method for retrieving the final product.
    public Car getResult() {
        return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

Concrete Builder class : CarManualBuilder

package com.rndayala.designpatterns.builder;

/**
 * Unlike other Creational patterns, Builder can construct unrelated products,
 * which don't have the common interface.
 *
 * In this case we build a user manual for a car, using the same steps as we
 * built a car. This allows to produce manuals for specific car models,
 * configured with different features.
 */
public class CarManualBuilder implements Builder{
    private CarType type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setCarType(CarType type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Manual getResult() {
        return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

Here, we have two unrelated product classes and their builder classes. The builders of these products follow the same construction steps.

Director

The Director class uses the builder object and specifies the ordering or sequence of steps to construct the object.

package com.rndayala.designpatterns.builder;

/**
 * This Director approach is used when we want to build different unrelated products.
 * However, those products use the same object construction steps.
 * If you observe, the construction methods are not returning any object. 
 * Director only specifies the sequence of steps, but does not know what product is being built.
 * 
 * Director defines the sequence/order of building steps. It works with a builder object
 * through common Builder interface. Therefore it may not know what product is
 * being built.
 */

public class Director {

    public void constructSportsCar(Builder builder) {
    	// specifies the sequence or order of the steps
        builder.setCarType(CarType.SPORTS_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(3.0, 0));
        builder.setTransmission(Transmission.AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());

    }

    public void constructCityCar(Builder builder) {
    	// specifies the sequence or order of the steps
        builder.setCarType(CarType.CITY_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(1.2, 0));
        builder.setTransmission(Transmission.SEMI_AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructSUV(Builder builder) {
    	// specifies the sequence or order of the steps
        builder.setCarType(CarType.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }
}

Demo / Client code

package com.rndayala.designpatterns.builder;

/**
 * Demo class. Everything comes together here.
 */
public class Demo {

    public static void main(String[] args) {
        Director director = new Director();

        // Director gets the concrete builder object from the client
        // (application code). That's because application knows better which
        // builder to use to get a specific product.
        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);

        // The final product is often retrieved from a builder object, since
        // Director is not aware and not dependent on concrete builders and
        // products.
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getCarType());


        CarManualBuilder manualBuilder = new CarManualBuilder();

        // Director may know several building recipes.
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }

}

Builder Design Pattern implementation using Inner class

The Builder pattern is a creational design pattern that is used to construct complex objects step by step. It separates the construction of the object from its representation, allowing you to create different variations of the same object with a consistent construction process.

When using the Builder pattern with an inner class in Java, the inner class is responsible for building the complex object and accessing the private fields of the outer class. This way, the inner class can set the values of the attributes of the outer class.

Let’s create an example of a complex object called Person using the Builder pattern with an inner class:

// in this builder design pattern implementation, we are using Builder as inner class.
// the inner class has access to private instance variable of the outer class.
public class Person {
    private final String firstName; // mandatory attribute
    private final String lastName;  // mandatory attribute
    private final int age;  // optional
    private final String address;  // optional

    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.address = builder.address;
    }

    // Getter methods (could be omitted for brevity)

    public static class Builder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String address;

        // we set the mandatory attributes using the constructor
        public Builder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        // optional attributes are set using the builder methods
        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        // The build() method in the Builder class constructs the Person object 
        // using the private constructor of the outer class.
        public Person build() {
            return new Person(this);
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person.Builder("John", "Doe")
                .age(30)
                .address("123 Main Street")
                .build();

        Person person2 = new Person.Builder("Jane", "Smith")
                .age(25)
                .build();

        System.out.println(person1); // Person [firstName=John, lastName=Doe, age=30, address=123 Main Street]
        System.out.println(person2); // Person [firstName=Jane, lastName=Smith, age=25, address=null]
    }
}

In this example, the Person class is the complex object we want to construct. It has private fields firstName, lastName, age, and address, and a private constructor that takes a Builder object to set its attributes.

The inner class Builder provides methods to set the optional attributes of the Person object (age and address). The build() method in the Builder class constructs the Person object using the private constructor of the outer class.

By using the Builder pattern with an inner class, we can create a Person object with a clear and expressive API, specifying only the attributes we need, and leaving out the optional ones.


Use cases

Use the Builder pattern when you want your code to be able to create different representations of some product.

 The Builder pattern can be applied when construction of various representations of the product involves similar steps that differ only in the details.

same construction steps, but differ in details

The base builder interface defines all possible construction steps, and concrete builders implement these steps to construct particular representations of the product. Meanwhile, the director class guides the order of construction.

Use the Builder pattern when :

  • The algorithm for creating a complex object should be independent of the parts that
    make up the object and how they’re assembled.
  • The construction process must allow different representations for the object that’s
    constructed.

Here’s a simplified example to illustrate the components of the Builder Design Pattern:

// Product
class Car {
    private String brand;
    private String model;
    private String color;
    private int year;
    // Other attributes...

    public Car(String brand, String model, String color, int year) {
        this.brand = brand;
        this.model = model;
        this.color = color;
        this.year = year;
        // Other attribute assignments...
    }

    // Getters and other methods...
}

// Builder Interface
interface CarBuilder {
    CarBuilder setBrand(String brand);
    CarBuilder setModel(String model);
    CarBuilder setColor(String color);
    CarBuilder setYear(int year);
    Car build();
}

// Concrete Builder
class ConcreteCarBuilder implements CarBuilder {
    private String brand;
    private String model;
    private String color;
    private int year;

    public CarBuilder setBrand(String brand) {
        this.brand = brand;
        return this;
    }

    public CarBuilder setModel(String model) {
        this.model = model;
        return this;
    }

    public CarBuilder setColor(String color) {
        this.color = color;
        return this;
    }

    public CarBuilder setYear(int year) {
        this.year = year;
        return this;
    }

    public Car build() {
        return new Car(brand, model, color, year);
    }
}

// Director
class CarDirector {
    public Car buildCar(CarBuilder builder) {
        return builder.setBrand("Toyota")
                      .setModel("Corolla")
                      .setColor("Silver")
                      .setYear(2023)
                      .build();
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        CarBuilder carBuilder = new ConcreteCarBuilder();
        CarDirector director = new CarDirector();

        Car car = director.buildCar(carBuilder);
        System.out.println(car);
    }
}

In this example, the Car class represents the Product, the CarBuilder is the Builder interface, the ConcreteCarBuilder is the Concrete Builder, and the CarDirector is the Director. The Client code interacts with the Director to build the complex object using the Builder. The Builder pattern allows you to add new Concrete Builders for different types of products without modifying the Client code or the Director. This flexibility makes it easier to manage and create complex objects with many optional attributes.

Refer – https://github.com/rndayala/TechieSkills/tree/main/Java-Examples/CoreJavaExamples/src/com/rndayala/designpatterns/builder2


Another implementation – using Inner class

// Product Class - Computer
public class Computer {
    private String cpu;
    private int ram;
    private int storage;

    // Constructor (private to enforce object creation through builder)
    private Computer(String cpu, int ram, int storage) {
        this.cpu = cpu;
        this.ram = ram;
        this.storage = storage;
    }

    // Getters
    public String getCpu() {
        return cpu;
    }

    public int getRam() {
        return ram;
    }

    public int getStorage() {
        return storage;
    }

    // Inner Builder Class
    public static class ComputerBuilder {
        private String cpu;
        private int ram;
        private int storage;

        public ComputerBuilder setCPU(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public ComputerBuilder setRAM(int ram) {
            this.ram = ram;
            return this;
        }

        public ComputerBuilder setStorage(int storage) {
            this.storage = storage;
            return this;
        }

        public Computer build() {
            return new Computer(cpu, ram, storage);
        }
    }
}


// Using the Builder - Application.java
public class Application {
    public static void main(String[] args) {
        Computer computer = new Computer.ComputerBuilder()
                .setCPU("Intel i7")
                .setRAM(16)
                .setStorage(512)
                .build();

        System.out.println("CPU: " + computer.getCpu());
        System.out.println("RAM: " + computer.getRam() + "GB");
        System.out.println("Storage: " + computer.getStorage() + "GB");
    }
}
  • The Computer class represents the complex object being built. It contains attributes such as the CPU, RAM, and storage.
  • The ComputerBuilder class is an inner static class within the Computer class, responsible for constructing the Computer object step by step.
  • The ComputerBuilder class provides setter methods for each attribute, allowing customization of the object being built.
  • The build() method in the ComputerBuilder class constructs and returns the final Computer object based on the configured attributes.

To build an Computer object with specific configurations, you can use the ComputerBuilder and chain the setter methods to customize the object.

Abstract Factory Design Pattern

What is an Abstract Factory Design Pattern

It’s more like factory of factories.

An abstract factory is a factory that returns factories. Why is this layer of abstraction useful? A normal factory can be used to create sets of related objects. An abstract factory returns factories. Thus, an abstract factory is used to return factories that can be used to create sets of related objects.

Abstract Factory is a creational design pattern, which solves the problem of creating entire product families without specifying their concrete classes.

** Abstract Factory defines an interface for creating all distinct products but leaves the actual product creation to concrete factory classes. Each factory type corresponds to a certain product variety.

In Abstract Factory pattern, we get rid of if-else block and have a concrete factory class for each sub-class and then an Abstract Factory class that will return the sub-class based on the input factory class.

The client code calls the creation methods of a factory object instead of creating products directly with a constructor call (new operator). Since a factory corresponds to a single product variant, all its products will be compatible.

Explanation – Understanding the pattern

This example illustrates how the Abstract Factory pattern can be used for creating cross-platform UI elements without coupling the client code to concrete UI classes, while keeping all created elements consistent with a selected operating system.

Ref – https://refactoring.guru/design-patterns/abstract-factory

The same UI elements in a cross-platform application are expected to behave similarly, but look a little bit different under different operating systems. Moreover, it’s your job to make sure that the UI elements match the style of the current operating system. You wouldn’t want your program to render macOS controls when it’s executed in Windows.

It works like this: when an application launches, it checks the type of the current operating system. The app uses this information to create a factory object from a class that matches the operating system. The rest of the code uses this factory to create UI elements. This prevents the wrong elements from being created.

The Abstract Factory interface declares a set of creation methods that the client code can use to produce different types of UI elements. Concrete factories correspond to specific operating systems and create the UI elements that match that particular OS.

Benefits

  • Abstract Factory pattern provides approach to code for interface rather than
    implementation.
  • Abstract Factory pattern is “factory of factories” and can be easily extended to
    accommodate more products, for example we can easily add Material theme product family.
  • Abstract Factory pattern is robust and avoid conditional logic of Factory pattern.

When to use: A family of related product objects is designed to be used together, and you need to enforce this constraint.

Examples

Usage examples: The Abstract Factory pattern is pretty common in Java code. Many frameworks and libraries use it to provide a way to extend and customize their standard components.

Here are some examples from core Java libraries:

Identification: The pattern is easy to recognize by methods, which return a factory object. Then, the factory is used for creating specific sub-components.

Implementation

In our example, we create families of cross-platform GUI components and their production. The components, buttons and checkboxes will act as products. They have two variants: macOS and Windows.

The abstract factory defines an interface for creating buttons and checkboxes. There are two concrete factories, which return both products in a single variant.

Client code works with factories and products using abstract interfaces. It makes the same client code working with many product variants, depending on the type of factory object.

buttons: First product hierarchy

Button.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * Abstract Factory assumes that you have several families of products,
 * structured into separate class hierarchies (Button/Checkbox). All products of
 * the same family have the common interface.
 *
 * This is the common interface for buttons family.
 */
public interface Button {
    void render();
}

MacOSButton.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is a MacOS variant of a button.
 */
public class MacOSButton implements Button {

    @Override
    public void render() {
        System.out.println("You have created MacOSButton.");
    }
}

WindowsButton.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is another variant of a button.
 */
public class WindowsButton implements Button {

    @Override
    public void render() {
        System.out.println("You have created WindowsButton.");
    }
}

checkboxes: Second product hierarchy

Checkbox.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * Checkboxes is the second product family. It has the same variants as buttons.
 */
public interface Checkbox {
    void render();
}

MacOSCheckbox.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is a variant of a checkbox.
 */
public class MacOSCheckbox implements Checkbox {

    @Override
    public void render() {
        System.out.println("You have created MacOSCheckbox.");
    }
}

WindowsCheckbox.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is another variant of a checkbox.
 */
public class WindowsCheckbox implements Checkbox {

    @Override
    public void render() {
        System.out.println("You have created WindowsCheckbox.");
    }
}

Abstract factory : GUIFactory.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * Abstract factory knows about all (abstract) product types.
 * It declares a set of creation methods for product types.
 */
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

MacOSFactory.java: Concrete factory (macOS)

package com.rndayala.designpatterns.abstractfactory;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class MacOSFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

WindowsFactory.java: Concrete factory (Windows)

package com.rndayala.designpatterns.abstractfactory;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class WindowsFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

Client code : Application.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * Factory users don't care which concrete factory they use since they work with
 * factories and products through abstract interfaces.
 */
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void render() {
        button.render();
        checkbox.render();
    }
}

App configuration : Demo.java

package com.rndayala.designpatterns.abstractfactory;

/**
 * Demo class. Everything comes together here.
 */
public class Demo {

    /**
     * Application picks the factory type and creates it in run time (usually at
     * initialization stage), depending on the configuration or environment
     * variables.
     */
    private static Application configureApplication() {
        Application app;
        GUIFactory factory;
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("mac")) {
            factory = new MacOSFactory();
        } else {
            factory = new WindowsFactory();
        }
        app = new Application(factory);
        return app;
    }

    public static void main(String[] args) {
        Application app = configureApplication();
        app.render();
    }
}

Ref : https://refactoring.guru/design-patterns/abstract-factory


Use cases

  • Use the Abstract Factory when your code needs to work with various families of related products, but you don’t want it to depend on the concrete classes of those products—they might be unknown beforehand or you simply want to allow for future extensibility.
  • The Abstract Factory provides you with an interface for creating objects from each class of the product family. As long as your code creates objects via this interface, you don’t have to worry about creating the wrong variant of a product which doesn’t match the products already created by your app.