এই পর্বে যা শিখবেন
- Design pattern-এর সংজ্ঞা — সমস্যা, প্রেক্ষাপট, শর্ত, সমাধান আর ফলাফল — আর algorithm, library বা idiom থেকে এর পার্থক্য
- Alexander থেকে Gang of Four পর্যন্ত ইতিহাস, আর ২৩টি GoF pattern-এর নাম ও পরিবার নির্ভুলভাবে
- GoF-এর বিবরণের ছাঁচ পড়া, আর একটি pattern-এর class diagram দেখে ভূমিকাগুলো চেনা
- আধুনিক ভাষায় pattern-এর বদলে যাওয়া রূপ, GoF-এর বাইরের pattern-এর জগৎ আর anti-pattern
- সমস্যা থেকে pattern বেছে নেওয়া, অতিরিক্ত pattern সরিয়ে কোড সরল করা, আর review-ইন্টারভিউতে pattern-এর ভাষা
১ · Design pattern কী — নাম দেওয়া, বারবার ব্যবহারযোগ্য সমাধান
মডিউল ৫-এ আপনি নকশার নীতি শিখেছেন: coupling-cohesion (পর্ব ২৫), DRY-KISS-YAGNI (পর্ব ২৬), SOLID (পর্ব ২৭-২৮), আর UML-এ নকশা আঁকা (পর্ব ২৯-৩০)। নীতি বলে কোন দিকে যেতে হবে। কিন্তু প্রতিদিনের কাজে প্রশ্নটা আরও নির্দিষ্ট: “এই ধরনের সমস্যা আগে কেউ সমাধান করেছে? কীভাবে?” এই প্রশ্নের জমানো উত্তরগুলোর নাম design pattern.
একটি design pattern হলো একটি নির্দিষ্ট প্রেক্ষাপটে (context) বারবার ফিরে আসা নকশার একটি সমস্যার নাম দেওয়া, পুনর্ব্যবহারযোগ্য সমাধান। এর চারটি অপরিহার্য অংশ — Gang of Four-এর ভাষায়: নাম (আলোচনার শব্দ), সমস্যা (কখন লাগে), সমাধান (কোন অংশ, কী সম্পর্ক, কার কী দায় — নির্দিষ্ট কোড নয়), আর ফলাফল (লাভ আর দাম)।
পাড়ার দর্জির কাছে একটি কাগজের প্যাটার্ন থাকে — পাঞ্জাবির কাঁধ, হাতা আর কলারের ছাঁচ। প্রতিটি খদ্দেরের জন্য তিনি একই ছাঁচ নেন, কিন্তু মাপ নিয়ে বড়-ছোট করেন, কাপড় বুঝে সেলাই বদলান। ছাঁচ নিজে পাঞ্জাবি নয়; আবার প্রতিবার শূন্য থেকে নকশাও আঁকতে হয় না। আর দুজন দর্জি “চাইনিজ কলার” বললেই দুজনে একই জিনিস বোঝেন — লম্বা বর্ণনা লাগে না। Software-এর pattern ঠিক এমন: ছাঁচ, শব্দভান্ডার আর অভিজ্ঞতা — তিনটি একসাথে।
সংজ্ঞার প্রতিটি শব্দের ওজন আছে:
- বারবার ফিরে আসা: একবার দেখা চালাকি pattern নয়। Pattern-লেখকদের মধ্যে একটি প্রচলিত রীতি আছে — অন্তত তিনটি স্বাধীন বাস্তব ব্যবহার (known uses) না দেখলে কোনো সমাধানকে pattern বলা হয় না। একে অনেকে pattern-এর “rule of three” বলেন (পর্ব ২৬-এর refactoring-এর rule of three-এর থেকে আলাদা প্রসঙ্গ, তবে চিন্তা কাছাকাছি)।
- প্রেক্ষাপট: একই সমাধান এক প্রেক্ষাপটে ঠিক, আরেকটিতে ভুল।
- শর্ত (forces): যে চাহিদাগুলো একে অপরকে টানে — নমনীয়তা চাই, কিন্তু সরলতাও চাই; গতি চাই, কিন্তু মেমরি কম। Pattern এই টানাপোড়েনের একটি ভারসাম্য।
- ফলাফল: প্রতিটি pattern-এর দাম আছে — বেশি class, বেশি indirection, পড়তে বেশি সময়। দাম না জানলে pattern জানা অর্ধেক।
Pattern কী নয়
| ধারণা | কী | Pattern থেকে পার্থক্য |
|---|---|---|
| Algorithm | একটি হিসাবের নির্দিষ্ট ধাপ (যেমন binary search) | Algorithm বলে কী হিসাব; pattern বলে কোড কীভাবে সাজাবেন। Strategy pattern অনেক algorithm-কে বদলযোগ্য করে, নিজে কোনো algorithm নয়। |
| Library বা framework | লেখা, চালানো যায় এমন কোড | Library import করেন; pattern প্রতিবার নিজের সমস্যায় মানিয়ে লেখেন। তবে অনেক library ভেতরে pattern ব্যবহার করে। |
| Idiom | একটি ভাষার নিজস্ব ছোট কৌশল (যেমন Python-এর with, C++-এর RAII) | Idiom ভাষার স্তরে, ছোট; design pattern কয়েকটি class বা object-এর সম্পর্কের স্তরে। |
| Architectural pattern | পুরো system-এর কাঠামো (layered, MVC, microservice) | আরও বড় স্তর; পর্ব ৩৮-৪০-এ দেখবেন। |
২ · স্থপতির খাতা থেকে software-এ — Alexander থেকে Gang of Four
১৯৭৭ — একজন স্থপতি। Christopher Alexander আর তাঁর সহকর্মীরা (Sara Ishikawa, Murray Silverstein প্রমুখ) প্রকাশ করেন A Pattern Language: Towns, Buildings, Construction. বইটিতে ২৫৩টি pattern — অঞ্চল আর শহরের নকশা থেকে শুরু করে বাড়ি, ঘর আর জানালার খুঁটিনাটি পর্যন্ত। প্রতিটি pattern একই ছাঁচে লেখা: প্রেক্ষাপট, সমস্যা, শর্তের টানাপোড়েন, আর সমাধান; যেমন “প্রতিটি ঘরে দুই দিক থেকে আলো” (Light on Two Sides of Every Room). নামের “Language” শব্দটি গুরুত্বপূর্ণ — pattern language: pattern-গুলো একে অপরের সাথে যুক্ত, বড়টি ছোটগুলোকে ডাকে — শব্দ মিলে যেমন বাক্য হয়।
এর সঙ্গী বই The Timeless Way of Building (১৯৭৯)-এ Alexander একটি ধারণার কথা বলেন, যাকে তিনি “quality without a name” বলেছেন — ভালো জায়গার এমন এক প্রাণবন্ত, স্বস্তির গুণ, যা “সুন্দর” বা “কার্যকর”-এর মতো কোনো এক শব্দে ধরা যায় না। ধারণাটি দার্শনিক ও বিতর্কিত; software-এর লোকেরা একে কখনো কখনো উদ্ধৃত করেন ভালো কোডের “বসবাসযোগ্যতা” বোঝাতে। তবে মনে রাখবেন, এটি মাপা যায় এমন কোনো মানদণ্ড নয়।
১৯৮৭ — দুজন programmer. Kent Beck আর Ward Cunningham OOPSLA ১৯৮৭-এর একটি workshop-এ “Using Pattern Languages for Object-Oriented Programs” নামে একটি ছোট লেখা দেন। প্রচলিত বর্ণনা অনুযায়ী তাঁরা Smalltalk-এ window-ভিত্তিক interface নকশার জন্য মাত্র কয়েকটি (প্রায় পাঁচটি) pattern-এর একটি ছোট ভাষা বানিয়ে দেখান, যা নতুন ব্যবহারকারীরাও কাজে লাগাতে পেরেছিলেন। খুঁটিনাটি নিয়ে উৎসভেদে সামান্য পার্থক্য আছে।
১৯৯৪ — চারজন লেখক। Erich Gamma, Richard Helm, Ralph Johnson আর John Vlissides প্রকাশ করেন Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley; বইয়ে copyright ১৯৯৫ লেখা থাকলেও প্রকাশ ১৯৯৪-এর শেষে)। চার লেখকের ডাকনাম হয়ে যায় Gang of Four, সংক্ষেপে GoF. উদাহরণ ছিল C++ আর Smalltalk-এ। প্রায় একই সময়ে Hillside Group নামে একটি দল pattern নিয়ে লেখার সম্মেলন PLoP (Pattern Languages of Programs) শুরু করে, যার প্রথমটি ১৯৯৪ সালে।
Alexander-এর pattern ছিল মানুষের বসবাসের জায়গার জন্য, GoF-এর pattern class আর object-এর দায় ভাগের জন্য — কিন্তু ছাঁচটি একই থাকল। আর সবচেয়ে বড় উত্তরাধিকার হলো শব্দভান্ডার: “এখানে একটা Adapter বসাই” বললে আধা পাতার ব্যাখ্যা বেঁচে যায়।
GoF বইটি UML-এর আগের (UML-এর মানদণ্ড ১৯৯৭ সালে, পর্ব ২৯). বইয়ের diagram-গুলো OMT নামের একটি পুরোনো notation-এর কাছাকাছি ধাঁচে আঁকা। আজকের বেশিরভাগ বই আর লেখা একই কাঠামো UML-এ আবার আঁকে — তাই মূল বই খুললে তীর আর বাক্সের চেহারা একটু অচেনা লাগলে অবাক হবেন না।
৩ · GoF-এর ২৩টি pattern — তিন পরিবার আর মডিউল ৬-এর মানচিত্র
GoF pattern-গুলোকে দুইভাবে ভাগ করেছেন। প্রথম ভাগ উদ্দেশ্য (purpose) ধরে:
- Creational pattern — object তৈরির প্রক্রিয়া আড়াল করে: কে, কখন, কোন class-এর object বানাবে।
- Structural pattern — class আর object জোড়া দিয়ে বড় কাঠামো বানায়: মোড়ানো, সাজানো, সামনে একটি সরল দরজা।
- Behavioral pattern — object-গুলোর মধ্যে আচরণ আর দায় ভাগ করে: কে কাকে জানাবে, কে কোন ধাপ করবে, কোন algorithm চলবে।
দ্বিতীয় ভাগ পরিসর (scope) ধরে: কিছু pattern class-এর স্তরে, inheritance দিয়ে, compile-এর সময় ঠিক হয় (যেমন Factory Method, Template Method, Interpreter, আর Adapter-এর class রূপ); বাকি বেশিরভাগ object-এর স্তরে, composition দিয়ে, চলার সময় বদলানো যায়। দ্বিতীয় দলটি অনেক বড় — অংশ ৫-এ এর কারণ দেখবেন।
Gang of Four-এর ২৩টি pattern
The 23 GoF patterns by purpose: creational 5, structural 7, behavioral 11
নামগুলো এক লাইনে — বিস্তারিত পরের পর্বগুলোতে। যে চারটি pattern-এর আলাদা পর্ব নেই (Bridge, Flyweight, Interpreter, Memento), সেগুলোর পরিচয় এখানে একটু বেশি দেওয়া হলো।
| Pattern | এক লাইনে | পর্ব |
|---|---|---|
| Creational (৫) | ||
| Abstract Factory | পরস্পর-সম্পর্কিত object-এর একটি পুরো পরিবার, concrete class না জেনে তৈরি | ৩৩ |
| Builder | জটিল object ধাপে ধাপে গড়া; একই ধাপে ভিন্ন রূপ | ৩৩ |
| Factory Method | কোন class-এর object হবে, সেই সিদ্ধান্ত subclass-এর একটি method-এ ছেড়ে দেওয়া | ৩৩ |
| Prototype | একটি নমুনা object কপি করে নতুন object | ৩৩ |
| Singleton | একটি class-এর একটিমাত্র object আর তার একটি বৈশ্বিক প্রবেশপথ | ৩৩ |
| Structural (৭) | ||
| Adapter | অমিল interface-কে প্রত্যাশিত interface-এ রূপান্তর | ৩৪ |
| Bridge | Abstraction আর implementation দুটি আলাদা hierarchy-তে, যাতে দুটি স্বাধীনভাবে বাড়ে — যেমন ৩ রকম report × ২ রকম output-এ ৬টি subclass-এর বদলে ৩ + ২টি class | ৩২ |
| Composite | অংশ আর সমগ্রকে গাছের মতো সাজিয়ে একই ভাবে ব্যবহার | ৩৪ |
| Decorator | একটি object-কে মুড়ে চলার সময় নতুন দায় যোগ, subclass ছাড়া | ৩৪ |
| Facade | জটিল subsystem-এর সামনে একটি সরল দরজা | ৩৪ |
| Flyweight | হাজার হাজার ছোট object-এর ভাগযোগ্য অংশ একবার রেখে মেমরি বাঁচানো — যেমন editor-এ প্রতিটি অক্ষরের font-তথ্য আলাদা না রেখে ভাগ করা | ৩২ |
| Proxy | আসল object-এর সামনে একজন প্রতিনিধি — দেরিতে লোড, প্রবেশাধিকার, দূরের ডাক | ৩৪ |
| Behavioral (১১) | ||
| Chain of Responsibility | অনুরোধ একটি শিকল ধরে এগোয়, যে পারে সে সামলায় | ৩৬ |
| Command | একটি অনুরোধকে object বানানো — সারিতে রাখা, লগ করা, undo | ৩৫ |
| Interpreter | একটি ছোট ভাষার ব্যাকরণকে class-এর গাছ বানিয়ে বাক্যের মান বের করা — যেমন “total > 1000 AND district = Dhaka” ধরনের ছাড়ের নিয়ম | ৩২ |
| Iterator | ভেতরের গঠন না দেখিয়ে একটি collection একে একে ঘোরা | ৩৬ |
| Mediator | অনেক object-এর পরস্পর কথাবার্তা একটি মধ্যস্থের মাধ্যমে | ৩৬ |
| Memento | Encapsulation না ভেঙে একটি object-এর অবস্থার snapshot রাখা, পরে ফেরানো — undo-র ভিত্তি | ৩২ |
| Observer | একটি object বদলালে তার ওপর নির্ভরশীল সবাইকে নিজে থেকে জানানো | ৩৫ |
| State | অবস্থা বদলালে object-এর আচরণও বদলায়, প্রতিটি অবস্থা আলাদা class | ৩৬ |
| Strategy | একই কাজের কয়েকটি algorithm আলাদা রেখে বদলযোগ্য করা | ৩৫ |
| Template Method | ধাপের কাঠামো base class-এ, নির্দিষ্ট ধাপ subclass-এ | ৩৫ |
| Visitor | Class না বদলে একটি object-কাঠামোর ওপর নতুন operation যোগ | ৩৬ |
পর্ব ৩৩ · Creational
Singleton, factory method, abstract factory, builder আর prototype — object কে, কীভাবে বানাবে।
তৈরিপর্ব ৩৫ · Behavioral ১
Strategy, observer, command আর template method — সবচেয়ে বেশি ব্যবহৃত আচরণের চারটি।
আচরণপর্ব ৩৭ · Composition
Dependency injection আর composition — pattern-গুলো জোড়া লাগানোর আধুনিক উপায়।
জোড়া৪ · একটি pattern-এর বিবরণ পড়া — GoF-এর ছাঁচ আর class diagram
GoF বইয়ের প্রতিটি pattern একই ছাঁচে লেখা। ছাঁচটি চিনলে যেকোনো pattern বই — বা অনলাইনের যেকোনো বিবরণ — দ্রুত পড়া যায়, আর কোন অংশ বাদ পড়েছে তাও ধরা যায়।
| অংশ | যে প্রশ্নের উত্তর দেয় |
|---|---|
| Pattern Name and Classification | নাম কী, কোন পরিবারের, class না object পরিসরের? |
| Intent | এক-দুই বাক্যে: কী সমস্যা সমাধান করে? |
| Also Known As | অন্য কোন নামে চেনা যায়? |
| Motivation | একটি গল্প-উদাহরণ: সমস্যাটা বাস্তবে কেমন দেখায়? |
| Applicability | কোন কোন পরিস্থিতিতে ব্যবহার করবেন? |
| Structure | Class diagram — কোন ভূমিকা, কী সম্পর্ক |
| Participants | প্রতিটি ভূমিকার দায়িত্ব কী? |
| Collaborations | চলার সময় ভূমিকাগুলো কে কাকে ডাকে? |
| Consequences | লাভ কী, দাম কী, কী বদলানো সহজ হয়? |
| Implementation | লেখার সময় কোন ফাঁদ আর কোন কৌশল? |
| Sample Code | ছোট উদাহরণ কোড (বইয়ে C++ আর Smalltalk) |
| Known Uses | বাস্তবে কোন system-এ দেখা গেছে? |
| Related Patterns | কোন pattern কাছাকাছি, কোনটির সাথে মেলে বা গুলিয়ে যায়? |
তেরোটি অংশের মধ্যে প্রথমবার পড়ার সময় তিনটিতে জোর দিন: Intent (কী সমস্যা), Applicability (কখন), আর Consequences (দাম)। অনেক অনলাইন লেখা শুধু Structure আর Sample Code দেখায় — ঠিক যে দুটি অংশ একা পড়লে pattern “মুখস্থ করার জিনিস” মনে হয়। নিচের stepper-এ Strategy pattern-এর বিবরণ অংশ ধরে পড়া হলো — বইয়ের ভাষা নয়, আমাদের সংক্ষেপ। “পরে” চাপুন।
| অংশ | |
| কী বলে | |
| আপনার প্রশ্ন |
- নাম আর শ্রেণি। “Strategy”, পরিবার behavioral, পরিসর object. Object পরিসর মানে বদলটা inheritance দিয়ে নয়, একটি object-কে আরেকটি দিয়ে বদলে — চলার সময়ও করা যায়।
- Intent — সবচেয়ে জরুরি লাইন। একই কাজের কয়েকটি algorithm, প্রতিটি নিজের মোড়কে, আর ব্যবহারকারী কোড না বদলে একটির জায়গায় আরেকটি বসানো যায়। নিজেকে জিজ্ঞেস করুন: আমার সমস্যা কি সত্যিই “একই কাজের কয়েকটি রূপ”?
- অন্য নাম। GoF Strategy-কে “Policy” নামেও চেনান।
- Motivation — গল্প। বইয়ের গল্প একটি text editor-এর: লেখাকে লাইনে ভাঙার কয়েকটি algorithm আছে — সরল, উন্নত মানের (TeX-ধাঁচের), আর প্রতি লাইনে নির্দিষ্ট সংখ্যক জিনিস রাখার। সবগুলো editor-এর ভেতরে if দিয়ে লিখলে কোড ফুলে ওঠে আর নতুন algorithm যোগ করা কঠিন হয়।
- Applicability — লক্ষণ। অনেকগুলো সম্পর্কিত class শুধু আচরণে আলাদা; একটি algorithm-এর কয়েকটি রূপ লাগে; algorithm-এর ভেতরের data বাইরে থেকে লুকাতে চান; একটি class-এর ভেতরে আচরণ বাছাইয়ের বড় if/switch. লক্ষণ না মিললে এখানেই থামুন।
- Structure আর Participants. তিনটি ভূমিকা:
Context(যে কাজ করায়),Strategy(সাধারণ চুক্তি),ConcreteStrategy(প্রতিটি রূপ)। চিহ্ন: Context-এর দিক থেকে হীরা-তীর — Context একটি Strategy ধরে রাখে; ড্যাশ রেখা আর ফাঁপা ত্রিভুজ — concrete class চুক্তি পূরণ করে। নিচের ছবি ৩২.২-এ বড় করে। - Collaborations — চলার সময়। Client একটি ConcreteStrategy বেছে Context-কে দেয়; এরপর Client শুধু Context-এর সাথে কথা বলে। Context দরকারি data strategy-কে পাঠায় (বা নিজেকেই পাঠায়)। পর্ব ৩০-এর sequence diagram-এ এটাই আঁকা যায়।
- Consequences — দাম। লাভ: সম্পর্কিত algorithm-এর পরিবার, subclass-এর বিকল্প, if/switch দূর, একই আচরণের ভিন্ন implementation. দাম: Client-কে বিকল্পগুলো চিনতে হয়; Context আর Strategy-র মধ্যে কিছু data অকারণে যেতে পারে; object-এর সংখ্যা বাড়ে।
- Implementation আর Related. ফাঁদ ও কৌশল: strategy ঐচ্ছিক রাখা যায় — না দিলে Context একটি ডিফল্ট আচরণ চালায়। কাছের pattern: State-এর class diagram প্রায় একই (পর্ব ৩৬), পার্থক্য উদ্দেশ্যে — State নিজে নিজে বদলায়, Strategy বাইরে থেকে বেছে দেওয়া হয়। Template Method (পর্ব ৩৫) একই সমস্যা inheritance দিয়ে সমাধান করে।
Pattern-এর class diagram পড়া
পর্ব ২৯-এ (UML class diagram) আপনি সব চিহ্ন শিখেছেন। Pattern-এর diagram পড়তে শুধু চারটি প্রশ্ন করুন: (১) কোনটি চুক্তি — interface বা abstract class (নাম italic বা «interface»)? (২) কোন class-গুলো চুক্তি পূরণ করে — ফাঁপা ত্রিভুজ, ড্যাশ রেখায় realization, পূর্ণ রেখায় generalization? (৩) কে কাকে ধরে রাখে — হীরা বা তীরওয়ালা association? (৪) কে কাকে শুধু ব্যবহার করে বা বানায় — ড্যাশ খোলা তীর? তারপর নামগুলো আপনার সমস্যার শব্দে বদলে নিন: Context = Checkout, Strategy = Discount.
Strategy-র কাঠামো — আপনার সমস্যার নামে
Context holds one Strategy (aggregation); concrete strategies realize the interface; Client creates and uses
৫ · GoF-এর দুই মূল নীতি — interface ধরে লিখুন, composition বেছে নিন
অনেকে ভুলে যান, GoF বইয়ের প্রথম অধ্যায়টি pattern-এর তালিকা নয় — একটি নকশা-দর্শন। সেখানে দুটি নীতি আছে, যা পরের ২৩টি pattern-এর অনেকগুলোর ভিত্তি।
১. Program to an interface, not an implementation
Variable, parameter আর field-এর type হবে চুক্তি, নির্দিষ্ট class নয়। Checkout জানে শুধু “যার apply(total) আছে” — PercentOff না FlatOff, তা জানে না। পর্ব ২৮-এর dependency inversion এই নীতিরই বড় রূপ।
২. Favor object composition over class inheritance
আচরণ পুনর্ব্যবহারে subclass বানানোর আগে ভাবুন — অন্য একটি object-কে ভেতরে রেখে কাজ করানো যায় কি? Inheritance-কে GoF বলেছেন “white-box” পুনর্ব্যবহার (base class-এর ভেতর দেখা যায়, বাঁধন শক্ত); composition “black-box” (শুধু interface দেখা যায়)।
composition over inheritanceবিয়েবাড়ির আয়োজনের কথা ভাবুন। একটি পরিবার নিজেরাই বাবুর্চি হয়ে যেতে পারে (inheritance — রান্নার দক্ষতা পরিবারের “ভেতরে” ঢুকে গেল, বদলানো কঠিন)। অথবা একজন বাবুর্চি ভাড়া করতে পারে, যাঁর সাথে চুক্তি শুধু “২০০ জনের বিরিয়ানি, সন্ধ্যা ৭টায়” (composition আর interface). বাবুর্চি অসুস্থ হলে আরেকজন ডাকা যায়; চুক্তি একই থাকে, আয়োজনের বাকি কিছু বদলায় না।
এই দুই নীতি দেখলে বোঝা যায়, কেন GoF-এর বেশিরভাগ pattern “object” পরিসরের। Strategy, Decorator, Observer, Composite, State — সবগুলোতে একটি object আরেকটি object-কে interface ধরে ধরে রাখে, আর চলার সময় তাকে বদলানো যায়। পর্ব ৩৭-এ (dependency injection আর composition) দেখবেন, এই object-গুলো বাইরে থেকে জোড়া লাগানোর পরিচ্ছন্ন উপায় কী। আর মনে রাখুন — GoF inheritance নিষেধ করেননি; Template Method আর Factory Method inheritance-এর ওপরই দাঁড়িয়ে। নীতিটি “অগ্রাধিকার”, নিষেধাজ্ঞা নয়।
GoF-এর প্রথম অধ্যায়ে বদলের জন্য নকশা-রও একটি তালিকা আছে: নকশা নতুন করে লিখতে বাধ্য হওয়ার সাধারণ কারণ (নির্দিষ্ট class-এর নামে object বানানো, algorithm বা platform-এর ওপর নির্ভরতা, শক্ত coupling) আর প্রতিটিতে কোন pattern সাহায্য করে। পর্ব ২৬-এর encapsulate what varies এরই সংক্ষেপ।
৬ · আধুনিক ভাষায় pattern — function যখন নিজেই strategy
GoF-এর উদাহরণ ১৯৯০-এর দশকের C++-এ। সেই C++-এ function-কে মানের মতো পাঠানোর সহজ পথ ছিল সীমিত (function pointer ছিল, কিন্তু চারপাশের data ধরে রাখা lambda আসে C++11-এ, ২০১১ সালে)। তাই “একটি আচরণ পাঠাও” বোঝাতে একটি class বানিয়ে তার object পাঠানো হতো। JavaScript আর Python-এ function first-class — variable-এ রাখা যায়, argument-এ পাঠানো যায়, ফেরত দেওয়া যায়। Java-তেও lambda এসেছে Java 8-এ (২০১৪)। ফলে কয়েকটি pattern-এর চেহারা নাটকীয়ভাবে ছোট হয়ে যায়।
চালিয়ে দেখুন — একই ছাড়ের সমস্যা, দুই সমাধান
একটি কাল্পনিক অনলাইন বাজার অ্যাপ। Checkout-এ তিন রকম ছাড়: কোনো ছাড় নেই, ১০% ছাড়, আর ১০০০ টাকা বা বেশি কিনলে ২০০ টাকা ছাড়। (A) অংশে GoF-ধাঁচের Strategy — একটি abstract base class আর প্রতি নিয়মে একটি class; (B) অংশে একই চিন্তা শুধু function দিয়ে, যেখানে higher-order function নিয়ম বানায়। শেষে প্রতিটি সমাধানের সংজ্ঞা কত লাইনের, তা function-এর নিজের source থেকে গোনা হয়েছে।
// Fictional online bazaar: pluggable discount rules, solved two ways.
// (A) GoF-style Strategy: an abstract base class + one class per rule
class Discount { apply(total) { throw new Error("abstract"); } }
class NoDiscount extends Discount { apply(t) { return t; } }
class PercentOff extends Discount {
constructor(p) { super(); this.p = p; }
apply(t) { return Math.round(t * (100 - this.p) / 100); }
}
class FlatOff extends Discount {
constructor(amount, min) { super(); this.amount = amount; this.min = min; }
apply(t) { return t >= this.min ? t - this.amount : t; }
}
class Checkout {
constructor(strategy) { this.strategy = strategy; }
pay(total) { return this.strategy.apply(total); }
}
// (B) The same idea with plain functions: a strategy is just a function
const none = t => t;
const percentOff = p => t => Math.round(t * (100 - p) / 100);
const flatOff = (amount, min) => t => (t >= min ? t - amount : t);
const pay = (rule, total) => rule(total);
const A = [new NoDiscount(), new PercentOff(10), new FlatOff(200, 1000)];
const B = [none, percentOff(10), flatOff(200, 1000)];
for (const total of [450, 1200, 2500]) {
const a = A.map(s => new Checkout(s).pay(total));
const b = B.map(rule => pay(rule, total));
console.log(total, "classes:", a.join(" "), "| functions:", b.join(" "),
a.join() === b.join() ? "same" : "DIFFERENT");
}
const lines = f => f.toString().split("\n").length;
const sum = fs => fs.map(lines).reduce((x, y) => x + y, 0);
console.log("lines A:", sum([Discount, NoDiscount, PercentOff, FlatOff, Checkout]),
"| lines B:", sum([none, percentOff, flatOff, pay]));আউটপুট:
450 classes: 450 405 450 | functions: 450 405 450 same
1200 classes: 1200 1080 1000 | functions: 1200 1080 1000 same
2500 classes: 2500 2250 2300 | functions: 2500 2250 2300 same
lines A: 14 | lines B: 4
তিনটি cart-এ দুই সমাধানের ফল হুবহু এক। কিন্তু সংজ্ঞার আকার আলাদা — class-গুলো মিলে ১৪ লাইন (Discount ১, NoDiscount ১, বাকি তিনটি ৪ করে), function-গুলো ৪ লাইন। toString() প্রতিটি class বা function-এর source ফেরত দেয়, তাই এই গোনা আমাদের হাতের অনুমান নয়। খেয়াল করুন, (B)-তে pattern হারিয়ে যায়নি: pay এখনো “Context”, প্রতিটি function এখনো একটি “ConcreteStrategy”, আর চুক্তি হলো “একটি সংখ্যা নিয়ে একটি সংখ্যা ফেরত”। Pattern-এর চিন্তা একই, শুধু অনুষ্ঠান (ceremony) কম। তবে class-রূপের নিজস্ব লাভ আছে: strategy-র যদি কয়েকটি method লাগে (apply, label, isEligible), বা নিজের অবস্থা রাখতে হয়, বা দলের ভাষা Java হয় — তখন class পরিষ্কার। পর্ব ৩৫-এ Strategy-র দুই রূপই বিস্তারিত।
১৯৯৬ সালে Peter Norvig “Design Patterns in Dynamic Languages” (কোথাও শিরোনাম “…Dynamic Programming”) নামে একটি বক্তৃতার slide-এ দেখান, তাঁর হিসাবে GoF-এর ২৩টির মধ্যে ১৬টি pattern Lisp বা Dylan-এর মতো dynamic ভাষায় হয় “অদৃশ্য” — ভাষার সাধারণ সুবিধায় মিশে যায় — নয়তো অনেক সরল, অন্তত কিছু ব্যবহারের ক্ষেত্রে। এই সংখ্যাটি একজনের বিশ্লেষণ, আর “সরল” মানে মাপা যায় এমন কিছু নয়; তবু মূল কথাটি আজ ব্যাপকভাবে স্বীকৃত: pattern অনেক সময় ভাষার না-থাকা সুবিধার জায়গা পূরণ করে।
| Pattern | ১৯৯৪-এর C++-ধাঁচে | JavaScript / Python-এ প্রায়ই |
|---|---|---|
| Strategy | Interface + প্রতি রূপে একটি class | একটি function, বা function-এর একটি map (registry) |
| Command | execute()-ওয়ালা class | একটি closure; undo লাগলে {do, undo} জোড়া |
| Template Method | Base class-এ কাঠামো, subclass-এ ধাপ | কাঠামো-function, ধাপগুলো argument হিসেবে function |
| Observer | Subject + update()-ওয়ালা observer class | callback-এর তালিকা, browser-এর addEventListener, Node-এর EventEmitter |
| Iterator | আলাদা iterator class | ভাষার ভেতরেই — for…of, Python-এর for আর generator |
| Decorator | একই interface-এর মোড়ক class | Function মোড়ানো function; Python-এর @decorator syntax এর কাছের আত্মীয়, তবে হুবহু একই নয় |
Observer বনাম callback — একই ঘটনা, দুই পোশাক
একটি কাল্পনিক বাস-টিকিট অ্যাপ। একটি seat-এর অবস্থা বদলালে SMS অংশ আর কাউন্টারের screen জানবে। (A)-তে GoF Observer — subject একটি তালিকায় observer object রাখে, যাদের update() আছে। (B)-তে একই কাজ callback দিয়ে — listener নিছক একটি function, আর on() একটি “বন্ধ করার” function ফেরত দেয়। দুই অংশেই প্রথম ঘটনার পর SMS অংশকে সরানো হয়।
// Fictional bus-ticket app: when a seat changes, several parts must react.
let log = [];
// (A) GoF Observer: a subject keeps observer OBJECTS that have update()
class SeatSubject {
constructor() { this.observers = []; }
attach(o) { this.observers.push(o); }
detach(o) { this.observers = this.observers.filter(x => x !== o); }
notify(ev) { for (const o of this.observers) o.update(ev); }
}
class SmsObserver { update(ev) { log.push(`sms ${ev.seat} ${ev.status}`); } }
class BoardObserver { update(ev) { log.push(`board ${ev.seat}=${ev.status}`); } }
// (B) The same with callbacks: a listener is just a FUNCTION
function makeEmitter() {
const fns = new Set();
return {
on(f) { fns.add(f); return () => fns.delete(f); }, // returns "off"
emit(ev) { fns.forEach(f => f(ev)); },
};
}
const subject = new SeatSubject(), sms = new SmsObserver();
subject.attach(sms); subject.attach(new BoardObserver());
subject.notify({ seat: "A3", status: "booked" });
subject.detach(sms);
subject.notify({ seat: "B1", status: "free" });
const fromObserver = log; log = [];
const seats = makeEmitter();
const offSms = seats.on(ev => log.push(`sms ${ev.seat} ${ev.status}`));
seats.on(ev => log.push(`board ${ev.seat}=${ev.status}`));
seats.emit({ seat: "A3", status: "booked" });
offSms();
seats.emit({ seat: "B1", status: "free" });
console.log("observer :", fromObserver.join(" ; "));
console.log("callbacks:", log.join(" ; "));
console.log("same result:", fromObserver.join() === log.join());আউটপুট:
observer : sms A3 booked ; board A3=booked ; board B1=free
callbacks: sms A3 booked ; board A3=booked ; board B1=free
same result: true
A3 বুক হলে দুজনই জানল; SMS অংশ সরানোর পর B1 খালি হওয়ার খবর গেল শুধু board-এ — দুই রূপে একই ফল, একই ক্রম। পার্থক্য পোশাকে: (A)-তে দুটি বাড়তি class আর একটি নির্দিষ্ট method-নাম (update); (B)-তে listener যেকোনো function. দুই রূপেরই একই ফাঁদ আছে, যা pattern-এর “Consequences” অংশে লেখা থাকে: listener সরাতে ভুলে গেলে সে চিরকাল খবর পায় আর মেমরিতে থেকে যায় (অনেকে একে lapsed listener বলেন); একটি listener exception ছুড়লে পরেরগুলো খবর না-ও পেতে পারে। পর্ব ৩৫-এ Observer-এর এই প্রশ্নগুলোর উত্তর।
৭ · GoF-এর বাইরে — enterprise, concurrency, cloud আর architecture pattern
২৩টি GoF pattern পুরো জগৎ নয়, একটি পাড়া মাত্র। একই ছাঁচে — প্রেক্ষাপট, সমস্যা, সমাধান, ফলাফল — আরও অনেক catalog লেখা হয়েছে। প্রতিটির জন্য এক লাইন:
| পরিবার | মূল উৎস (বছর) | কয়েকটি নাম | এই সাইটে |
|---|---|---|---|
| Architectural pattern | Buschmann ও সহলেখক, Pattern-Oriented Software Architecture খণ্ড ১ (১৯৯৬) | Layers, Pipes and Filters, Broker, MVC | পর্ব ৩৮-৩৯ |
| Enterprise application pattern | Martin Fowler, Patterns of Enterprise Application Architecture (২০০২) | Repository, Unit of Work, Data Mapper, Active Record, Lazy Load | DBMS ও SQL কোর্স |
| Integration pattern | Hohpe ও Woolf, Enterprise Integration Patterns (২০০৩) | Message Channel, Publish-Subscribe Channel, Dead Letter Channel | System design · message queue |
| Concurrency pattern | Schmidt ও সহলেখক, POSA খণ্ড ২ (২০০০) | Reactor, Proactor, Active Object, Monitor Object, Leader/Followers | Operating Systems কোর্স |
| Cloud ও resilience pattern | Michael Nygard, Release It! (২০০৭) আর বিভিন্ন cloud প্ল্যাটফর্মের প্রকাশিত catalog | Circuit Breaker, Retry, Bulkhead, Sidecar | System design · resilience pattern |
শিক্ষাটি একটাই: “pattern” কোনো বইয়ের সম্পত্তি নয়, অভিজ্ঞতা লিখে রাখার একটি ধরন। যেকোনো ক্ষেত্রে — testing, UI, security, data pipeline — অভিজ্ঞ মানুষ বারবার একই সমাধানে পৌঁছালে কেউ না কেউ তার নাম দেন।
৮ · Anti-pattern আর কখন pattern নয়
Pattern যদি “বারবার কাজ করা সমাধান” হয়, তবে anti-pattern হলো “বারবার দেখা যায় এমন সমাধান, যা দেখতে যুক্তিসংগত, কিন্তু শেষ পর্যন্ত লাভের চেয়ে ক্ষতি বেশি করে”। শব্দটি সাধারণত Andrew Koenig-এর নামে উল্লেখ হয় — ১৯৯৫ সালে Journal of Object-Oriented Programming-এ একটি লেখায় তিনি এটি ব্যবহার করেন। ১৯৯৮ সালে Brown, Malveau, McCormick ও Mowbray-এর AntiPatterns বইটি ধারণাটিকে জনপ্রিয় করে।
Golden hammer
“হাতে হাতুড়ি থাকলে সবকিছু পেরেক মনে হয়।” একটি পরিচিত প্রযুক্তি বা pattern সব সমস্যায় চাপানো — সদ্য Observer শিখে সব কিছুতে event. Golden hammer নামটি এই প্রবাদ থেকে।
এক অস্ত্র, সব যুদ্ধGod object
যে একটি class বা object সবার সব কাজ করে, সবাই যার ওপর নির্ভর করে। পর্ব ২৭-এ এর নাম দেখেছেন god class; AntiPatterns বইয়ে এর নাম “The Blob”.
সব এক জায়গায়Singleton-এর অপব্যবহার
Singleton আসলে একটি সুন্দর নামের global variable হয়ে যায়: লুকানো নির্ভরতা, test-এর মধ্যে অবস্থা থেকে যাওয়া, একসাথে চলা thread-এ গোলমাল। পর্ব ৩৩-এ বিস্তারিত।
লুকানো globalPattern-itis
Pattern শেখার পর প্রতিটি কোডে pattern খোঁজা — AbstractDiscountStrategyFactoryProvider. অনানুষ্ঠানিক নাম pattern-itis; পর্ব ২৬-এর over-engineering আর speculative generality-এর আত্মীয়।
কখন pattern নয় — পাঁচটি প্রশ্ন
- সমস্যাটি কি আজ আছে? “পরে লাগতে পারে” প্রমাণ নয়। পর্ব ২৬-এর YAGNI: দ্বিতীয় রূপ আসার আগে strategy বানানো প্রায়ই অপচয়।
- সবচেয়ে সরল সমাধান কী, আর সেটা কেন যথেষ্ট নয়? একটি function, একটি dictionary, একটি if — এগুলোর সাথে তুলনা না করে pattern বেছে নেবেন না (KISS).
- ভাষা কি কাজটা আগেই করে দেয়? Iterator লিখতে বসার আগে
for…of, Observer-এর আগে ভাষার event ব্যবস্থা। - দাম কে দেবে? প্রতিটি নতুন class আর indirection পরের পাঠককে পড়তে হবে।
- পরে আনা কি কঠিন হবে? ভালো test থাকলে সরল কোড থেকে pattern-এ যাওয়া কয়েক মিনিটের refactoring (পর্ব ৪২)। আজ সরল রাখার ঝুঁকি সাধারণত কম।
চালিয়ে দেখুন — একটি খেলনা “pattern smell” খোঁজক
নিচের Python প্রোগ্রাম standard library-র ast module দিয়ে একটি কাল্পনিক দোকান-অ্যাপের কোড পড়ে (চালায় না, শুধু গঠন দেখে)। নিয়ম দুটি, স্পষ্ট করে বলা: R1 — যে class-এর কোনো base class নেই আর public method ঠিক একটি, সেটি সম্ভবত class-এর পোশাক পরা একটি function; R2 — যে base class-এর ঠিক একটি subclass, সেটি সম্ভবত অকালপক্ব abstraction. এগুলো বিচার নয়, প্রশ্ন তোলার সংকেত।
import ast
SOURCE = '''
class PriceFormatter:
def __init__(self, currency): self.currency = currency
def format(self, amount): return f"{amount} {self.currency}"
class VatCalculator:
def calculate(self, amount): return amount * 15 // 100
class Cart:
def __init__(self): self.items = []
def add(self, item): self.items.append(item)
def total(self): return sum(i.price for i in self.items)
class ShippingRule:
def cost(self, kg): raise NotImplementedError
class FlatShipping(ShippingRule):
def cost(self, kg): return 60
'''
# Stated toy heuristic, not a law:
# R1 no base class, exactly ONE public method -> probably a function
# R2 a base class with exactly ONE subclass -> single-use abstraction
classes = [n for n in ast.parse(SOURCE).body if isinstance(n, ast.ClassDef)]
children = {}
for c in classes:
for b in c.bases: children.setdefault(b.id, []).append(c.name)
for c in classes:
methods = [f.name for f in c.body if isinstance(f, ast.FunctionDef)]
public = [m for m in methods if not m.startswith("_")]
kids = children.get(c.name, [])
if len(kids) == 1:
print(f"{c.name}: R2 only {kids[0]} implements it -> inline until a 2nd rule")
elif not c.bases and len(public) == 1:
extra = " (pass its __init__ data as a parameter)" if "__init__" in methods else ""
print(f"{c.name}: R1 one method '{public[0]}' -> plain function{extra}")
else:
print(f"{c.name}: ok (public methods: {len(public)}, base classes: {len(c.bases)})")আউটপুট:
PriceFormatter: R1 one method 'format' -> plain function (pass its __init__ data as a parameter)
VatCalculator: R1 one method 'calculate' -> plain function
Cart: ok (public methods: 2, base classes: 0)
ShippingRule: R2 only FlatShipping implements it -> inline until a 2nd rule
FlatShipping: ok (public methods: 1, base classes: 1)
VatCalculator-এর কোনো অবস্থা নেই, একটি method — একটি vat(amount) function-ই যথেষ্ট। PriceFormatter একটি মান (currency) মনে রাখে আর একটি কাজ করে; এটিও একটি parameter-ওয়ালা function বা functools.partial দিয়ে হয়। Cart নিজের তালিকা রাখে আর দুটি কাজ করে — আসল class, ঠিক আছে। ShippingRule-এর একটিমাত্র রূপ, তাই R2 সতর্ক করল; দ্বিতীয় রূপ এলে তখন abstraction ফেরানো যায়। FlatShipping একটি hierarchy-র অংশ, তাই R1 তাকে ছাড় দিল। খেলনাটির সীমাও দেখুন: class-টি যদি একটি framework-এর চাওয়া চুক্তি পূরণ করে, বা test-এ নকল বসানোর জন্য interface-টি লাগে (পর্ব ৪৯), তবে নিয়মগুলো ভুল সংকেত দেবে। প্রশ্নটা আপনি করবেন, উত্তরটা প্রেক্ষাপট দেবে।
এবার একটি পুরো over-patterned নকশাকে ধাপে ধাপে সরল করা যাক। কাল্পনিক একটি বিশ্ববিদ্যালয়ের ফলাফল পোর্টালে GPA হিসাবের কোড। নিয়ম একটিই, আর তিন বছরে বদলায়নি; তবু কেউ “ভবিষ্যতের জন্য” ছয়টি pattern-এর চিহ্ন বসিয়েছেন। প্রতিটি ধাপে আগে test চলে, পরে বদল।
| ধাপ | |
| Class-এর সংখ্যা | |
| ডাক থেকে নিয়ম পর্যন্ত ধাপ | |
| যা সরল হলো |
- শুরুর অবস্থা — ৮টি class.
GradeStrategy(interface),GpaStrategy(একমাত্র রূপ),GradeStrategyFactory,GradeService(Singleton),GradeEventSubject,LoggerObserver(একমাত্র listener),ResultBuilder,Result. একটি ফল পেতে ৪টি ডাক:getInstance()→grade()→factory.create()→strategy.grade(). - আগে test, পরে বদল। কয়েকজন কাল্পনিক ছাত্রের নম্বর আর তাদের প্রত্যাশিত GPA দিয়ে test লেখা হলো — বর্তমান আচরণ যেমন আছে, ঠিক তেমন (characterization test). এখনো কোনো class বদলায়নি; পরের প্রতিটি ধাপের আগে-পরে তুলনার ভিত্তি তৈরি হলো।
- Singleton সরানো।
GradeServiceএখন সাধারণ object, যে দরকার তাকে তৈরি করে দেয়। Class একই ৮টি, কিন্তুgetInstance()-এর ডাক গেল — ৩টি ধাপ। বড় লাভ test-এ: এক test-এর অবস্থা আর পরের test-এ চুইয়ে পড়ে না। - Factory inline. Factory সবসময় একই
GpaStrategyবানাত — সিদ্ধান্ত নেওয়ার কিছুই ছিল না। Factory সরিয়ে সরাসরি তৈরি: ৭টি class, ডাক ২টি ধাপে। - Strategy → function. একটিমাত্র রূপের interface আর তার class মিলে একটি
gpa(marks)function হলো। ২টি class কমে ৫টি।GradeService.grade()এখন সরাসরিgpa()ডাকে — ধাপ এখনো ২টি। - Observer → সরাসরি ডাক। Subject-এর একমাত্র listener ছিল logger. Subject আর observer সরিয়ে হিসাবের পরে এক লাইনের log ডাক। ৩টি class বাকি:
GradeService,ResultBuilder,Result. - Builder → সাধারণ object.
Result-এর মাত্র দুটি field (roll আর GPA); builder-এর বদলে সরাসরি তৈরি। ২টি class বাকি। সব test সবুজ — আচরণ অক্ষত। দ্বিতীয় কোনো grading নিয়ম কখনো এলেgpa-কে একটি parameter-এ নেওয়া function বানালেই Strategy ফিরে আসবে — এক লাইনে।
৯ · Pattern বেছে নেওয়া — সমস্যা থেকে নাম, আর review-ইন্টারভিউতে pattern-এর ভাষা
নতুনদের সবচেয়ে বড় ভুল হলো pattern দিয়ে শুরু করা: “আজ একটা Visitor ব্যবহার করব।” পেশাদার পথ উল্টো — সমস্যা থেকে শুরু, কী বদলায় তা খোঁজা, তারপর সবচেয়ে সরল সমাধানের সাথে pattern-এর তুলনা। নিচের টেবিলটি একটি শুরুর মানচিত্র, আইন নয়।
| আপনার সমস্যা | প্রার্থী pattern | আগে যে সরল বিকল্প ভাববেন |
|---|---|---|
| একই কাজের কয়েকটি নিয়ম, বেছে নেওয়া চলার সময় | Strategy | Function বা function-এর map |
| একটি জিনিস বদলালে কয়েকজনকে জানাতে হবে, যাদের সংখ্যা বাড়ে-কমে | Observer | Callback-এর তালিকা; আলাদা server হলে message queue |
| পুরোনো বা বাইরের library-র interface আপনার প্রত্যাশার সাথে মেলে না | Adapter | একটি ছোট wrapper function |
| Undo, redo, কাজ সারিতে রাখা বা লগ করা | Command (প্রয়োজনে Memento) | {do, undo} function-জোড়া |
| অনেক ঐচ্ছিক অংশওয়ালা object তৈরি | Builder | নাম দেওয়া argument বা default মানওয়ালা object |
| Object-এর আচরণ তার অবস্থার (status) ওপর নির্ভর করে, অনেক if-এ | State | একটি transition টেবিল (পর্ব ২৩-এর state diagram) |
| জটিল subsystem-এর জন্য একটি সহজ প্রবেশপথ | Facade | একটি module-এর কয়েকটি public function |
| একটি object-এ চলার সময় স্তরে স্তরে নতুন আচরণ (log, cache, retry) | Decorator | Function মোড়ানো function |
এখন একটি কাল্পনিক সমস্যা নিয়ে পুরো সিদ্ধান্ত-প্রক্রিয়া। একটি বাস-টিকিট অ্যাপে ঈদের আগে seat-এর অবস্থা বদলালে কয়েকটি অংশকে জানাতে হয়, আর নতুন অংশ যোগ হতেই থাকে। “পরে” চাপুন।
- সমস্যাএক বাক্যে
- কী বদলায়আর কী স্থির
- শর্তforces
- প্রার্থীObserver, Mediator, queue
- দাম মেলানোconsequences
- সিদ্ধান্তসরলতম রূপ + নাম
- সমস্যা এক বাক্যে। “একটি seat-এর অবস্থা বদলালে SMS, কাউন্টারের screen আর হিসাবের অংশকে জানাতে হবে — booking-এর কোড তাদের চিনবে না।” বাক্যে কোনো pattern-এর নাম নেই; থাকা উচিতও নয়।
- কী বদলায়, কী স্থির। Booking-এর নিয়ম স্থির। বদলায় খবর-পাওয়াদের তালিকা — গত মাসে analytics যোগ হয়েছে, আগামী মাসে হয়তো একটি app notification. বদলের অক্ষ স্পষ্ট এবং প্রমাণিত।
- শর্ত। Booking-এর কোড SMS library-র ওপর নির্ভর করবে না; খবর-পাওয়াদের যোগ-বাদ সহজ হবে; একজনের ব্যর্থতায় booking আটকাবে না; ঈদের ভিড়ে booking দ্রুত থাকবে।
- প্রার্থী। Observer (“একজন বদলালে অনেককে জানানো”), Mediator (অনেকে পরস্পরের সাথে কথা বললে), আর process-এর বাইরে message queue-ভিত্তিক publish-subscribe (system design-এর pub/sub).
- দাম মেলানো। Mediator অতিরিক্ত — খবর-পাওয়ারা একে অপরের সাথে কথা বলে না। Queue শক্তিশালী, কিন্তু আলাদা server, retry আর পর্যবেক্ষণের খরচ — সবকিছু যদি একই প্রোগ্রামে চলে, তবে এখনই লাগে না। Observer-এর দাম কম; শুধু listener-এর exception আর ক্রমের সতর্কতা মানতে হবে।
- সিদ্ধান্ত — সরলতম রূপ, স্পষ্ট নাম। JavaScript-এ callback-এর তালিকা (Observer-এর হালকা রূপ), প্রতিটি listener-এর ভুল আলাদা করে ধরা। Pull request-এর বিবরণে লেখা: “seat status-এর জন্য Observer (callback list); SMS ধীর হলে পরে queue-তে সরানো হবে।” নামটি reviewer-এর জন্য, অনুমানটি ভবিষ্যতের জন্য।
অনুশীলনের খেলনা — সমস্যার শব্দ থেকে pattern-এর তালিকা
নিচের প্রোগ্রাম বেছে নেওয়ার অনুশীলনের জন্য, সিদ্ধান্তের জন্য নয়। প্রতিটি pattern-এর কয়েকটি “সংকেত-শব্দ” আছে; একটি সমস্যার বর্ণনায় যতগুলো সংকেত-শব্দ পাওয়া যায়, সেটাই score. সমান score হলে নামের বর্ণানুক্রম। ০ score দেখানো হয় না। সমস্যাগুলো ইংরেজিতে, কারণ শব্দ মেলানো ইংরেজি অক্ষরে।
// Toy pattern finder (study aid only). Stated rule: score = number of a pattern's
// cue words found in the problem; ties alphabetical; score 0 is not shown.
const catalogue = {
"Adapter": ["incompatible", "legacy", "convert", "wrap", "interface"],
"Builder": ["construct", "options", "step", "complex", "object"],
"Command": ["undo", "queue", "request", "action", "history"],
"Composite": ["tree", "nested", "part", "whole", "uniform"],
"Decorator": ["add", "wrap", "layer", "runtime", "optional"],
"Facade": ["subsystem", "complex", "simple", "entry", "many"],
"Factory Method": ["create", "subclass", "decide", "type"],
"Iterator": ["traverse", "collection", "sequence", "hide"],
"Observer": ["notify", "subscribe", "event", "many", "change"],
"State": ["status", "mode", "transition", "state"],
"Strategy": ["algorithm", "rule", "swap", "variant", "policy"],
"Template Method": ["steps", "skeleton", "order", "hook", "subclass"],
};
function suggest(problem, top = 3) {
const words = new Set(problem.toLowerCase().split(/[^a-z]+/));
return Object.entries(catalogue)
.map(([name, cues]) => [name, cues.filter(w => words.has(w)).length])
.filter(([, score]) => score > 0)
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))
.slice(0, top);
}
const problems = [
"notify many screens when a seat status change event happens",
"swap the delivery fee rule for each district variant",
"wrap a legacy payment library with an incompatible interface",
"undo the last action in a ticket editor",
"print one invoice as a PDF",
];
for (const p of problems) {
const r = suggest(p).map(([n, s]) => `${n}(${s})`).join(", ");
console.log(`${p}\n -> ${r || "no cue matched: maybe no pattern needed"}`);
}আউটপুট:
notify many screens when a seat status change event happens
-> Observer(4), Facade(1), State(1)
swap the delivery fee rule for each district variant
-> Strategy(3)
wrap a legacy payment library with an incompatible interface
-> Adapter(4), Decorator(1)
undo the last action in a ticket editor
-> Command(2)
print one invoice as a PDF
-> no cue matched: maybe no pattern needed
প্রথম সমস্যায় Observer পেল ৪ (notify, many, change, event). কিন্তু Facade পেল ১ শুধু “many” শব্দে, আর State পেল ১ “status”-এ — শব্দ মেলানোর ফাঁদ, কারণ seat-এর status থাকা মানেই State pattern নয়। Adapter-এর ৪ (legacy, incompatible, interface, wrap) আর Decorator-এর ১ (“wrap”) দেখায় যে দুটি pattern-ই মোড়ায়, পার্থক্য উদ্দেশ্যে: Adapter interface বদলায়, Decorator একই interface রেখে আচরণ যোগ করে। শেষ সমস্যায় কোনো সংকেত নেই — একটি invoice ছাপাতে কোনো pattern লাগে না, আর সেটাই সবচেয়ে সৎ উত্তর। সিদ্ধান্ত নিতে হয় Applicability আর Consequences পড়ে।
Code review আর ইন্টারভিউতে pattern-এর ভাষা
Pattern-এর সবচেয়ে বড় দৈনন্দিন লাভ শব্দভান্ডার। Code review-এ (পর্ব ৪৪) “এখানে Strategy” বললে reviewer জানেন কী খুঁজতে হবে: চুক্তিটা কোথায়, রূপগুলো কোথায়, কে বেছে দেয়। তবে ভাষার সাথে দায়িত্বও আসে:
- নাম বলুন, কারণও বলুন। “Observer ব্যবহার করেছি, কারণ খবর-পাওয়ার তালিকা প্রতি মাসে বাড়ছে” — কারণ ছাড়া pattern-এর নাম শুধু অলংকার।
- Review-তে উল্টো প্রশ্নও করুন। “এই abstraction কোন বদল সহজ করছে? দ্বিতীয় রূপটি কোথায়?” — pattern-itis ধরার সবচেয়ে ভদ্র প্রশ্ন।
- ইন্টারভিউতে সাধারণত তিন রকম প্রশ্ন আসে: একটি pattern বোঝানো (intent, কাঠামো, একটি উদাহরণ), দুটি pattern-এর পার্থক্য (Strategy বনাম State, Adapter বনাম Decorator), আর “আপনার কোনো project-এ pattern কোথায় কাজে লেগেছে, আর কোথায় ভুল ছিল”। শেষটিতে অতিরিক্ত pattern সরানোর গল্পও শক্ত উত্তর। পর্ব ৭০-এ (Software engineering ইন্টারভিউ) আরও অনুশীলন।
আরও গভীরে: মূল GoF বইয়ের প্রথম দুই অধ্যায় (পরিচিতি আর একটি document editor-এর case study, যেখানে কয়েকটি pattern একসাথে কাজ করে) প্রায়ই তালিকার চেয়ে বেশি শিক্ষণীয়। Pattern কীভাবে লিখতে হয়, তা জানতে PLoP সম্মেলনের “writers' workshop” রীতি দেখুন — লেখক চুপ থাকেন, অন্যরা তাঁর pattern নিয়ে আলোচনা করেন।
পরীক্ষা ও ইন্টারভিউতে:
- Design pattern কী? Algorithm-এর সাথে পার্থক্য? — নির্দিষ্ট প্রেক্ষাপটে বারবার আসা নকশা-সমস্যার নাম দেওয়া সমাধান; algorithm হিসাবের ধাপ, pattern কোড সাজানোর কাঠামো।
- GoF-এর তিন পরিবার আর সংখ্যা? — creational ৫, structural ৭, behavioral ১১; মোট ২৩।
- GoF-এর দুই মূল নীতি? — implementation নয়, interface ধরে প্রোগ্রাম; class inheritance-এর চেয়ে object composition-কে অগ্রাধিকার।
- Anti-pattern কাকে বলে? একটি উদাহরণ? — বারবার দেখা যায় এমন আকর্ষণীয় কিন্তু ক্ষতিকর সমাধান; যেমন god object বা golden hammer.
- Strategy আর State-এর পার্থক্য? — কাঠামো প্রায় একই; Strategy বাইরে থেকে বেছে দেওয়া হয়, State object নিজের অবস্থা বদলে নিজেই আচরণ বদলায়।
১০ · AI-এর যুগে — AI-এর “pattern-সমৃদ্ধ” কোড যাচাই
AI-কে design pattern ব্যবহার করে কোড লিখতে বললে প্রায় সবসময় কোড আসে — অনেক interface, factory আর manager-সহ। দেখতে পেশাদার, কিন্তু প্রয়োজন আছে কি না, তা AI জানে না; সে জানে আপনি pattern চেয়েছেন। এই বিষয়ে AI-এর চেনা ভুল:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| প্রমাণ ছাড়া pattern — একটিমাত্র রূপের Strategy, এক product-এর Factory, এক listener-এর Observer | প্রতিটি pattern-এর জন্য জিজ্ঞেস করুন: কোন বদল সহজ হলো, দ্বিতীয় রূপ কোথায়? উত্তর না থাকলে সরান |
| ভুল নাম — Decorator বলে Adapter লেখা, Strategy বলে State | Intent মিলিয়ে দেখুন: interface বদলায় না আচরণ যোগ হয়, বাইরে থেকে বেছে দেওয়া না নিজে বদলায় |
| দেখতে বিশ্বাসযোগ্য কিন্তু ভুল UML — realization-এর জায়গায় generalization, হীরা উল্টো প্রান্তে | পর্ব ২৯-এর চিহ্নের তালিকা পাশে রেখে প্রতিটি তীর পড়ুন; কোডের সাথে diagram মেলান |
| বানানো তথ্য — “MVC হলো GoF-এর ২৪তম pattern”, ভুল বছর, অস্তিত্বহীন বই | GoF-এ ২৩টি; MVC বইয়ের পরিচিতিতে উদাহরণ হিসেবে আছে, catalog-এ নয়। বই আর বছর মূল উৎসে মেলান |
| Refactor-এর পর আচরণ চুপচাপ বদলায় (rounding, ক্রম, default) | আগের test রেখে চালান; একটি ইচ্ছাকৃত bug ঢুকিয়ে দেখুন test ব্যর্থ হয় কি না (পর্ব ৫০) |
| অন্য project থেকে হুবহু কোড, license-এর উল্লেখ ছাড়া; prompt-এ গোপন তথ্য | বড় অংশের উৎস আর license যাচাই করুন; prompt থেকে key, password আর গ্রাহকের তথ্য সরান |
নিচের prompt-এ AI-কে আগে প্রয়োজন প্রমাণ করতে, পরে কোড দিতে বলা হয়েছে — আর “কোনো pattern নয়” উত্তরটিকেও বৈধ রাখা হয়েছে। কোড পাঠানোর আগে key, password আর গ্রাহকের তথ্য সরিয়ে নিন।
AI-এর দেওয়া pattern-ওয়ালা কোড হাতে পেলে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১একজন স্থপতির বাড়ি-নকশার ধারণা software-এ এত জনপ্রিয় হলো কেন? আর software জগৎ কি Alexander-কে ঠিকভাবে নিয়েছে?
Alexander-এর সমস্যাটা ছিল software-এর মতোই। ১৯৬০-৭০-এর দশকে তিনি দেখছিলেন, আধুনিক স্থাপত্য বিশেষজ্ঞদের হাতে এমন বাড়ি বানাচ্ছে, যেখানে মানুষ স্বস্তি পায় না; অথচ পুরোনো গ্রাম আর শহর — কোনো স্থপতি ছাড়াই — প্রায়ই বেশি বাসযোগ্য। তাঁর ব্যাখ্যা: পুরোনো সমাজে বারবার পরীক্ষিত সমাধানগুলো মানুষের মুখে মুখে ছিল — জানালা কোথায়, উঠোন কত বড়, রাস্তা কোথায় বাঁক নেবে। তিনি সেই জ্ঞান লিখে রাখতে চাইলেন, যাতে সাধারণ মানুষও নিজের বাড়ি নকশা করতে পারেন।
Software-এর মানুষরা ১৯৮০-৯০-এর দশকে ঠিক এই সমস্যায় ছিলেন। Object-oriented প্রোগ্রামিং ছড়িয়ে পড়ছিল, কিন্তু ভালো নকশার জ্ঞান ছিল অভিজ্ঞ কয়েকজনের মাথায়। নতুনরা একই ভুল বারবার করতেন। Pattern-এর ছাঁচ সেই অব্যক্ত জ্ঞানকে লেখা যায়, শেখানো যায় আর আলোচনা করা যায় এমন রূপ দিল। তার ওপর ছিল শব্দভান্ডারের জাদু: একটি নাম আধা পাতার ব্যাখ্যার কাজ করে।
তবে Alexander নিজে পুরোপুরি সন্তুষ্ট ছিলেন না বলে প্রচলিত বর্ণনা। ১৯৯৬ সালে OOPSLA সম্মেলনে software-এর মানুষদের উদ্দেশে তাঁর বক্তৃতায় তিনি — অনেকের স্মৃতি অনুযায়ী — বলেন, তাঁর কাছে pattern-এর আসল লক্ষ্য ছিল নৈতিক: মানুষের জীবনকে উন্নত করে এমন “জীবন্ত” কাঠামো বানানো। Software জগৎ ছাঁচ আর ফর্ম নিয়েছে, কিন্তু সেই প্রশ্ন — “এই কোড কি এর ব্যবহারকারী আর রক্ষণাবেক্ষণকারীর জীবন ভালো করছে?” — ততটা নেয়নি।
আরেকটি পার্থক্য: Alexander-এর pattern মিলে একটি “ভাষা” — বড় pattern ছোটগুলোকে ডাকে, ক্রম মেনে প্রয়োগ হয়। GoF catalog বেশি একটি অভিধানের মতো: ২৩টি প্রায় স্বাধীন entry, “Related Patterns” অংশে কিছু সংযোগ। Pattern “language” আর pattern “catalog”-এর এই পার্থক্য নিয়ে pattern community-তে দীর্ঘ আলোচনা হয়েছে।
মূল উপলব্ধি: Pattern-এর শক্তি অব্যক্ত অভিজ্ঞতাকে নাম আর ছাঁচ দেওয়ায়; কিন্তু Alexander-এর আসল প্রশ্নটি — সমাধানটি মানুষের জন্য ভালো কি না — ছাঁচের চেয়ে বেশি গুরুত্বপূর্ণ।
প্র ০২“Design pattern মানে ভাষার দুর্বলতা” — এই সমালোচনা কতটা ঠিক? Function-এর যুগে কি GoF বই পড়ার দরকার আছে?
সমালোচনাটির পক্ষে শক্ত প্রমাণ আছে। Peter Norvig-এর ১৯৯৬ সালের বিশ্লেষণে ২৩টির মধ্যে ১৬টি pattern dynamic ভাষায় অদৃশ্য বা অনেক সরল। Paul Graham-ও একটি প্রবন্ধে (প্রচলিত উদ্ধৃতি অনুযায়ী “Revenge of the Nerds”, ২০০২) লেখেন, নিজের কোডে pattern দেখলে তিনি একে সমস্যার সংকেত ভাবেন — কারণ তার মানে তিনি এমন কাজ হাতে করছেন, যা একটি ভালো abstraction-এর করার কথা। আজকের JavaScript-এ Strategy একটি function, Iterator ভাষার for…of, Observer browser-এর event ব্যবস্থা। Java ২০১৪-এ lambda যোগ করার পর অনেক Java কোডেও এই ছবি।
কিন্তু “ভাষা ভালো হলে pattern লাগবে না” — এটি অর্ধেক সত্য। প্রথমত, pattern অদৃশ্য হয় মানে তার ceremony চলে যায়, চিন্তা নয়। Function দিয়ে strategy লিখলেও প্রশ্নগুলো একই থাকে: চুক্তিটা কী, কে বেছে দেয়, default কী, নতুন রূপ কোথায় নিবন্ধন হবে। দ্বিতীয়ত, ভাষা যত ভালোই হোক, বড় স্তরের pattern — Adapter দিয়ে বাইরের system-এর সাথে জোড়া, Facade দিয়ে subsystem লুকানো, Command দিয়ে undo — ভাষার feature-এ মিশে যায় না। তৃতীয়ত, নতুন ভাষা নতুন pattern আনে: React-এর hooks-এর ব্যবহারবিধি, async কোডের retry আর cancellation, distributed system-এর circuit breaker — এগুলোও “বারবার আসা সমস্যার নাম দেওয়া সমাধান”।
তাহলে GoF বই কেন পড়বেন? প্রথম অধ্যায়ের নকশা-দর্শনের জন্য — interface ধরে লেখা, composition, বদলের জন্য নকশা — যা সময়ের সাথে পুরোনো হয়নি। প্রতিটি pattern-এর “Consequences” অংশের জন্য, যেখানে লেখকরা সৎভাবে দাম লিখেছেন। আর শব্দভান্ডারের জন্য — পুরোনো codebase, framework-এর documentation আর ইন্টারভিউ এখনো এই নামগুলোতে কথা বলে। তবে Sample Code অংশটি ১৯৯০-এর দশকের C++-এর সীমা মনে রেখে পড়ুন, আর নিজের ভাষায় সবচেয়ে সরল রূপটি কী হবে, তা নিজে ভেবে দেখুন।
মূল উপলব্ধি: ভাষা pattern-এর অনুষ্ঠান কমায়, প্রয়োজনীয় নকশার চিন্তা নয়; GoF পড়ুন দর্শন, দাম আর শব্দের জন্য — কোড কপির জন্য নয়।
প্র ০৩২৩টির মধ্যে Singleton-ই কেন সবচেয়ে বেশি সমালোচিত? তবু এটি এত জনপ্রিয় কেন?
Singleton-এর intent সরল: একটি class-এর একটিই object, আর তার একটি বৈশ্বিক প্রবেশপথ। এই সরলতাই এর জনপ্রিয়তার কারণ — শিখতে পাঁচ মিনিট, লিখতে দশ লাইন, আর “সব জায়গা থেকে config বা database connection পাওয়া যায়” — শুরুতে খুব সুবিধাজনক। বহু শিক্ষার্থীর প্রথম শেখা pattern এটিই।
সমস্যাটা ওই “বৈশ্বিক প্রবেশপথ”-এ। প্রথমত, এটি একটি global variable-এর ভদ্র নাম; যেকোনো কোড ভেতর থেকে getInstance() ডাকতে পারে, তাই function-এর signature দেখে বোঝা যায় না সে কিসের ওপর নির্ভর করে — লুকানো coupling (পর্ব ২৫)। দ্বিতীয়ত, test কঠিন: এক test-এ Singleton-এর অবস্থা বদলালে পরের test-এ তা থেকে যায়, test-এর ক্রম বদলালে ফল বদলায়; আর নকল বসানো (পর্ব ৪৯) কঠিন। তৃতীয়ত, একসাথে চলা thread-এ “একবারই তৈরি” নিশ্চিত করা জটিল — Java-র double-checked locking-এর পুরোনো ভুলগুলো এর বিখ্যাত উদাহরণ। চতুর্থত, “একটিই থাকবে” অনুমান প্রায়ই সময়ের সাথে ভাঙে — একটি database থেকে দুটি, এক tenant থেকে অনেক।
প্রচলিত বর্ণনা অনুযায়ী GoF-এর একজন লেখক Erich Gamma-ও ২০০৯ সালের একটি সাক্ষাৎকারে বলেছিলেন, বইটি আবার লিখলে তিনি Singleton বাদ দেওয়ার পক্ষে, কারণ এর ব্যবহার প্রায় সবসময় একটি নকশার দুর্গন্ধ। হুবহু কথাটি মূল উৎসে মিলিয়ে নেবেন, তবে সমালোচনার সুরটি বহু আলোচনায় পাওয়া যায়।
তাহলে বিকল্প কী? সাধারণ পরামর্শ: “একটিই object” আর “সব জায়গা থেকে পাওয়া” — এ দুটি আলাদা করুন। প্রোগ্রাম শুরুর জায়গায় (composition root, পর্ব ২৮ ও ৩৭) একটি object বানিয়ে যার দরকার তাকে constructor-এ দিন — dependency injection. Object একটিই থাকল, কিন্তু নির্ভরতা দৃশ্যমান, test-এ সহজে বদলানো যায়। Logger বা সত্যিকারের hardware-এর একমাত্র প্রবেশপথের মতো কিছু জায়গায় Singleton-ধাঁচ এখনো যুক্তিসংগত হতে পারে — কিন্তু সেটা সিদ্ধান্ত হিসেবে, অভ্যাস হিসেবে নয়।
মূল উপলব্ধি: Singleton-এর সমস্যা “একটিই object” নয়, “লুকানো global প্রবেশপথ”; object একটি রাখুন, কিন্তু নির্ভরতা দৃশ্যমান রাখুন।
প্র ০৪“Pattern আবিষ্কার হয়, বানানো হয় না” — কথাটার মানে কী? Pattern community কীভাবে একটি নতুন pattern যাচাই করে?
GoF-এর ২৩টি pattern চার লেখক কল্পনা করে বানাননি। তাঁরা তখনকার বাস্তব system — Smalltalk-এর পরিবেশ, ET++ আর InterViews-এর মতো UI framework, বিভিন্ন compiler আর editor — ঘেঁটে দেখেছিলেন, ভালো নকশাকারীরা স্বাধীনভাবে বারবার কোন সমাধানে পৌঁছাচ্ছেন। প্রতিটি pattern-এর “Known Uses” অংশ সেই প্রমাণের তালিকা। অর্থাৎ pattern হলো পর্যবেক্ষণ: কেউ একটি পুনরাবৃত্তি দেখলেন, নাম দিলেন, লিখে রাখলেন। এই অর্থে pattern আবিষ্কৃত — যেমন ভাষাবিদ ব্যাকরণ আবিষ্কার করেন, বানান না।
এখান থেকে pattern-লেখকদের একটি প্রচলিত রীতি আসে, যাকে অনেকে “rule of three” বলেন: অন্তত তিনটি স্বাধীন known use না দেখা পর্যন্ত একটি সমাধানকে pattern হিসেবে দাবি না করা। একবার কাজ করলে সেটা একটি ভালো ধারণা; দুবার হলে কাকতাল হতে পারে; তিনবার স্বাধীনভাবে দেখা গেলে বোঝা যায়, সমস্যাটিতে কিছু একটা আছে যা মানুষকে এই সমাধানের দিকে টানে।
যাচাইয়ের আরেকটি সামাজিক প্রক্রিয়া Hillside Group আর PLoP সম্মেলনের “shepherding” আর “writers' workshop”. সম্মেলনের আগে একজন অভিজ্ঞ “মেষপালক” (shepherd) লেখকের সাথে কয়েক দফায় খসড়া উন্নত করেন। সম্মেলনে workshop-এ লেখক চুপ করে বসে থাকেন, আর অন্যরা তাঁর লেখা নিয়ে আলোচনা করেন — কী ভালো, কী অস্পষ্ট, কোথায় আরও প্রমাণ দরকার — যেন লেখক সেখানে নেই।
এর সাথে software engineering-এর একটি বড় শিক্ষা জড়িত: আপনার দলের নিজস্ব pattern-ও একইভাবে জন্মায়। একই ধরনের retry কোড তিন জায়গায় দেখলে, বা প্রতিটি নতুন report-এ একই ধাপ ফিরে আসলে, সেটা লিখে নাম দেওয়ার সময়। আবার একটি “pattern” যার একটিমাত্র ব্যবহার, সেটা আসলে এখনো একটি অনুমান — pattern-itis-এর শুরু প্রায়ই এখানেই।
মূল উপলব্ধি: Pattern-এর বৈধতা আসে বারবার, স্বাধীনভাবে দেখা ব্যবহার থেকে; একবারের চতুর সমাধানকে pattern বলার আগে আরও দুটি প্রমাণ খুঁজুন।
প্র ০৫AI যখন যেকোনো pattern-এর কোড কয়েক সেকেন্ডে লিখে দেয়, তখন একজন developer-এর pattern জানার মূল্য কোথায়?
প্রথমে মেনে নিই কী বদলেছে। Strategy বা Observer-এর boilerplate লিখতে এখন আর মুখস্থ লাগে না; AI মুহূর্তে লেখে, প্রায়ই নির্ভুল syntax-এ। “Pattern-এর কোড লেখা” দক্ষতার বাজারদর তাই কমেছে।
কিন্তু pattern-এর আসল কাজ কখনো কোড লেখা ছিল না; ছিল বিচার — এই প্রেক্ষাপটে এই সমস্যার জন্য কোন কাঠামো, কোন দামে। AI এখানে তিনভাবে দুর্বল। প্রথমত, সে আপনার প্রেক্ষাপট জানে না: কোন অংশ গত দুই বছরে বারবার বদলেছে, backlog-এ কী আসছে, দল কোন pattern চেনে। আপনি pattern চাইলে সে pattern দেবেই — প্রয়োজন থাকুক বা না থাকুক। দ্বিতীয়ত, তার ভুল বিশ্বাসযোগ্য চেহারায় আসে: Decorator-এর নামে Adapter, উল্টো দিকের তীরওয়ালা UML, বানানো বই বা বছর। এগুলো ধরতে পারবেন শুধু তিনি, যিনি intent আর কাঠামো জানেন। তৃতীয়ত, AI-লেখা কোড পরিমাণে বেশি; অতিরিক্ত abstraction-এর দাম — পড়া, বোঝা, বদলানো — তাই দ্রুত জমে, যা পরে technical debt হয় (পর্ব ৪৩)।
ফলে AI-এর যুগে pattern জানার মূল্য সরে গেছে তিন জায়গায়। (১) Review: AI-এর প্রস্তাব পড়ে বলতে পারা, “এখানে দ্বিতীয় রূপ নেই, function-ই যথেষ্ট” বা “এটা আসলে State, Strategy নয়”। (২) নির্দেশনা: AI-কে সঠিক প্রশ্ন করা — আগে সরলতম সমাধান চাওয়া, তারপর সেটা কেন যথেষ্ট নয় তা জিজ্ঞেস করা — যেমন অংশ ১০-এর prompt-এ দেখেছেন। (৩) যোগাযোগ: মানুষ-মানুষে আর মানুষ-AI-তে নকশা নিয়ে কথা বলার সংক্ষিপ্ত ভাষা। একটি শব্দে — “এখানে Adapter, Facade নয়” — AI-কেও স্পষ্ট নির্দেশ দেওয়া যায়।
মূল উপলব্ধি: AI pattern-এর কোড সস্তা করেছে, pattern-এর বিচার নয়; প্রেক্ষাপট, দাম আর নাম ঠিক রাখার দায় এখনো আপনার।
অনুশীলন
একটি সমস্যা-pattern মেলানো, একটি আউটপুট অনুমান, একটি অতিরিক্ত pattern-এর সমালোচনা আর একটি হিসাব — প্রতিটির পূর্ণ উত্তর দেওয়া আছে।
-
সমস্যার সাথে pattern মেলান (কাল্পনিক অ্যাপ): প্রতিটির জন্য একটি GoF pattern বলুন, এক লাইনে কারণ, আর একটি সরল বিকল্প। (ক) একটি পুরোনো SMS gateway-এর method
sendText(phone, msg), কিন্তু নতুন কোড চায়notify(user, message). (খ) ফলাফল পোর্টালে GPA হিসাবের তিনটি নিয়ম — পুরোনো syllabus, নতুন syllabus, বিদেশি ছাত্র — বিভাগ ভেদে বেছে নিতে হয়। (গ) একটি text editor-এ undo আর redo. (ঘ) বিশ্ববিদ্যালয়ের বিভাগ, তার ভেতরে বিভাগ, আর তার ভেতরে কোর্স — সবার মোট ক্রেডিট একইভাবে বের করতে হয়। (ঙ) একটি অর্ডার “নতুন → প্যাক → পথে → পৌঁছেছে / ফেরত” অবস্থায় যায়, আর প্রতিটি অবস্থায় “বাতিল” চাপলে ভিন্ন কিছু ঘটে। (চ) একটি report ছাপার আগে log করা, cache থেকে দেখা আর ব্যর্থ হলে আবার চেষ্টা — যেকোনো সমন্বয়ে।- (ক) Adapter. Interface অমিল, কাজ একই — নতুন interface-এর পেছনে পুরোনোটিকে রূপান্তর। সরল বিকল্প:
notify = (u, m) => gateway.sendText(u.phone, m)— এক লাইনের wrapper function. - (খ) Strategy. একই কাজের (GPA) কয়েকটি algorithm, বেছে নেওয়া চলার সময়। সরল বিকল্প: নিয়মের নাম থেকে function-এর একটি map.
- (গ) Command (প্রয়োজনে Memento). প্রতিটি সম্পাদনা একটি object, যার
doআরundoআছে, দুটি stack-এ রাখা। পুরো লেখার snapshot সস্তা হলে Memento-ধাঁচে প্রতিটি ধাপের snapshot রাখাই সরলতম। - (ঘ) Composite. অংশ (কোর্স) আর সমগ্র (বিভাগ) একই interface —
credits()— দিয়ে গাছের মতো। সরল বিকল্প: nested data আর একটি recursive function. - (ঙ) State. আচরণ অবস্থার ওপর নির্ভর করে, আর অবস্থা নিজে বদলায়। সরল বিকল্প: পর্ব ২৩-এর state diagram থেকে একটি transition টেবিল — (অবস্থা, ঘটনা) → নতুন অবস্থা।
- (চ) Decorator. একই interface রেখে স্তরে স্তরে আচরণ যোগ, যেকোনো ক্রমে। সরল বিকল্প: function মোড়ানো function —
withRetry(withCache(withLog(print))).
সরল বিকল্পগুলোও আসলে একই চিন্তা, হালকা পোশাকে। পরীক্ষায় নাম জিজ্ঞেস করলে নামটি লিখুন; বাস্তব কাজে আগে সরল রূপটি ভাবুন।
- (ক) Adapter. Interface অমিল, কাজ একই — নতুন interface-এর পেছনে পুরোনোটিকে রূপান্তর। সরল বিকল্প:
-
আউটপুট অনুমান করুন: নিচের JavaScript কোড চালালে ঠিক কী ছাপবে? বিশেষভাবে ভাবুন:
rules.reverse()-এর পর আগে বানানোpipelineকি বদলায়? আরconsole.log-এর দুটি argument কোন ক্রমে হিসাব হয়?const log = []; const withLog = (name, f) => x => { const r = f(x); log.push(`${name}:${x}->${r}`); return r; }; const rules = [withLog("half", x => x / 2), withLog("minus50", x => x - 50)]; const compose = list => list.reduce((g, f) => x => f(g(x)), x => x); const pipeline = compose(rules); console.log(pipeline(300)); rules.reverse(); console.log(compose(rules)(300), pipeline(300)); console.log(log.length, log[2]);100 125 100 6 minus50:300->250composeনিয়মগুলো বাঁ থেকে ডানে জোড়ে: প্রথমেhalf, তারপরminus50. তাইpipeline(300)= ৩০০ / ২ = ১৫০, তারপর ১৫০ − ৫০ = ১০০।reverse()array-টিকে জায়গায় উল্টায়, কিন্তুpipelineতৈরির সময়েই প্রতিটি function closure-এর ভেতরে ধরা পড়ে গেছে — তাই এটি বদলায় না। নতুনcompose(rules)এখন আগেminus50: ৩০০ − ৫০ = ২৫০, তারপর ২৫০ / ২ = ১২৫। Argument বাঁ থেকে ডানে হিসাব হয়, তাই আগে ১২৫, তারপর পুরোনো pipeline-এর ১০০। মোট ছয়টি ডাক log হয়েছে (প্রতিবার দুটি নিয়ম × তিনবার), আর তৃতীয়টি (index ২) হলো নতুন ক্রমের প্রথম ডাকminus50:300->250. শিক্ষা: Decorator বা pipeline-এ মোড়ানোর ক্রম ফল বদলায় — ১০% ছাড় আগে না ৫০ টাকা ছাড় আগে, তা ব্যবসার সিদ্ধান্ত, কোডের দুর্ঘটনা নয়। -
নকশার সমালোচনা — অতিরিক্ত pattern: একটি কাল্পনিক রেস্তোরাঁর অর্ডার অ্যাপে একজন সহকর্মী (বা একটি AI) এই নকশা দিয়েছেন: (১)
TipCalculatorStrategyinterface, একটিTenPercentTipclass আর একটিTipStrategyFactory— বকশিশের নিয়ম সবসময় ১০%, কেউ বদল চায়নি; (২)MenuRepositoryএকটি Singleton, যা নিজের ভেতরে database খোলে, আর test-গুলো মাঝে মাঝে ক্রম বদলালে ব্যর্থ হয়; (৩) অর্ডারের অবস্থা (নতুন, রান্না হচ্ছে, পরিবেশিত, বিল দেওয়া) সাতটি function-এ ছড়ানো if/else দিয়ে, আর গত তিন মাসে দুটি নতুন অবস্থা যোগ হয়েছে; (৪) প্রতিটি class-এর নামের শেষে pattern:OrderObserverSubjectImpl,BillBuilderFactory. প্রতিটির মূল্যায়ন করুন — কী রাখবেন, কী বদলাবেন, কেন?- (১) Pattern-itis — সরান। একটিমাত্র রূপ, বদলের ইতিহাস নেই, কেউ চায়নি — YAGNI. একটি
tip(total)function আর তার test যথেষ্ট। দ্বিতীয় নিয়ম এলে function-টিকে parameter হিসেবে নিলেই Strategy — এক মিনিটের refactoring. - (২) Singleton-এর অপব্যবহার — বদলান। লুকানো global অবস্থাই test-এর ক্রম-নির্ভর ব্যর্থতার কারণ। প্রোগ্রাম শুরুতে একটি repository বানিয়ে যার দরকার তাকে constructor-এ দিন (পর্ব ৩৭); test-এ একটি নকল বা in-memory repository দিন। Object একটিই থাকতে পারে, কিন্তু প্রবেশপথ global নয়।
- (৩) এখানে pattern দরকার — যোগ করুন। একই অবস্থা-নির্ভর if/else সাত জায়গায়, আর বদলের অক্ষ প্রমাণিত (তিন মাসে দুটি নতুন অবস্থা)। State pattern, অথবা হালকা রূপে একটি transition টেবিল আর অবস্থা-প্রতি আচরণের map. নতুন অবস্থা মানে এক জায়গায় যোগ।
- (৪) নামে pattern নয়, দায়িত্ব।
OrderObserverSubjectImplবলে না এটি কী করে;OrderEventsবাOrderStatusFeedবলে।BillBuilderFactoryসম্ভবত দুই স্তরের অপ্রয়োজনীয় indirection — একটি builder-কে বানাতে factory কেন লাগছে, সেই প্রশ্ন review-তে তুলুন।
সারকথা: একই নকশায় pattern-এর অপব্যবহার (১, ২, ৪) আর pattern-এর প্রয়োজন (৩) পাশাপাশি থাকতে পারে। বিচারের মাপকাঠি বদলের প্রমাণ আর দামের হিসাব — pattern-এর নাম নয়।
- (১) Pattern-itis — সরান। একটিমাত্র রূপ, বদলের ইতিহাস নেই, কেউ চায়নি — YAGNI. একটি
-
হিসাব — class গোনা আর খেলনা score: (ক) একটি কাল্পনিক অনলাইন বাজারে ৫টি ছাড়ের নিয়ম। GoF-ধাঁচের Strategy-তে লাগে ১টি abstract base, ১টি Context আর প্রতি নিয়মে ১টি concrete class; function-ধাঁচে প্রতি নিয়মে ১টি function আর ১টি
payfunction. দুই রূপে মোট কয়টি class বা function? আরও ৩টি নিয়ম যোগ হলে কয়টি? সাধারণ সূত্র কী? (খ) অংশ ৯-এর খেলনা finder-এর catalog ধরে “queue each request and keep a history for undo” সমস্যার জন্য Command, Adapter আর Observer-এর score হাতে গুনুন। (গ) “add an optional layer at runtime to wrap the report” সমস্যায় Decorator আর Adapter-এর score কত, আর তালিকার ক্রম কী হবে?(ক) Class-রূপ: ১ + ১ + n. n = ৫ হলে ৭টি class; n = ৮ হলে ১০টি। Function-রূপ: n + ১. n = ৫ হলে ৬টি function; n = ৮ হলে ৯টি। সংখ্যার পার্থক্য মাত্র ১ — অর্থাৎ বড় লাভটা সংখ্যায় নয়, প্রতিটি এককের আকারে: অংশ ৬-এর কোডে তিনটি নিয়মের class-রূপ মিলে ১৪ লাইন, function-রূপ ৪ লাইন। নিয়ম বাড়লে দুই রূপই একটি করে নতুন একক যোগ করে, পুরোনো কিছু বদলায় না — দুটিই open-closed (পর্ব ২৭)।
(খ) সমস্যার শব্দ: queue, each, request, and, keep, a, history, for, undo. Command-এর সংকেত — undo, queue, request, action, history — এর মধ্যে মেলে undo, queue, request, history: ৪। Adapter-এর সংকেত (incompatible, legacy, convert, wrap, interface): ০। Observer-এর (notify, subscribe, event, many, change): ০। তালিকায় শুধু Command(4) — কারণ catalog-এর অন্য কোনো pattern-এর সংকেতও এই শব্দগুলোতে নেই (“keep” বা “each” কোনো তালিকায় নেই)।
(গ) শব্দ: add, an, optional, layer, at, runtime, to, wrap, the, report. Decorator-এর সংকেত add, wrap, layer, runtime, optional — পাঁচটিই মেলে: ৫। Adapter-এর মেলে শুধু wrap: ১। আর কোনো pattern-এর সংকেত মেলে না। তালিকা:
Decorator(5), Adapter(1). শিক্ষাটি অংশ ৯-এর মতোই — “wrap” শব্দ দুটি pattern-ই ভাগ করে; পার্থক্য উদ্দেশ্যে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নকশার সমস্যায় pattern বেছে নেওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- সমস্যাটি pattern-এর নাম ছাড়া এক বাক্যে লেখা
- কোন অংশ বদলায় আর কোন অংশ স্থির, তা প্রমাণসহ খোঁজা
- প্রার্থী pattern-গুলোর Intent আর Applicability মেলানো
- প্রতিটির Consequences-এর দাম সবচেয়ে সরল সমাধানের সাথে তুলনা
- বেছে নেওয়া রূপটি test-সহ লেখা আর review-তে নাম ও কারণ জানানো
Gamma, Helm, Johnson ও Vlissides-এর ১৯৯৪ সালের Design Patterns বইয়ের ২৩টি pattern উদ্দেশ্য ধরে তিন পরিবারে ভাগ করা। কোন বণ্টনটি সঠিক?
- Creational ৭, structural ৫, behavioral ১১
- Creational ৬, structural ৬, behavioral ১১
- Creational ৫, structural ৭, behavioral ১১
- Creational ৫, structural ৯, behavioral ৯
নিচের কোনটি Gang of Four-এর behavioral pattern নয়?
- Memento
- Interpreter
- Visitor
- Facade
Gang of Four-এর নীতি “favor object composition over class inheritance”-এর সবচেয়ে সঠিক ব্যাখ্যা কোনটি?
- Inheritance কখনো ব্যবহার করা যাবে না
- আচরণ পুনর্ব্যবহারে আগে ভাবুন অন্য object-কে ভেতরে রেখে কাজ করানো যায় কি না, কারণ তাতে বাঁধন হালকা আর অংশ বদলানো যায়
- Composition সবসময় inheritance-এর চেয়ে দ্রুত চলে
- Class diagram-এ inheritance-এর তীরের বদলে ভরাট হীরা আঁকতে হবে
১৯৯৬ সালে Peter Norvig-এর একটি বক্তৃতার বহুল উদ্ধৃত পর্যবেক্ষণ কোনটি?
- GoF-এর ২৩টির মধ্যে ১৬টি pattern dynamic ভাষায় অদৃশ্য বা অনেক সরল
- Design pattern একটি অর্থহীন ধারণা, কোনো ভাষায় দরকার নেই
- Dynamic ভাষায় pattern আরও জটিল হয়ে যায়
- GoF catalog-এ আরও ১৬টি pattern যোগ করা দরকার
একটি দল সদ্য Observer pattern শিখে নতুন সব feature-এ — এমনকি একটিমাত্র listener থাকলেও — event, subject আর observer class বসাচ্ছে। এটি কোন anti-pattern-এর সবচেয়ে কাছাকাছি?
- God object
- Singleton-এর অপব্যবহার
- Golden hammer
- Shotgun surgery
একটি কাল্পনিক অ্যাপের নতুন কোড pay(order) নামের method আশা করে, কিন্তু একটি পুরোনো payment library দেয় makePayment(amount, currency). Library-টি বদলানো যাবে না। কোন pattern সবচেয়ে উপযুক্ত?
- Decorator
- Adapter
- Observer
- Singleton
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Design pattern = নির্দিষ্ট প্রেক্ষাপটে বারবার আসা নকশা-সমস্যার নাম দেওয়া সমাধান; চার অংশ: নাম, সমস্যা, সমাধান, ফলাফল। কপির কোড নয়, মানিয়ে নেওয়ার ছাঁচ।
- Alexander (১৯৭৭) → Beck ও Cunningham (১৯৮৭) → Gang of Four (১৯৯৪): ২৩টি pattern — creational ৫, structural ৭, behavioral ১১।
- GoF বিবরণ পড়ুন Intent, Applicability আর Consequences দিয়ে; class diagram-এ চুক্তি, পূরণকারী, ধরে রাখা আর ব্যবহার — চারটি প্রশ্ন।
- দুই নীতি: interface ধরে লিখুন, composition বেছে নিন। First-class function-এর ভাষায় Strategy, Command, Template Method, Observer অনেক হালকা।
- Anti-pattern — golden hammer, god object, Singleton-এর অপব্যবহার, pattern-itis. সমস্যা থেকে শুরু, সরল সমাধানের সাথে তুলনা, তারপর pattern.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৩ · Creational pattern — singleton, factory method, abstract factory, builder আর prototypeপরবর্তী পর্বObject তৈরির পাঁচটি GoF pattern.
- পর্ব ৩১ · Interface আর contract — design by contract, error handling আর immutabilityআগের পর্বPattern-এর ভূমিকাগুলো যে চুক্তি দিয়ে জোড়া লাগে।
- পর্ব ২৯ · UML class diagram — class, association, aggregation, composition আর inheritanceঝালিয়ে নিনPattern-এর Structure অংশ পড়ার চিহ্নগুলো।
- সফটওয়্যার ইঞ্জিনিয়ারিং ও Git · কোর্স হোম৭০টি পর্বপুরো কোর্সের রোডম্যাপ; মডিউল ৬-এর পাঁচটি pattern পর্ব।
- JavaScript · functional programmingপাশের ট্র্যাকFirst-class আর higher-order function — হালকা pattern-এর যন্ত্র।
- Python · inheritance আর compositionগভীরে যানGoF-এর দ্বিতীয় নীতি Python-এ হাতেকলমে।