এই পর্বে যা শিখবেন
- UML 2.5.1 অনুযায়ী class-এর বাক্স: attribute আর operation-এর পূর্ণ syntax, visibility, static, abstract, «interface» আর «enumeration»
- Association-এর multiplicity, role name আর navigability; association class আর qualified association
- Aggregation আর composition-এর আসল পার্থক্য (আর aggregation নিয়ে বিতর্ক); generalization, realization আর dependency-র সঠিক তীর
- Python আর Java কোড থেকে class diagram, আর জীবন্ত object থেকে object diagram — চালিয়ে দেখা যায় এমন কোডে
- Conceptual, specification আর implementation দৃষ্টিকোণ; চেনা ভুল; PlantUML/Mermaid-এ লেখা diagram আর AI-এর আঁকা diagram যাচাই
১ · Class diagram কেন — কোডের মানচিত্র
একটি কাল্পনিক বাস-টিকিট অ্যাপের কথা ভাবুন। ঈদের আগে এক সপ্তাহে লাখো মানুষ টিকিট কাটেন, অ্যাপের কোড প্রায় চল্লিশ হাজার লাইন। দলে নতুন একজন developer এলেন। প্রথম দিন তিনি জানতে চাইলেন: “একটি টিকিট কি একটি আসনের সাথে যুক্ত, নাকি একটি যাত্রার সাথে? বাস বাতিল হলে আসনের রেকর্ডের কী হয়? ছাত্র-টিকিট কি আলাদা class, নাকি শুধু একটি flag?” এই প্রশ্নগুলোর উত্তর কোডের কোথাও না কোথাও আছে — কিন্তু চল্লিশ হাজার লাইন পড়ে বের করতে কয়েক দিন লাগবে।
দলের lead হোয়াইটবোর্ডে দশটি বাক্স আর কয়েকটি রেখা আঁকলেন। পনেরো মিনিটে নতুন developer পুরো system-এর কাঠামো বুঝে গেলেন। সেই ছবিটিই class diagram.
বাড়ির নকশার (floor plan) কথা ভাবুন। নকশায় থাকে কয়টি ঘর, কোন ঘরের সাথে কোন ঘরের দরজা, কোনটি কোনটির ভেতরে। কিন্তু নকশায় থাকে না সকালে কে কোন ঘরে যান, বা রাতে কোন বাতি জ্বলে। Class diagram ঠিক তেমনি কাঠামো দেখায় — কোন ধরনের জিনিস আছে, প্রত্যেকে কী তথ্য রাখে, কী কাজ করতে পারে, কে কার সাথে যুক্ত। কে কখন কাকে ডাকে, সেই সময়ের গল্প বলে behaviour diagram — পরের পর্ব ৩০-এ (UML behaviour diagram) দেখব।
পর্ব ২৩-এ (Requirement modeling) আপনি domain model দেখেছেন — বাক্সে শুধু নাম আর attribute, রেখায় multiplicity. সেখানে UML-এর ইতিহাস আর use case diagram-ও বলা হয়েছে, তাই এখানে আর বলছি না। সেটি ছিল class diagram-এর একটি হালকা অংশ। এই পর্বে পুরো নোটেশন: operation, visibility, interface, পাঁচ রকম সম্পর্ক, আর কোডের সাথে diagram-এর মিল-অমিল।
Class diagram একটি structure diagram: এতে থাকে classifier (class, interface, enumeration…) আর তাদের মধ্যের স্থির সম্পর্ক. প্রতিটি class হলো একই ধরনের অনেক object-এর ছাঁচ; diagram-টি বলে “এই system-এ কোন object থাকা সম্ভব আর তারা কীভাবে যুক্ত হতে পারে” — কোনো নির্দিষ্ট মুহূর্তে কী আছে, তা নয়। নির্দিষ্ট মুহূর্তের ছবি object diagram (অংশ ৮)।
Class diagram কোথায় কাজে লাগে, তার একটি তালিকা:
| কাজ | Class diagram যেভাবে সাহায্য করে |
|---|---|
| নকশা নিয়ে আলোচনা | কোড লেখার আগে হোয়াইটবোর্ডে দায়িত্ব ভাগ — কোন class কী জানবে, কে কাকে চিনবে; ভুল সস্তায় ধরা পড়ে। |
| নতুন সদস্যকে বোঝানো | এক পাতায় মূল ধারণাগুলো; বিস্তারিত পরে কোডে। |
| নকশার মান বিচার | কোন class-এর দিকে অনেক তীর (বেশি coupling, পর্ব ২৫), কোথায় দীর্ঘ inheritance-এর শিকল — ছবিতে চোখে পড়ে। |
| Design pattern শেখা ও বলা | প্রতিটি design pattern-এর কাঠামো class diagram দিয়েই বোঝানো হয় (পর্ব ৩২ থেকে)। |
| পরীক্ষা ও ইন্টারভিউ | “এই বর্ণনা থেকে class diagram আঁকুন” — বিশ্ববিদ্যালয়, চাকরি আর ইন্টারভিউর পরীক্ষায় খুব চেনা প্রশ্ন। |
২ · একটি class-এর বাক্স — নাম, attribute আর operation
UML-এ একটি class একটি আয়তক্ষেত্র, সাধারণত তিনটি ঘরে (compartment) ভাগ করা: ওপরে নাম, মাঝে attribute, নিচে operation. নিচের ছবিতে কাল্পনিক বাস-টিকিট অ্যাপের Ticket class পূর্ণ নোটেশনে, আর পাশে দুটি বিশেষ ধরনের বাক্স।
Class-এর বাক্স — পূর্ণ নোটেশন
Name, attributes and operations; an enumeration; an abstract class
issuedCount static (পুরো class-এর একটি); italic Vehicle আর seatCount() abstract. নাম, ঘর আর চিহ্নগুলো নিচের টেবিলে।Attribute আর operation-এর syntax
UML 2.5.1-এ (OMG-র সর্বশেষ প্রকাশিত সংস্করণ, ২০১৭) একটি attribute-এর লেখার ছাঁচ, যেখানে বন্ধনীর অংশ ঐচ্ছিক:
visibility name : Type [multiplicity] = default {property}
উদাহরণ: - seatNo : String [0..1]
- stops : Stop [2..*] {ordered}
visibility name(param : Type, …) : ReturnType
উদাহরণ: + pay(method : PaymentMethod) : Receipt
- Multiplicity না লিখলে ধরা হয়
[1]— অর্থাৎ ঠিক একটি মান।[0..1]মানে মান না-ও থাকতে পারে (কোডে null বা None),[*]মানে একটি সংগ্রহ। - Default (
= BOOKED) হলো object তৈরির সময়ের শুরুর মান। - Property বাঁকা বন্ধনীতে:
{readOnly}— একবার বসলে বদলায় না;{ordered}— সংগ্রহের ক্রমের অর্থ আছে (রুটের stop-গুলো ক্রমে)। - Derived attribute-এর আগে
/:/ totalFare : Money— রাখা হয় না, হিসাব করে পাওয়া যায়। - Operation-এর return type না থাকলে কিছুই লেখা হয় না (অনেক বই আর tool
: voidলেখে — UML-এ বাধ্যতামূলক নয়)। Parameter-এর আগে দিক লেখা যায় (in,out,inout), ডিফল্টin.
Visibility, static আর abstract
| চিহ্ন | UML অর্থ | Java-তে | Python-এ (শুধু রীতি) |
|---|---|---|---|
+ | public — সবাই ব্যবহার করতে পারে | public | name |
- | private — শুধু এই class | private | __name (name mangling; আসলে private নয়) |
# | protected — এই class আর subclass | protected (একই package-ও পায়) | _name (“ভেতরের জিনিস” বোঝানোর রীতি) |
~ | package — একই package-এর সবাই | কোনো modifier না দিলে | সমতুল্য কিছু নেই |
| নিচে দাগ | static — object-এর নয়, class-এর | static | class variable, @staticmethod, @classmethod |
italic বা {abstract} | abstract — সরাসরি object হয় না / বাস্তবায়ন নেই | abstract | ABC আর @abstractmethod |
হাতে আঁকার সময় italic বোঝা কঠিন, তাই অনেকে abstract class-এর নামের নিচে {abstract} লেখেন — UML 2.5.1 দুটোই মেনে নেয়।
«interface», «enumeration» আর stereotype
দুই কোণা বন্ধনী (guillemet) «…»-এ লেখা শব্দ বাক্সের ধরন বা বাড়তি অর্থ বলে। «interface» লেখা বাক্স একটি interface — শুধু operation-এর চুক্তি; «enumeration» লেখা বাক্স একটি enumeration — নির্দিষ্ট কয়েকটি মান। কঠোরভাবে বললে UML-এ এ দুটি keyword; আর «entity», «use»-এর মতো লেবেলগুলো stereotype. দেখতে একই, তাই দৈনন্দিন কথায় সবাইকেই “stereotype” বলা হয়।
«abstract» কোনো মানক stereotype নয়। কিছু tool আর বই «abstract» লেখে, আর পাঠক বুঝেও যান। কিন্তু UML 2.5.1-এর নিয়মে abstract বোঝাতে italic নাম বা {abstract} property. পরীক্ষায় বা দলের মানদণ্ডে নিয়মের রূপটিই নিরাপদ।
শেষ কথা: সব ঘর সবসময় দেখাতে হয় না। UML ঘর লুকিয়ে রাখার (suppress) অনুমতি দেয়। আলোচনার ছবিতে শুধু নাম, বা নাম আর মূল কয়েকটি attribute — এটাই সাধারণ চর্চা। প্রতিটি getter-setter লেখা বাক্স তথ্য বাড়ায় না, ছবিটা ঢেকে দেয়।
৩ · Association — কে কাকে চেনে, কতজনকে
দুটি class-এর বাক্সের মাঝে একটি সাধারণ রেখা মানে association: এক class-এর object অন্য class-এর object-এর সাথে দীর্ঘস্থায়ীভাবে যুক্ত থাকতে পারে। কোডে সাধারণত এটি একটি field — Ticket-এর ভেতরে passenger. রেখাটির চারপাশে কয়েকটি তথ্য লেখা যায়:
- Association-এর নাম রেখার মাঝখানে, পাশে ছোট ভরাট ত্রিভুজ পড়ার দিক দেখায়: “Passenger holds ▶ Ticket”.
- Role name প্রান্তে:
+passenger— Ticket-এর চোখে ওই প্রান্তের object-টির ভূমিকা। কোডে প্রায়ই এটিই field-এর নাম, আর সামনে visibility-ও বসে। - Multiplicity প্রান্তে: ওই প্রান্তের কতগুলো object অন্য প্রান্তের একটি object-এর সাথে যুক্ত থাকতে পারে।
- Navigability: প্রান্তে খোলা তীরের মাথা মানে “এই দিকে যাওয়া যায়” — Ticket থেকে Passenger খুঁজে পাওয়া যায়। প্রান্তে ছোট
×মানে স্পষ্টভাবে “যাওয়া যায় না”। কোনো চিহ্ন না থাকলে বিষয়টি নির্দিষ্ট করা হয়নি — দুই দিকই বোঝায় না।
| লেখা | অর্থ | বাস-টিকিট অ্যাপে উদাহরণ |
|---|---|---|
1 | ঠিক একটি | প্রতিটি টিকিট ঠিক একজন যাত্রীর |
0..1 | নেই, বা একটি | টিকিটে আসন পরে বাছাই করা যায় — এখনো না-ও থাকতে পারে |
* বা 0..* | শূন্য বা যতগুলো খুশি | একজন যাত্রীর শূন্য বা অনেক টিকিট |
1..* | অন্তত একটি | একটি booking-এ অন্তত একটি টিকিট |
2..* | অন্তত দুটি | একটি রুটে অন্তত দুটি stop (শুরু আর শেষ) |
0..4 (n..m) | শূন্য থেকে চার | এক booking-এ সর্বোচ্চ চারটি টিকিট — ঈদের ভিড়ে কালোবাজারি ঠেকানোর নিয়ম |
পড়ার নিয়মটি পর্ব ২৩-এর মতোই: যে প্রান্তে সংখ্যা লেখা, সেটি সেই প্রান্তের class-এর সংখ্যা। “Booking 1 —— 1..* Ticket” পড়ুন দুবার: “একটি Booking-এর সাথে এক বা একাধিক Ticket”; “একটি Ticket-এর সাথে ঠিক একটি Booking”. Multiplicity শুধু সাজসজ্জা নয় — এটি ব্যবসার নিয়ম। “এক booking-এ সর্বোচ্চ চারটি টিকিট” নিয়মটি কোনো developer-এর মাথায় না থাকলেও diagram-এ লেখা থাকে।
Association class আর qualified association
কখনো সম্পর্কটির নিজেরই তথ্য থাকে। একজন যাত্রী অনেক যাত্রায় যান, একটি যাত্রায় অনেক যাত্রী — Passenger * —— * Trip. কিন্তু “ভাড়া কত, কোন আসন” তথ্যটি যাত্রীরও নয়, যাত্রারও নয় — এই নির্দিষ্ট জোড়াটির। তখন একটি association class আঁকা হয়: একটি class-এর বাক্স, ড্যাশ রেখা দিয়ে association-এর রেখার মাঝখানে জোড়া। আমাদের Ticket আসলে এমনই একটি জিনিস। কোডে প্রায় সবসময় এটি একটি সাধারণ class হয়ে যায়, যার দুটি field — ডাটাবেসের “join table”-এর মতো।
Qualified association-এ এক প্রান্তে ছোট একটি বাক্সে চাবি লেখা থাকে। “Trip [seatNo] —— 0..1 Ticket” মানে: একটি যাত্রা আর একটি আসন নম্বর দিলে সর্বোচ্চ একটি টিকিট পাওয়া যায়। চাবি ছাড়া একটি যাত্রার টিকিট *; চাবিসহ 0..1. অর্থাৎ “একই যাত্রায় একই আসন দুবার বিক্রি নয়” নিয়মটি ছবিতেই লেখা হয়ে গেল। কোডে এটি প্রায়ই একটি dictionary বা map: tickets_by_seat: dict[str, Ticket].
Multiplicity যাচাই — একটি ছোট checker
Diagram-এর multiplicity আর বাস্তব data মেলে কি না, তা যন্ত্র দিয়েও দেখা যায়। নিচের কোডে model-এর প্রতিটি association-প্রান্তের নিয়ম একটি তালিকায়, আর কয়েকটি object ও তাদের link আরেকটি তালিকায়। কোডটি প্রতিটি object-এর link গুনে নিয়মের সাথে মেলায়। এর আকার পর্ব ২৩-এর Order 1 — 1..* OrderLine-এর মতোই, শুধু এখানে Booking আর Ticket.
// Multiplicity checker for a tiny fictional bus-ticket model.
// Each association end: "how many <to> may one <from> be linked to?"
const ends = [
{ from: "Booking", to: "Ticket", m: "1..*" }, // a booking has 1 or more tickets
{ from: "Ticket", to: "Booking", m: "1" }, // each ticket is in exactly 1 booking
{ from: "Ticket", to: "Passenger", m: "1" },
{ from: "Ticket", to: "Seat", m: "0..1" }, // seat may be chosen later
];
const range = m => { const [lo, hi = lo] = (m === "*" ? "0..*" : m).split(".."); return [+lo, hi === "*" ? Infinity : +hi]; };
// Instances: [id, class] and links [a, b] (a link is navigable both ways here)
const objs = [["bk1","Booking"],["bk2","Booking"],["bk3","Booking"],["t1","Ticket"],["t2","Ticket"],
["t3","Ticket"],["p1","Passenger"],["s1","Seat"],["s2","Seat"]];
const links = [["bk1","t1"],["bk1","t2"],["bk2","t2"],["t1","p1"],["t2","p1"],
["t1","s1"],["t1","s2"],["t3","p1"]];
const cls = Object.fromEntries(objs);
let bad = 0;
for (const e of ends) {
const [lo, hi] = range(e.m);
for (const [id, c] of objs) {
if (c !== e.from) continue;
const n = links.filter(l => l.includes(id))
.map(l => (l[0] === id ? l[1] : l[0])).filter(o => cls[o] === e.to).length;
if (n < lo || n > hi) {
bad++;
console.log(`VIOLATION ${id}:${c} has ${n} ${e.to} (allowed ${e.m})`);
}
}
}
console.log(bad ? `${bad} violation(s)` : "model satisfied");আউটপুট:
VIOLATION bk3:Booking has 0 Ticket (allowed 1..*)
VIOLATION t2:Ticket has 2 Booking (allowed 1)
VIOLATION t3:Ticket has 0 Booking (allowed 1)
VIOLATION t1:Ticket has 2 Seat (allowed 0..1)
4 violation(s)
চারটি লঙ্ঘন, প্রতিটি একটি আলাদা ভুলের ধরন: bk3 একটি খালি booking (1..*-এর নিচের সীমা ভাঙা); t2 দুটি booking-এ ঢুকে গেছে (1-এর ওপরের সীমা ভাঙা — হয়তো দুবার “confirm” চাপার bug); t3 কোনো booking-এ নেই (অনাথ টিকিট); আর t1-এ দুটি আসন। লক্ষ করুন, s2 আসনটি কোনো টিকিটে নেই — সেটা লঙ্ঘন নয়, কারণ Seat-এর দিক থেকে কোনো নিয়ম লেখা হয়নি। links-এ ["bk2","t2"]-কে ["bk2","t3"] করে আবার চালিয়ে দেখুন — দুটি লঙ্ঘন কমবে (t2 আর t3 এখন ঠিক একটি করে booking-এ)। শুধু মুছে দিলে কিন্তু সংখ্যা কমে না: তখন bk2 নিজেই খালি booking হয়ে যায়। Diagram-এর প্রতিটি সংখ্যা তাই একটি test-এর প্রার্থী।
৪ · Aggregation আর composition — অংশ আর সমগ্র
কিছু association-এ একটি জিনিস অন্যটির অংশ: বাসের আসন, রুটের stop. UML এর জন্য দুটি বিশেষ রূপ দেয়, দুটিতেই হীরা (diamond) বসে সমগ্রের (whole) প্রান্তে।
◆ Composition — ভরাট হীরা
শক্ত মালিকানা। একটি অংশ এক সময়ে সর্বোচ্চ একটি সমগ্রের। সমগ্র মুছলে তার অংশগুলোও মুছে যায় — অংশের জীবনের দায়িত্ব সমগ্রের। তাই সমগ্রের প্রান্তের multiplicity 1 বা 0..1, কখনো * নয়।
◇ Aggregation — ফাঁপা হীরা
দুর্বল “অংশ-সমগ্র”. অংশ একাধিক সমগ্রে থাকতে পারে, সমগ্র মুছলেও অংশ থাকে। UML-এর ভাষায় এটি “shared” aggregation.
Route ◇—— Stopএকটি বাড়ি আর তার ঘরগুলোর কথা ভাবুন। বাড়ি ভেঙে ফেললে “তিনতলার দক্ষিণের ঘর” বলে আর কিছু থাকে না, আর একটি ঘর একসাথে দুটি বাড়ির হতে পারে না — এটা composition. এবার একটি ক্রিকেট দল আর খেলোয়াড়দের কথা ভাবুন। একজন খেলোয়াড় জাতীয় দলেও খেলেন, ঘরোয়া লিগের ক্লাবেও; একটি দল ভেঙে গেলে খেলোয়াড় হারিয়ে যান না — এটা aggregation-এর চেনা উদাহরণ।
UML 2.5.1-এর composition-এর নিয়ম দুটি: অংশটি এক সময়ে সর্বোচ্চ একটি composite-এর; আর composite মুছলে তার সব অংশ-object তার সাথে মুছে যায়। একটি সূক্ষ্মতা: মোছার আগে একটি অংশকে সরিয়ে নেওয়া যায় — তখন সেটি বাঁচে। একটি বাস বিক্রির আগে একটি আসন খুলে অন্য বাসে লাগালে আসনটির মালিক বদলায়, কিন্তু দুই বাসের একসাথে নয়।
Aggregation-এর অর্থ নিয়ে বিতর্ক আছে, আর UML নিজেই সেটা স্বীকার করে। UML 2.5.1-এর spec shared aggregation সম্পর্কে বলে, এর সুনির্দিষ্ট অর্থ প্রয়োগের ক্ষেত্র আর modeler-ভেদে আলাদা হয়। অর্থাৎ ফাঁপা হীরা দেখে কোডের কোনো নির্দিষ্ট আচরণ ধরে নেওয়া যায় না। Martin Fowler UML Distilled-এ UML-এর সহ-স্রষ্টা Jim Rumbaugh-এর একটি কথা উদ্ধৃত করেন বলে জানা যায় — aggregation-কে “modeling placebo” ভাবতে। তাই অনেক দল ফাঁপা হীরা একদম ব্যবহার করে না: শক্ত মালিকানা হলে composition, না হলে সাধারণ association. পরীক্ষায় দুটির পার্থক্য জানতে হবে; দলের কাজে স্পষ্টতার জন্য আলাদা করে লিখে দেওয়াই ভালো।
| প্রশ্ন | “হ্যাঁ” হলে |
|---|---|
| সমগ্র মুছলে অংশও কি মুছতে হবে, আর অংশ কি এক সময়ে একটিরই? | Composition ◆ |
| অংশ-সমগ্রের অর্থ আছে, কিন্তু অংশ ভাগ হয় আর স্বাধীনভাবে বাঁচে? | Aggregation ◇ (বা সাধারণ association, দলের রীতি অনুযায়ী) |
| “অংশ” বলার মতো কিছু নেই, শুধু দুটি জিনিস যুক্ত (যাত্রী আর টিকিট)? | সাধারণ association |
Composition আর aggregation-এর আসল পার্থক্য জীবনচক্রে, তাই কোডে চালিয়েই দেখা ভালো। নিচের কোডে alive নামের একটি set সব “বেঁচে থাকা” object রাখে। Bus নিজেই আসন তৈরি করে আর মোছে; Route শুধু আগে থেকে থাকা stop-গুলোর দিকে ইঙ্গিত রাখে।
// Composition (Bus owns its Seats) vs aggregation (a Route groups shared Stops).
const alive = new Set(); // every object that still exists
class Seat { constructor(no) { this.no = no; this.bus = null; alive.add(this); } }
class Bus { // composite: creates and destroys parts
constructor(reg, n) {
this.reg = reg; this.seats = []; alive.add(this);
for (let i = 1; i <= n; i++) this.attach(new Seat(reg + "-S" + i));
}
attach(seat) { // a part has at most one whole
if (seat.bus && seat.bus !== this) throw new Error(seat.no + " already belongs to " + seat.bus.reg);
seat.bus = this; this.seats.push(seat);
}
destroy() { this.seats.forEach(s => alive.delete(s)); this.seats = []; alive.delete(this); }
}
class Stop { constructor(name) { this.name = name; alive.add(this); } }
class Route { // aggregate: only refers to stops
constructor(name, stops) { this.name = name; this.stops = stops; alive.add(this); }
destroy() { alive.delete(this); } // the stops are NOT deleted
}
const count = (C) => [...alive].filter(o => o instanceof C).length;
const show = (t) => console.log(t.padEnd(26), "buses", count(Bus), "seats", count(Seat),
"routes", count(Route), "stops", count(Stop));
const b1 = new Bus("DHA-11", 3), b2 = new Bus("DHA-12", 2);
const gabtoli = new Stop("Gabtoli"), savar = new Stop("Savar"), aricha = new Stop("Aricha");
const r1 = new Route("Dhaka-Aricha", [gabtoli, savar, aricha]);
const r2 = new Route("Dhaka-Savar", [gabtoli, savar]); // shares two stops
show("start");
try { b2.attach(b1.seats[0]); } catch (e) { console.log("refused:", e.message); }
b1.destroy(); show("after b1.destroy()");
r1.destroy(); show("after r1.destroy()");
console.log("r2 still has:", r2.stops.map(s => s.name).join(", "));আউটপুট:
start buses 2 seats 5 routes 2 stops 3
refused: DHA-11-S1 already belongs to DHA-11
after b1.destroy() buses 1 seats 2 routes 2 stops 3
after r1.destroy() buses 1 seats 2 routes 1 stops 3
r2 still has: Gabtoli, Savar
শুরুতে ৩ + ২ = ৫টি আসন, কারণ প্রতিটি বাস নিজের আসন বানিয়েছে। b1-এর আসন b2-তে লাগানোর চেষ্টা প্রত্যাখ্যাত — composition-এর “একটি অংশ, একটি সমগ্র” নিয়ম attach-এ রক্ষা করা হয়েছে। b1.destroy()-এর পরে আসন ৫ থেকে ২: বাসের সাথে তার তিনটি আসনও গেল। কিন্তু r1.destroy()-এর পরে রুট ২ থেকে ১, অথচ stop এখনো ৩ — Gabtoli আর Savar r2-তেও আছে, আর Aricha হয়তো ভবিষ্যতের কোনো রুটে লাগবে। এই পার্থক্যই diagram-এ ভরাট বনাম ফাঁপা হীরা। লক্ষ করুন, Java বা Python-এ garbage collector থাকায় “মুছে ফেলা” প্রায়ই মানে “আর কেউ ধরে রাখছে না” বা “ডাটাবেস থেকে মোছা”; C++-এ composition প্রায়ই সরাসরি member object বা std::unique_ptr.
৫ · Generalization, realization আর dependency
বাকি তিনটি সম্পর্ক “কে কার অংশ” নয় — “কে কোন ধরনের” আর “কে কার ওপর নির্ভর করে”।
- Generalization: সাধারণ রেখা, সাধারণ class-এর দিকে ফাঁপা ত্রিভুজ।
StudentTicketএকটি বিশেষ ধরনেরTicket— “is-a” সম্পর্ক। কোডে এটি inheritance: Java-তেextends, Python-এclass StudentTicket(Ticket). Subclass যেন সত্যিই superclass-এর জায়গায় বসতে পারে — সেই শর্তটি Liskov substitution, পর্ব ২৮-এ (SOLID ২)। - Realization: ড্যাশ রেখা, interface-এর দিকে ফাঁপা ত্রিভুজ।
WalletPayment«interface» PaymentMethod-এর চুক্তি পূরণ করে। Java-তেimplements; Python-এ Protocol বা abstract class. বিকল্প নোটেশন “lollipop”: class থেকে একটি ছোট বৃত্ত বের হয়, পাশে interface-এর নাম। - Dependency: ড্যাশ রেখা, যাকে ব্যবহার করা হয় তার দিকে খোলা তীর। সবচেয়ে দুর্বল সম্পর্ক: একটি class অন্যটিকে অল্প সময়ের জন্য ব্যবহার করে — parameter, local variable, বা static call — field হিসেবে ধরে রাখে না। পাশে keyword লেখা যায়:
«use»,«create». অর্থ: “সরবরাহকারী বদলালে এটিও বদলাতে পারে।”
নিচের stepper-এ ছয়টি রেখা একটি একটি করে আসবে। প্রতিটির তীর বা হীরা কোন প্রান্তে, সেটি খেয়াল করুন।
- Association, navigable. সাধারণ রেখা; Passenger প্রান্তে খোলা তীর মানে Ticket থেকে Passenger খুঁজে পাওয়া যায় (কোডে
ticket.passenger), উল্টোটা নির্দিষ্ট করা হয়নি। পড়ুন: “একটি Ticket-এর ঠিক1জন Passenger; একজন Passenger-এর0..*Ticket.” - Aggregation. ফাঁপা হীরা সমগ্রের (Route) প্রান্তে। একটি রুটে অন্তত
2টি stop; একটি stop0..*রুটে — ভাগ করা যায়, তাই composition হতে পারে না। রুট বাতিল হলেও stop থাকে। - Composition. ভরাট হীরা সমগ্রের (Bus) প্রান্তে, আর সেখানে multiplicity
1— প্রতিটি আসন ঠিক একটি বাসের। বাস মুছলে তার সব আসন মুছে যায়। হীরা ভুল করে Seat প্রান্তে আঁকলে অর্থ দাঁড়ায় “আসন বাসের মালিক” — সবচেয়ে চেনা ভুলগুলোর একটি। - Generalization. ফাঁপা ত্রিভুজ সাধারণ class (Ticket)-এর দিকে, রেখা নিরেট। “StudentTicket is a Ticket” — তীরটি subclass থেকে superclass-এ যায়, কারণ subclass superclass-কে জানে, superclass subclass-কে জানে না। Multiplicity বা role এখানে লেখা হয় না।
- Realization. একই ফাঁপা ত্রিভুজ, কিন্তু ড্যাশ রেখা, আর ত্রিভুজ interface-এর দিকে। WalletPayment চুক্তিটি পূরণ করে; PaymentMethod নিজে কোনো কোড দেয় না। রেখা নিরেট হলে অর্থ বদলে generalization হয়ে যায়।
- Dependency. ড্যাশ রেখা আর খোলা তীর, যাকে ব্যবহার করা হয় তার (FareTable) দিকে। FareCalculator হয়তো একটি method-এর ভেতরে FareTable থেকে ভাড়া পড়ে, কিন্তু field হিসেবে রাখে না। মনে রাখার কৌশল: ত্রিভুজ = “ধরন”, হীরা = “অংশ” (সমগ্রের প্রান্তে), খোলা তীর = “চেনে বা ব্যবহার করে”; ড্যাশ = চুক্তি বা দুর্বল সম্পর্ক।
নিরেট রেখা = গঠনগত সম্পর্ক · ড্যাশ রেখা = চুক্তি বা দুর্বল নির্ভরতা
শক্তির ক্রমে সাজালে: dependency (সবচেয়ে দুর্বল) < association < aggregation < composition; আর generalization সবচেয়ে শক্ত বাঁধন, কারণ subclass superclass-এর ভেতরের খুঁটিনাটিও উত্তরাধিকারে পায়। একটি diagram-এ কোনো class-এর দিকে যত বেশি রেখা, সেটি বদলানো তত ঝুঁকিপূর্ণ — পর্ব ২৫-এর (coupling আর cohesion) মাপকাঠি এখানে চোখেই দেখা যায়।
৬ · পূর্ণ ছবি — বর্ণনা থেকে class diagram
এবার সব নোটেশন একসাথে। নিচের ছবিটি কাল্পনিক বাস-টিকিট অ্যাপের মূল অংশ — এতে ছয় রকম সম্পর্কের প্রতিটি অন্তত একবার আছে। Attribute আর operation লুকানো রাখা হয়েছে, যাতে কাঠামোটি পরিষ্কার দেখা যায়।
কাল্পনিক বাস-টিকিট অ্যাপের class diagram
Association with multiplicities, aggregation, composition, generalization, realization and dependency
ছবিটি পড়ে কয়েকটি বাক্য বলা যায়, যা কোড না দেখেই নতুন developer জানলেন: প্রতিটি টিকিট ঠিক একজন যাত্রীর আর ঠিক একটি যাত্রার; ছাত্র-টিকিট একটি বিশেষ টিকিট; টিকিট পেমেন্টের জন্য একটি PaymentMethod ব্যবহার করে কিন্তু ধরে রাখে না; mobile wallet সেই চুক্তির একটি বাস্তবায়ন; একটি বাসের আসনগুলো বাসেরই অংশ; রুট stop-গুলোকে ভাগ করে নেয়। তীরের দিক থেকে আরও বোঝা যায়: Passenger তার টিকিটের তালিকা নিজে রাখে না — “এই যাত্রীর সব টিকিট” পেতে হলে টিকিটগুলো খুঁজতে হবে (কোডে প্রায়ই একটি query দিয়ে)। এটি একটি নকশার সিদ্ধান্ত, আর ছবিতে স্পষ্ট।
এবার উল্টো কাজ — একটি লেখা বর্ণনা থেকে নিজে diagram আঁকা। পরীক্ষা আর ইন্টারভিউর সবচেয়ে চেনা প্রশ্ন এটিই। বর্ণনা (একটি কাল্পনিক জেলা গণগ্রন্থাগার): “প্রতিটি সদস্যের নাম আর ফোন নম্বর রাখা হয়। একজন সদস্য একসাথে সর্বোচ্চ তিনটি বই ধার নিতে পারেন, প্রতিটি ধারের ফেরতের তারিখ থাকে। গ্রন্থাগারে প্রতিটি বইয়ের (title আর ISBN) এক বা একাধিক কপি থাকে, প্রতিটি কপির একটি barcode. ধার দেওয়া হয় একটি নির্দিষ্ট কপি। কোনো বই catalogue থেকে বাদ দিলে তার কপিগুলোর রেকর্ডও বাদ যায়। ছাত্র-সদস্য একটি বিশেষ সদস্য, তাঁর student ID রাখা হয়।”
- বিশেষ্য থেকে class. বর্ণনার বিশেষ্য: সদস্য, বই, কপি, ধার, ছাত্র-সদস্য, গ্রন্থাগার, catalogue. “গ্রন্থাগার” পুরো system নিজেই, আর “catalogue” এখানে শুধু “সব বইয়ের তালিকা” — দুটিই বাদ। থাকল পাঁচটি:
Member,Book,Copy,Loan,StudentMember. “বই” আর “কপি” আলাদা — একটি title-এর অনেক ভৌত কপি, এই পার্থক্যই প্রশ্নের মূল ফাঁদ। - Attribute বসানো। শুধু বর্ণনায় যা আছে: নাম, ফোন, ফেরতের তারিখ, barcode (
code), title, ISBN, student ID. সব-(private) — তথ্য object-এর ভেতরে লুকানো থাকবে, বাইরে থেকে operation দিয়ে। কোনোmemberIdবাbookIdattribute নেই — সম্পর্ক দেখাবে রেখা, foreign key নয়। - Association আর multiplicity. “সর্বোচ্চ তিনটি” — Member
1——0..3Loan. এখানে Loan মানে চলতি ধার; ফেরত হওয়া ধারের ইতিহাস রাখতে চাইলে নিয়মটি অন্য জায়গায় যাবে (একটি constraint হিসেবে)। “ধার দেওয়া হয় একটি নির্দিষ্ট কপি” — প্রতিটি Loan ঠিক1টি Copy-র; একটি Copy এক সময়ে0..1টি চলতি ধারে। - Composition. “বই বাদ দিলে কপির রেকর্ডও বাদ” — জীবনচক্রের কথা, তাই Book প্রান্তে ভরাট হীরা। Book প্রান্তে
1(প্রতিটি কপি ঠিক একটি বইয়ের), Copy প্রান্তে1..*(“এক বা একাধিক কপি”)। Member আর Loan-এর মধ্যে composition নয় কেন? বর্ণনায় সদস্য মুছলে ধারের কী হবে, বলা নেই — এটি stakeholder-কে জিজ্ঞেস করার প্রশ্ন। - Generalization. “ছাত্র-সদস্য একটি বিশেষ সদস্য” — StudentMember থেকে Member-এর দিকে ফাঁপা ত্রিভুজ। StudentMember আলাদা করে name বা phone লেখে না; সেগুলো উত্তরাধিকারে পায়। (এটি ঠিক কি না, ভাবনার প্রশ্ন ২-এ দেখুন — পাস করার পরে ছাত্র আর ছাত্র থাকেন না!)
- Design-এর দিকে: navigability, role আর operation. এখন পর্যন্ত ছবিটি conceptual — বাস্তবের ধারণা। কোডের নকশায় গেলে প্রশ্ন আসে “কে কাকে চেনে?”: Member তার চলতি ধারগুলো চেনে (
+loans), Loan চেনে তার কপি (+item). আর দায়িত্ব: ধার নেওয়ার কাজটিMember.borrow(c : Copy) : Loan— যেখানে0..3নিয়মটি রক্ষা করা হবে। Domain model-এ এই ধাপটি থাকে না; design class diagram-এ থাকে (অংশ ৯)।
৭ · কোড থেকে diagram — Java আর Python
বাস্তবে প্রায়ই উল্টো দিক থেকে কাজ হয়: কোড আগে থেকেই আছে, আর আপনাকে তার ছবি আঁকতে হবে — নতুন দলে যোগ দিয়ে, কোড review-তে, বা পুরোনো system বোঝার সময় (reverse engineering বলা হয়)। মিলের মূল নিয়মগুলো:
| Java-তে | Python-এ | Class diagram-এ |
|---|---|---|
class B extends A | class B(A): | generalization, B থেকে A-র দিকে ফাঁপা ত্রিভুজ |
class W implements P | class W(P):, P একটি Protocol বা ABC | realization (ড্যাশ + ত্রিভুজ), P-এর বাক্সে «interface» |
private Seat seat; | seat: Seat (type hint) | association → Seat, multiplicity 1, role -seat; বা attribute - seat : Seat |
null হতে পারে / Optional<Seat> | seat: Seat | None | multiplicity 0..1 |
List<Seat> seats | seats: list[Seat] | multiplicity * (ক্রমের অর্থ থাকলে {ordered}) |
| শুধু parameter বা local variable | একই | dependency (ড্যাশ + খোলা তীর) |
static int count | class variable (ClassVar[int]) | নিচে দাগ |
abstract class / method | ABC + @abstractmethod | italic বা {abstract} |
enum TicketStatus | class TicketStatus(Enum) | «enumeration» |
একটি field-কে attribute হিসেবে লিখবেন, না association-এর রেখা হিসেবে? নিয়ম: মানটি যদি একটি সাধারণ data type হয় (String, Integer, Date, Money), attribute; যদি diagram-এর আরেকটি class হয়, রেখা। দুটোই একসাথে লিখবেন না — - seat : Seat attribute আর Seat-এর দিকে রেখা দিলে একই তথ্য দুবার, আর পাঠক ভাববেন দুটি আলাদা সম্পর্ক।
নিচের stepper-এ একটি ছোট Java কোড লাইন ধরে ধরে diagram-এ অনুবাদ হচ্ছে।
1public abstract class Vehicle {2 protected String regNo;3 public abstract int seatCount();4}5public class Bus extends Vehicle {6 private final List<Seat> seats = new ArrayList<>();7 private Driver driver; // may be null8 private static int busCount = 0;9 public int seatCount() { return seats.size(); }10 public Receipt sell(PaymentMethod m) { /* ... */ }11}12class Seat { String no; }13class WalletPayment implements PaymentMethod { /* ... */ }
| উপাদান | |
| কেন | |
| কোড যা বলে না |
- লাইন ১ আর ৩ — abstract class.
abstractশব্দের জন্যVehicle-এর নাম italic (বা নিচে{abstract}), আরseatCount()একটি italic abstract operation:+ seatCount() : Integer. - লাইন ২ — protected attribute.
protectedহলো#, আর String একটি সাধারণ type, তাই রেখা নয়, attribute:# regNo : String. (Java-তে protected একই package-কেও দেখতে দেয় — UML-এর#-এর চেয়ে একটু চওড়া।) - লাইন ৫ — generalization.
extends Vehicle: Bus থেকে Vehicle-এর দিকে নিরেট রেখা আর ফাঁপা ত্রিভুজ। BusseatCount()বাস্তবায়ন করে, তাই Bus-এর বাক্সে সেটি সাধারণ (italic নয়) লেখায়। - লাইন ৬ — সংগ্রহ মানে অনেক।
List<Seat>একটি field, আর Seat আমাদের একটি class — তাই রেখা, Seat প্রান্তে*আর role-seats. কিন্তু হীরা দেবেন কি? কোড বলে Bus নিজের তালিকা বানায় (new ArrayList), কিন্তু আসনগুলো কে বানায় আর অন্য কেউ ধরে রাখে কি না, এই লাইন থেকে বলা যায় না। - লাইন ৭ — nullable field. Driver না-ও থাকতে পারে (মন্তব্যে বলা), তাই
0..1. Java-র type এটি জানায় না — এজন্যই মন্তব্য,Optional, বা annotation দরকার হয়। - লাইন ৮ — static.
busCountপুরো class-এর একটি, তাই নিচে দাগ:- busCount : Integer = 0. - লাইন ১০ — dependency.
PaymentMethodশুধু parameter, Bus সেটি রেখে দেয় না — তাই association নয়, ড্যাশ রেখার dependency,«use». Field আর parameter-এর এই পার্থক্যই association আর dependency-র পার্থক্য। - লাইন ১২–১৩ — package আর realization.
String no-এর আগে কোনো modifier নেই, Java-তে সেটা package-private:~ no : String.implements PaymentMethod: ড্যাশ রেখা আর ফাঁপা ত্রিভুজ। এভাবে ১৩ লাইন থেকে ছয়টি বাক্স (Vehicle, Bus, Seat, Driver, PaymentMethod, WalletPayment) আর পাঁচটি রেখা।
Python দিয়ে নিজেই diagram বানানো
Python-এ class নিজেই নিজের সম্পর্কে অনেক কিছু বলে দেয়: __annotations__-এ type hint, __bases__ আর __mro__-এ উত্তরাধিকার, inspect module দিয়ে method-এর signature. নিচের কোড কয়েকটি class পড়ে PlantUML-এর মতো লেখায় একটি class diagram ছাপে। Python-এ সত্যিকারের visibility নেই, তাই কোডটি নামের রীতি ধরে নেয় (এটা আমাদের বেছে নেওয়া নিয়ম, ভাষার নিয়ম নয়): এক underscore _x হলে #, name mangling হওয়া __x হলে -, বাকি সব +. typing.is_protocol Python ৩.১৩ থেকে আছে।
import inspect, typing; from abc import ABC, abstractmethod
class PaymentMethod(typing.Protocol): # an interface
def pay(self, amount: int) -> str: ...
class Wallet(PaymentMethod): # realizes the interface
def pay(self, amount: int) -> str: return "OK"
class Seat:
no: str; held_by: str | None # "X | None" -> [0..1]
class Bus(ABC): # abstract class
seats: list[Seat] # list -> [*]
@abstractmethod
def capacity(self) -> int: ...
class AcBus(Bus):
_fare_rate: int # one "_" -> protected (#)
def capacity(self) -> int: return len(self.seats)
def __check(self, seat_no: str) -> bool: return True # "__" -> private (-)
def vis(n): return "#" if n.startswith("_") else "+" # naming convention!
def typ(t):
a, o = typing.get_args(t), typing.get_origin(t)
return a[0].__name__ + (" [*]" if o is list else " [0..1]") if a else t.__name__
for c in (PaymentMethod, Wallet, Seat, Bus, AcBus):
print("interface" if typing.is_protocol(c) else "abstract class" if inspect.isabstract(c) else "class", c.__name__, "{")
for n, t in (ann := inspect.get_annotations(c)).items(): # own __annotations__
print(f" {vis(n)}{n.lstrip('_')} : {typ(t)}")
for n, f in vars(c).items():
if not inspect.isfunction(f) or n.endswith("__"): continue
p = "_" + c.__name__ + "__"; s = inspect.signature(f) # __x is stored as _Class__x
v, n = ("-", n[len(p):]) if n.startswith(p) else (vis(n), n.lstrip("_"))
ps = ", ".join(f"{k} : {q.annotation.__name__}" for k, q in list(s.parameters.items())[1:])
print(f" {v}{n}({ps}) : {s.return_annotation.__name__}" + (" {abstract}" if getattr(f, "__isabstractmethod__", 0) else ""))
print("}")
for b in [b for b in c.__bases__ if b not in (object, ABC, typing.Protocol)]:
print(f"{b.__name__} {'<|..' if typing.is_protocol(b) else '<|--'} {c.__name__}")
for n, t in ann.items():
if typing.get_origin(t) is list: print(f'{c.__name__} --> "*" {typ(t)[:-4]} : {n}')
print("MRO of AcBus:", " -> ".join(k.__name__ for k in AcBus.__mro__))আউটপুট:
interface PaymentMethod {
+pay(amount : int) : str
}
class Wallet {
+pay(amount : int) : str
}
PaymentMethod <|.. Wallet
class Seat {
+no : str
+held_by : str [0..1]
}
abstract class Bus {
+seats : Seat [*]
+capacity() : int {abstract}
}
Bus --> "*" Seat : seats
class AcBus {
#fare_rate : int
+capacity() : int
-check(seat_no : str) : bool
}
Bus <|-- AcBus
MRO of AcBus: AcBus -> Bus -> ABC -> object
খেয়াল করুন: PaymentMethod Protocol বলে interface, আর Wallet সেটি সরাসরি subclass করেছে বলে realization <|... Bus-এ একটি abstract method থাকায় inspect.isabstract সত্য, তাই abstract class আর {abstract}. __check method-টি আসলে _AcBus__check নামে রাখা — কোডটি সেই mangled নাম চিনে -check ছাপল। held_by: str | None হলো [0..1], list[Seat] হলো [*] আর একটি association. শেষ লাইনে MRO: AcBus-এ কোনো নাম খুঁজতে Python আগে AcBus, তারপর Bus, তারপর ABC, শেষে object দেখে।
কিন্তু যা ছাপা হয়নি, সেটাই বড় শিক্ষা: (১) Bus --> "*" Seat — composition কি না, কোড জানে না; (২) [*]-এর নিচের সীমা ০ না ১, জানে না; (৩) Wallet যদি PaymentMethod-কে subclass না করে শুধু pay method রাখত, Python-এর structural typing-এ সেটিও চুক্তি পূরণ করত — কিন্তু এই কোড কোনো realization ছাপত না। স্বয়ংক্রিয় diagram কোডের রূপ দেখায়; নকশার উদ্দেশ্য মানুষকে যোগ করতে হয়। Python-এ abstract class আর Protocol-এর বিস্তারিত: Python · Abstract class আর Protocol; Java-তে: Java · Abstract class আর interface.
৮ · Object diagram — একটি মুহূর্তের ছবি
Class diagram বলে কী সম্ভব; object diagram বলে একটি নির্দিষ্ট মুহূর্তে কী আছে। এটি যেন রেলের সময়সূচি আর আজ সকাল সাতটায় কমলাপুর স্টেশনের একটি ছবির পার্থক্য। নোটেশন class diagram-এর মতোই, তিনটি পার্থক্যসহ:
- বাক্সের নাম
objectName : ClassName, নিচে দাগ দেওয়া। নাম না জানলে শুধু: Ticket. - Attribute-এর জায়গায় slot — নির্দিষ্ট মান:
fare = 850. Type লেখার দরকার নেই। - রেখাগুলো link — association-এর একটি করে উদাহরণ। এতে multiplicity লেখা হয় না, কারণ এখানে সংখ্যা গোনা যায়।
একটি মুহূর্ত: একজন যাত্রী, দুটি টিকিট, একটি যাত্রা
An object diagram that is an instance of the bus-ticket class diagram
0..* মেলে), প্রতিটি টিকিটের ঠিক একজন যাত্রী আর একটি যাত্রা (1 মেলে), বাসে দুটি আসন (1..* মেলে)।Object diagram-এর সবচেয়ে বড় কাজ class diagram পরীক্ষা করা। একটি বাস্তব পরিস্থিতি আঁকতে গিয়ে যদি দেখেন কোনো নিয়ম ভাঙতে হচ্ছে — যেমন একটি পরিবারের চারজনের জন্য একটি booking, অথচ class diagram-এ লেখা Booking 1 —— 1 Ticket — তাহলে class diagram-টি ভুল। কঠিন multiplicity নিয়ে stakeholder-এর সাথে আলোচনায়ও এটি কাজের: “এই ছবির মতো কি কখনো হয়?” প্রশ্নটি বিমূর্ত সংখ্যার চেয়ে অনেক সহজে বোঝা যায়।
জীবন্ত object থেকেও object diagram বানানো যায়। নিচের Python কোড কয়েকটি dataclass object-এর ভেতর ঘুরে (vars() দিয়ে) প্রতিটি সাধারণ মানকে slot আর প্রতিটি object-মানকে link হিসেবে PlantUML-এর object-লেখার আকারে ছাপে। এখানের ছোট model-এ Ticket সরাসরি একটি Seat ধরে রাখে, যা ছবি ২৯.২ থেকে একটু আলাদা।
from dataclasses import dataclass, field
@dataclass
class Seat:
no: str
@dataclass
class Bus:
reg_no: str
seats: list[Seat] = field(default_factory=list)
@dataclass
class Passenger:
name: str
@dataclass
class Ticket:
fare: int
passenger: Passenger
seat: Seat | None = None
bus = Bus("DHA-11", [Seat("A1"), Seat("A2")])
karim = Passenger("Karim")
tickets = [Ticket(850, karim, bus.seats[0]), Ticket(850, karim)]
names, lines, links = {}, [], []
def visit(obj):
"""Walk live objects; plain values become slots, objects become links."""
if id(obj) in names: return names[id(obj)]
names[id(obj)] = n = type(obj).__name__.lower() + str(len(names) + 1)
slots = []
for k, v in vars(obj).items():
for item in (v if isinstance(v, list) else [v]):
if hasattr(item, "__dict__"): links.append(f"{n} -- {visit(item)} : {k}")
elif item is not None: slots.append(f" {k} = {item!r}")
lines.append(f'object "{n} : {type(obj).__name__}" as {n} {{\n' + "\n".join(slots) + "\n}")
return n
for root in [bus] + tickets: visit(root)
print("\n".join(lines + links))আউটপুট:
object "seat2 : Seat" as seat2 {
no = 'A1'
}
object "seat3 : Seat" as seat3 {
no = 'A2'
}
object "bus1 : Bus" as bus1 {
reg_no = 'DHA-11'
}
object "passenger5 : Passenger" as passenger5 {
name = 'Karim'
}
object "ticket4 : Ticket" as ticket4 {
fare = 850
}
object "ticket6 : Ticket" as ticket6 {
fare = 850
}
bus1 -- seat2 : seats
bus1 -- seat3 : seats
ticket4 -- passenger5 : passenger
ticket4 -- seat2 : seat
ticket6 -- passenger5 : passenger
নামের নম্বর আসে দেখার ক্রম থেকে: bus1 প্রথমে, তারপর তার আসন seat2, seat3; কিন্তু object-গুলো ছাপা হয় তাদের ভেতরের সব কিছু দেখা শেষ হলে, তাই আসনগুলো বাসের আগে। id(obj) দিয়ে একই object দুবার আঁকা এড়ানো হয়েছে — Karim দুটি টিকিটে থাকলেও বাক্স একটিই (passenger5), আর তার দিকে দুটি link. দ্বিতীয় টিকিটের seat হলো None — কোনো slot নেই, কোনো link নেই; ঠিক এটাই 0..1-এর “শূন্য” দিক। আর লক্ষ করুন, seat2-এর দিকে দুটি link — বাস থেকে আর টিকিট থেকে। এটা composition ভাঙে না: আসনের মালিক বাস, টিকিট শুধু চেনে।
৯ · তিন দৃষ্টিকোণ, চেনা ভুল আর লেখা থেকে diagram
একই বাক্স, তিন স্তরের অর্থ
“Ticket” নামের একটি বাক্স তিনটি ভিন্ন জিনিস বোঝাতে পারে। Martin Fowler UML Distilled-এ (প্রথম সংস্করণ ১৯৯৭) class diagram-এর তিনটি perspective বা দৃষ্টিকোণ ব্যাখ্যা করেন, আর ধারণাটির কৃতিত্ব দেন Steve Cook আর John Daniels-কে (১৯৯৪ সালের বই Designing Object Systems) বলে জানা যায়। পরের সংস্করণে তিনি মূলত conceptual আর software — এই দুই ভাগে কথা বলেন; সংস্করণ মিলিয়ে নিন।
| দৃষ্টিকোণ | বাক্স মানে | যা থাকে | যা থাকে না |
|---|---|---|---|
| Conceptual | বাস্তব জগতের একটি ধারণা — পর্ব ২৩-এর domain model | নাম, ব্যবসার attribute, association, multiplicity | operation, visibility, navigability, কোডের type |
| Specification | Software-এর একটি অংশের interface — কী দায়িত্ব নেয় | public operation, navigability, interface, dependency | কীভাবে রাখা হয় (private field, কোন collection) |
| Implementation | কোডের একটি আসল class | সব field, private-protected, আসল type (ArrayList), static | প্রায় কিছুই বাদ নয় — তাই খুব বড় হয় |
সমস্যা হয় যখন একটি ছবিতে তিন স্তর মিশে যায়: domain model-এ ArrayList, বা আলোচনার ছবিতে প্রতিটি private field. আঁকার আগে নিজেকে জিজ্ঞেস করুন, “এই ছবিটি কে পড়বেন, কোন প্রশ্নের উত্তরে?” ব্যবসার মানুষের সাথে আলোচনায় conceptual; দলের মধ্যে module-এর চুক্তি ঠিক করতে specification (পর্ব ৩১-এর interface আর contract-এর বিষয়); কোনো জটিল অংশ refactor করার আগে implementation.
চেনা ভুল
| ভুল | কেন ভুল / ঠিক রূপ |
|---|---|
| হীরা অংশের প্রান্তে (Seat ◆—— Bus) | হীরা সবসময় সমগ্রের প্রান্তে: Bus ◆—— Seat. |
| Generalization-এর ত্রিভুজ subclass-এর দিকে | ত্রিভুজ সাধারণ class-এর দিকে; “StudentTicket ─▷ Ticket”. |
| Dependency-র তীর উল্টো | তীর ব্যবহারকারী থেকে যাকে ব্যবহার করা হয়, তার দিকে; FareCalculator ┄┄> FareTable. |
| Realization নিরেট রেখায় | নিরেট রেখা + ত্রিভুজ মানে generalization; interface-এর জন্য ড্যাশ রেখা। |
| Aggregation আর composition গুলিয়ে ফেলা | জীবনচক্র আর একক মালিকানা প্রশ্ন করুন (অংশ ৪); সন্দেহ থাকলে সাধারণ association. |
Composition-এ সমগ্রের প্রান্তে * | একটি অংশ এক সময়ে একাধিক composite-এ থাকতে পারে না; সমগ্রের প্রান্ত 1 বা 0..1. |
Domain model-এ method আর tripId জাতীয় foreign key | Conceptual স্তরে operation নয়; সম্পর্ক রেখা দিয়ে, id attribute দিয়ে নয় (পর্ব ২৩)। |
Attribute আর রেখা দুটোই (- trip : Trip + রেখা) | একই তথ্য দুবার; একটি বাছুন — class-type হলে রেখা। |
| সব association দুই দিকে navigable | দুই দিকেই ইঙ্গিত রাখলে দুটিকে মিলিয়ে রাখার ঝামেলা আর coupling বাড়ে; যেদিকে সত্যিই দরকার, শুধু সেদিকে তীর। |
| Static italic-এ, abstract দাগে | উল্টো: static = নিচে দাগ, abstract = italic. |
ER diagram বনাম class diagram: Database কোর্সের ER modeling দেখতে অনেকটা একই — বাক্স, রেখা, cardinality. পার্থক্য: ER (Peter Chen, ১৯৭৬) শুধু data আর তার সম্পর্ক দেখায়, table-এর নকশার দিকে যায়; class diagram-এ আচরণ (operation), interface, navigability আর inheritance-ও আছে। Many-to-many association ER থেকে table-এ গেলে join table হয়; class diagram-এ সেটি প্রায়ই একটি association class — যেমন আমাদের Ticket.
লেখা থেকে diagram — PlantUML আর Mermaid
হাতে আঁকা ছবি Git-এ রাখলে diff দেখা যায় না, review-ও কঠিন। তাই অনেক দল diagram লেখে সাধারণ text-এ: PlantUML (open-source, ২০০৯ সালের দিকে শুরু বলে জানা যায়) বা Mermaid (JavaScript-এ লেখা; কিছু code-hosting platform README-তে সরাসরি দেখায়)। নিচের দুটি উদাহরণ ছবি ২৯.২-এর একটি অংশ। Syntax সংস্করণ-ভেদে বদলাতে পারে, তাই নিজের tool-এ render করে যাচাই করুন।
@startuml
abstract class Vehicle {
#regNo : String
{abstract} +seatCount() : int
}
class Bus {
-busCount : int {static}
+seatCount() : int
}
interface PaymentMethod {
+pay(amount : int) : Receipt
}
enum TicketStatus {
BOOKED
PAID
CANCELLED
}
Vehicle <|-- Bus
Bus "1" *-- "1..*" Seat : seats
Route "0..*" o-- "2..*" Stop
Ticket "0..*" --> "1" Trip
WalletPayment ..|> PaymentMethod
Ticket ..> PaymentMethod : <<use>>
@endumlclassDiagram
class PaymentMethod {
<<interface>>
+pay(amount: int) Receipt
}
Vehicle <|-- Bus
Bus "1" *-- "1..*" Seat : seats
Route "0..*" o-- "2..*" Stop
Ticket "0..*" --> "1" Trip
WalletPayment ..|> PaymentMethod
Ticket ..> PaymentMethod : usesদুটিতেই চিহ্নগুলো ছবির মতো: <|-- ত্রিভুজ, *-- ভরাট হীরা, o-- ফাঁপা হীরা, .. ড্যাশ রেখা, > খোলা তীর। হীরা বা ত্রিভুজের চিহ্ন যে নামের পাশে, ছবিতে সেটি সেই বাক্সের প্রান্তে বসে — Bus "1" *-- "1..*" Seat-এ হীরা Bus-এর দিকে। Diagram-টি repository-তে docs/ ফোল্ডারে রাখলে প্রতিটি পরিবর্তন commit আর pull request-এ (পর্ব ১৬) দেখা যায়।
আরও গভীরে: UML 2.5.1-এর spec-এ class, property আর association-এর নিয়ম “Classification” আর “Structured Classifiers” অধ্যায়ে; সেখানে association-প্রান্তের মালিকানা দেখানোর একটি সূক্ষ্ম নোটেশনও আছে — প্রান্তে ছোট ভরাট বিন্দু, মানে প্রান্তটি অন্য class-এর একটি property. Diagram-এ যা আঁকা যায় না, এমন নিয়ম (“চলতি ধার ৩-এর বেশি নয়”, “ফেরতের তারিখ ধারের তারিখের পরে”) লেখার জন্য OMG-র OCL, যেমন context Member inv: loans->size() <= 3. Booch, Rumbaugh আর Jacobson-এর The Unified Modeling Language User Guide (১৯৯৯; দ্বিতীয় সংস্করণ ২০০৫) আর Fowler-এর UML Distilled (তৃতীয় সংস্করণ ২০০৩) দুটি ক্লাসিক শুরুর বই।
পরীক্ষা ও ইন্টারভিউতে:
- Aggregation আর composition-এর পার্থক্য? — Composition-এ অংশ এক সময়ে একটিরই আর সমগ্রের সাথে মোছে (ভরাট হীরা); aggregation-এ অংশ ভাগ হয় আর স্বাধীনভাবে বাঁচে (ফাঁপা হীরা)।
- Association আর dependency-র পার্থক্য? — Association দীর্ঘস্থায়ী যোগ (সাধারণত field); dependency অস্থায়ী ব্যবহার (parameter, local variable), ড্যাশ রেখা।
- Generalization আর realization-এর পার্থক্য? — দুটিতেই ফাঁপা ত্রিভুজ; generalization নিরেট রেখা, class থেকে superclass; realization ড্যাশ রেখা, class থেকে interface.
#আর~মানে? — protected আর package visibility.- Object diagram কেন? — একটি নির্দিষ্ট মুহূর্তের object আর link দেখিয়ে class diagram-এর multiplicity আর নিয়ম যাচাই করতে।
১০ · AI-এর যুগে — AI-এর আঁকা class diagram যাচাই
একটি বর্ণনা বা কোড দিলে AI কয়েক সেকেন্ডে PlantUML লিখে দেয়, আর render করলে ছবিটি খুব পেশাদার দেখায়। কিন্তু UML-এ তীরের একটি মাথা বা হীরার একটি প্রান্ত বদলালেই অর্থ উল্টে যায় — আর এই ভুলগুলো দেখতে নির্দোষ। এই বিষয়ে AI-এর চেনা ভুল:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| হীরা ভুল প্রান্তে, বা সব “has” সম্পর্কে composition | প্রতিটি হীরায় প্রশ্ন: সমগ্র মুছলে অংশ কি মোছে? অংশ কি একটিরই? |
| Interface-এর জন্য নিরেট রেখা, বা extends/implements উল্টো | কোডের extends/implements লাইনের সাথে প্রতিটি ত্রিভুজ মিলিয়ে দেখুন |
আন্দাজে multiplicity — সব জায়গায় 1..* বা কিছুই না | প্রতিটি সংখ্যার উৎস চান — বর্ণনার কোন বাক্য; একটি object diagram এঁকে পরীক্ষা করুন |
অপ্রয়োজনীয় pattern আর class যোগ (TicketFactory, AbstractManager) | প্রতিটি class-এর জন্য একটি requirement দেখাতে বলুন; না থাকলে বাদ |
| PlantUML/Mermaid-এর বানানো syntax, বা UML-এর বানানো নিয়ম (“UML 3.0”, «abstract» বাধ্যতামূলক) | আসলেই render করুন; tool-এর documentation আর OMG-র spec দেখুন |
| কোড থেকে diagram-এ visibility বা nullable ভুল | কোডের modifier আর type নিজে পড়ে মেলান; Python-এ visibility যে শুধু রীতি, তা মনে রাখুন |
নিচের prompt-এ AI-কে প্রতিটি সম্পর্কের পক্ষে যুক্তি আর অনুমানের তালিকা দিতে বলা হয়েছে, যাতে আপনি প্রতিটি রেখা যাচাই করতে পারেন। দলের আসল কোড বা requirement দেওয়ার আগে গ্রাহকের নাম, API key, password বা গোপন ব্যবসার নিয়ম সরিয়ে নিন; আর অন্যের লাইসেন্সযুক্ত কোড বিনা অনুমতিতে পেস্ট করবেন না।
AI-এর দেওয়া class diagram হাতে পেলে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১UML-এর স্রষ্টারাই যদি aggregation-কে “placebo” বলেন, তাহলে নোটেশনটি টিকে আছে কেন, আর আপনি কি এটি ব্যবহার করবেন?
ইতিহাসটা একটু জটিল। UML-এর আগের পদ্ধতিগুলোতে — বিশেষ করে OMT আর Booch-এ — “অংশ-সমগ্র” বোঝানোর নিজস্ব চিহ্ন ছিল, আর modeler-রা এর সাথে অভ্যস্ত ছিলেন। ১৯৯০-এর দশকের মাঝামাঝি তিন পদ্ধতি মিলিয়ে UML বানানোর সময় সবার চেনা জিনিস রাখার চাপ ছিল। ফলে দুটি রূপ এলো: শক্ত composition, যার অর্থ নির্দিষ্ট (একক মালিকানা আর একসাথে মোছা), আর দুর্বল aggregation, যার অর্থ কখনোই পরিষ্কার হলো না।
UML 2.5.1-এর spec নিজেই shared aggregation সম্পর্কে বলে, এর সুনির্দিষ্ট অর্থ প্রয়োগক্ষেত্র আর modeler-ভেদে আলাদা। অর্থাৎ মানদণ্ড এখানে কোনো আচরণের প্রতিশ্রুতি দেয় না। Martin Fowler UML Distilled-এ Jim Rumbaugh-এর “modeling placebo” কথাটি উদ্ধৃত করেন বলে জানা যায় — ওষুধের মতো দেখতে, কিন্তু কাজ করে বিশ্বাসে। Fowler-এর নিজের পরামর্শও মোটামুটি এমন: aggregation এড়িয়ে চলুন, composition-এর অর্থ জানুন।
তাহলে টিকে আছে কেন? তিনটি কারণ। প্রথমত, বই আর পরীক্ষা — বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর প্রশ্নে “aggregation বনাম composition” খুব চেনা, তাই সবাই শেখেন। দ্বিতীয়ত, অনুভূতির স্তরে এটি একটি বার্তা দেয়: “এই জিনিসগুলো মিলে ওই জিনিসটি গড়ে” — আলোচনার sketch-এ এটুকু কাজের হতে পারে। তৃতীয়ত, অভ্যাস: tool-গুলো চিহ্নটি দেয়, মানুষ ব্যবহার করেন।
ব্যবহারিক সিদ্ধান্ত: দলের মধ্যে একটি নিয়ম ঠিক করুন। একটি ভালো নিয়ম — composition শুধু তখনই, যখন সত্যিই জীবনচক্রের মালিকানা আছে (আর কোডে সেটা রক্ষা করা হয়); বাকি সব সাধারণ association, প্রয়োজনে একটি note বা role name দিয়ে অর্থটি লেখা। ফাঁপা হীরা যদি ব্যবহার করেনই, পাশে লিখে দিন আপনি কী বোঝাচ্ছেন। পরীক্ষার খাতায় অবশ্য সংজ্ঞাটি ঠিকঠাক লিখুন: অংশ ভাগ হয়, সমগ্র ছাড়াও বাঁচে।
মূল উপলব্ধি: নোটেশনের মূল্য তার দ্ব্যর্থহীনতায়। যে চিহ্নের অর্থ পাঠকভেদে বদলায়, তা স্পষ্ট করে না লিখলে ছবিটি বোঝাপড়া বাড়ায় না, ভুল বোঝাবুঝি বাড়ায়।
প্র ০২ছাত্র-সদস্যকে Member-এর subclass করা কি ভালো নকশা? Class diagram-এ inheritance আর composition-এর মধ্যে বাছবেন কীভাবে?
Generalization আঁকা খুব সহজ, আর বর্ণনায় “X একটি বিশেষ Y” থাকলেই হাত চলে যায় ত্রিভুজের দিকে। কিন্তু একটি পরীক্ষা করে দেখুন: একজন ছাত্র-সদস্য পাস করে চাকরিতে ঢুকলেন। এখন তিনি সাধারণ সদস্য। Java বা C++-এ একটি object জন্মের পরে তার class বদলাতে পারে না। তাহলে কী করবেন — পুরোনো object মুছে নতুন Member বানাবেন? তার চলতি ধারগুলো, ইতিহাস, বকেয়া জরিমানা সব নতুন object-এ সরাতে হবে। এটি একটি ক্লাসিক নকশার ভুল: ভূমিকা (role) বা অবস্থাকে ধরন (type) বানানো।
বিকল্প: Member একটি class, আর তার একটি association Member 1 ◆—— 0..1 StudentStatus (studentId, মেয়াদ)। ছাত্রত্ব শেষ হলে শুধু সেই অংশটি সরিয়ে দিলেই হলো। বা ফি-র নিয়ম যদি ভূমিকা অনুযায়ী বদলায়, একটি FeePolicy interface, যার বাস্তবায়ন পাল্টানো যায় — এটি Strategy pattern (পর্ব ৩৫)। দুটিই composition দিয়ে আচরণ জোড়া।
১৯৯৪ সালের Gang of Four বই Design Patterns-এর একটি বিখ্যাত নীতি: “Favor object composition over class inheritance.” কারণ inheritance compile-এর সময়ে স্থির, আর subclass superclass-এর ভেতরের খুঁটিনাটির ওপর নির্ভর করে — superclass বদলালে subclass ভাঙতে পারে, যাকে “fragile base class” সমস্যা বলা হয়। Composition চলার সময়ে বদলানো যায় আর প্রতিটি অংশ নিজের interface-এর পেছনে লুকানো থাকে। (মজার তথ্য: ওই বই UML-এর আগের, তাই এর ছবিগুলো OMT-ভিত্তিক নোটেশনে।) পর্ব ৩৭-এ এই বিষয়ে বিস্তারিত।
তাহলে inheritance কখন ঠিক? যখন “is-a” সম্পর্কটি object-এর পুরো জীবনে সত্য, আর subclass সত্যিই superclass-এর সব জায়গায় বসতে পারে (পর্ব ২৮-এর Liskov শর্ত)। একটি AcBus আজীবন AC বাস; একটি WalletPayment আজীবন wallet-এর পেমেন্ট। Diagram-এ একটি ভালো লক্ষণ: inheritance-এর শিকল ছোট (দুই-তিন স্তর), আর প্রতিটি স্তরের একটি স্পষ্ট নাম।
মূল উপলব্ধি: ত্রিভুজ আঁকার আগে জিজ্ঞেস করুন, “এই object কি কখনো এই ধরন ছেড়ে অন্য ধরন হতে পারে?” উত্তর হ্যাঁ হলে সেটি ভূমিকা বা অবস্থা — association বা composition দিয়ে আঁকুন।
প্র ০৩Class diagram আর ER diagram দেখতে প্রায় একই। তাহলে object-কে database-এর table-এ রাখতে গেলে এত ঝামেলা হয় কেন?
Peter Chen ১৯৭৬ সালে Entity-Relationship model প্রকাশ করেন — উদ্দেশ্য ছিল data আর তার সম্পর্ক বর্ণনা করা, যা থেকে relational table বানানো যায়। Class diagram এসেছে object-oriented নকশা থেকে, যেখানে data-র সাথে আচরণও আছে। ছবির মিল তাই উপরিতলের; নিচে দুটি জগতের নিয়ম আলাদা। এই অমিলকে সাধারণত “object-relational impedance mismatch” বলা হয়।
কয়েকটি নির্দিষ্ট পার্থক্য: (১) Inheritance — relational model-এ এর সরাসরি রূপ নেই। Martin Fowler-এর Patterns of Enterprise Application Architecture (২০০২) তিনটি কৌশলের নাম দেয়: সব subclass এক table-এ (Single Table Inheritance), প্রতিটি class-এর আলাদা table (Class Table Inheritance), আর শুধু concrete class-এর table (Concrete Table Inheritance) কৌশল। প্রতিটির দাম আলাদা — ফাঁকা column, join-এর খরচ, বা একই column বারবার। (২) Navigability — object-এ Ticket থেকে Passenger-এ যাওয়া একটি pointer; table-এ foreign key দুই দিকেই join করা যায়, “দিক” বলে কিছু নেই। (৩) Many-to-many — class diagram-এ * —— * একটি রেখা; table-এ একটি আলাদা join table, যা প্রায়ই পরে নিজের attribute পেয়ে association class হয়ে ওঠে। (৪) Identity — object-এর পরিচয় memory-র ঠিকানা; row-এর পরিচয় primary key. (৫) Composition — table-এ এর কাছাকাছি রূপ ON DELETE CASCADE, কিন্তু সেটি database-এর নিয়ম, কোডের নয়।
ORM tool (Hibernate, SQLAlchemy, Django ORM ইত্যাদি) এই অনুবাদের অনেকটা করে দেয়, কিন্তু ভুল বোঝার জায়গাও তৈরি করে — যেমন দুই দিকে navigable association-এর একটি দিককে “মালিক” ঘোষণা না করলে অপ্রত্যাশিত query বা update. Database কোর্সের ER modeling আর ER থেকে table-এ যাওয়ার নিয়ম পাশাপাশি পড়লে দুই জগতের সেতুটি পরিষ্কার হয়।
ব্যবহারিক পরামর্শ: domain-এর বোঝাপড়ার জন্য class diagram (conceptual), আর storage-এর নকশার জন্য আলাদা ER বা schema diagram — একটিকে অন্যটি দিয়ে চালাতে গেলে দুটোই খারাপ হয়।
মূল উপলব্ধি: একই রকম ছবি মানে একই নিয়ম নয়। কোন ছবি কোন জগতের প্রশ্নের উত্তর দেয়, তা জেনে আঁকলে অনুবাদের দামটি আগেই চোখে পড়ে।
প্র ০৪Tool তো কোড থেকে নিজেই class diagram বানিয়ে দেয়। তাহলে মানুষের হাতে diagram আঁকার দরকার কী?
১৯৯০-এর দশক থেকে অনেক বাণিজ্যিক CASE tool “round-trip engineering”-এর প্রতিশ্রুতি দিত: diagram থেকে কোড, কোড থেকে diagram, দুটি সবসময় মিলে থাকবে। কিছু ক্ষেত্রে এটি কাজে লেগেছে, কিন্তু সাধারণ ব্যবসার software-এ বড় আকারে টেকেনি বলে মনে করা হয়। কারণটা অংশ ৭-এর Python কোডেই দেখেছেন: স্বয়ংক্রিয় diagram কোডের রূপ ধরে, নকশার উদ্দেশ্য ধরে না।
কী হারায়? (১) বাছাই — তিনশো class-এর একটি স্বয়ংক্রিয় ছবি কেউ পড়েন না। মানুষ জানেন কোন দশটি class দিয়ে গল্পটি বলা যায়, কোন helper, DTO আর exception বাদ দেওয়া যায়। ছবির মূল্য তার বাদ দেওয়ায়। (২) অর্থ — list[Seat] composition, aggregation না সাধারণ association, কোড বলে না; নিচের সীমা ০ না ১, বলে না; দুটি field আসলে একই association-এর দুই প্রান্ত কি না, বলে না। (৩) দৃষ্টিকোণ — tool সবসময় implementation স্তরে আঁকে; আলোচনার জন্য দরকার প্রায়ই specification বা conceptual. (৪) যা এখনো নেই — নকশার আলোচনা হয় কোড লেখার আগে, তখন generate করার মতো কোডই নেই।
তবে উল্টো দিকের সত্যও আছে: হাতে আঁকা ছবি পুরোনো হয়ে যায়। ছয় মাস পরে কোড বদলেছে, ছবি বদলায়নি — তখন ছবিটি মিথ্যা বলে, যা কোনো ছবি না থাকার চেয়েও খারাপ। মধ্যপথ: (ক) গুরুত্বপূর্ণ কয়েকটি ছবি text হিসেবে (PlantUML/Mermaid) repository-তে রাখুন, আর বড় পরিবর্তনের pull request-এ ছবিটিও হালনাগাদ করুন; (খ) বাকিগুলো আলোচনার sketch — তারিখসহ ছবি তুলে রাখুন, “ঐতিহাসিক” বলে চিহ্ন দিন; (গ) বিশাল পুরোনো কোড বুঝতে স্বয়ংক্রিয় diagram একটি শুরু হিসেবে নিন, তারপর হাতে ছেঁটে অর্থ যোগ করুন। Architecture স্তরে অনেক দল আজকাল Simon Brown-এর C4 model-এর মতো স্তরভিত্তিক ছবি ব্যবহার করে, যেখানে class diagram একদম নিচের, ঐচ্ছিক স্তর।
মূল উপলব্ধি: Diagram একটি যুক্তি, ফটোকপি নয়। কোড থেকে ছবি বানানো যায়, কিন্তু কী দেখাবেন আর কী বাদ দেবেন — সেই বিচারটিই নকশা।
প্র ০৫Diagram-এ Member 1 —— 0..3 Loan লেখা থাকলেই কি নিয়মটি মানা হয়? Multiplicity কোথায় রক্ষা করবেন?
না — diagram একটি প্রতিশ্রুতি, পাহারাদার নয়। নিয়মটি কোথাও না কোথাও কোডে বা database-এ রক্ষা করতে হয়, নইলে অংশ ৩-এর checker-এর মতো লঙ্ঘন জমতে থাকবে। কয়েকটি স্তর আছে, প্রতিটির নিজের শক্তি:
- Type দিয়ে:
1মানে field null হবে না — Kotlin বা TypeScript-এর strict null check, বা Python-এ type checker দিয়েSeatআরSeat | None-এর পার্থক্য ধরা যায়।1..*-এর জন্য কিছু ভাষায় “non-empty list” type বানানো যায়। - Constructor আর method দিয়ে:
Bookingতৈরির সময়েই অন্তত একটি টিকিট চাওয়া;Member.borrow()-এ “তিনটির বেশি নয়” যাচাই। এটিই object-এর invariant — পর্ব ৩১-এ design by contract-এর বিষয়। - Database দিয়ে:
NOT NULL, foreign key,UNIQUE (trip_id, seat_no)— শেষটি qualified association-এর “একই যাত্রায় একই আসন একবার” নিয়ম। একসাথে অনেক অনুরোধ এলে শুধু কোডের যাচাই যথেষ্ট নয়: দুজন একই মুহূর্তে শেষ আসনটি বুক করলে দুটি যাচাই-ই “খালি” দেখতে পারে। তখন database-এর constraint বা transaction-ই শেষ ভরসা। System design কোর্সের টিকিট বুকিং case study ঠিক এই সমস্যা নিয়ে। - লেখা নিয়ম দিয়ে: যে নিয়ম রেখায় আঁকা যায় না — “ফেরতের তারিখ ধারের তারিখের পরে” — তার জন্য OCL, বা অন্তত diagram-এর পাশে একটি note আর একটি test.
একটি সূক্ষ্ম দিক: সব সময় সব নিয়ম সত্য থাকে না। একটি booking তৈরির মাঝপথে (টিকিট যোগ হচ্ছে) কিছুক্ষণ শূন্য টিকিট থাকতে পারে। তাই নিয়মটি সাধারণত “লেনদেন শেষে” সত্য হওয়ার কথা — database-এর transaction বা পর্ব ৪০-এর DDD-র aggregate ধারণা এই সীমা নির্ধারণ করে।
মূল উপলব্ধি: প্রতিটি multiplicity-র পাশে নিজেকে জিজ্ঞেস করুন, “এটি কে রক্ষা করবে — type, কোড, না database?” উত্তর না থাকলে নিয়মটি শুধু ছবিতেই আছে।
অনুশীলন
কোড থেকে diagram, ভুল নোটেশনের সমালোচনা, আউটপুট অনুমান আর multiplicity-র হিসাব — প্রতিটির পূর্ণ উত্তর দেওয়া আছে।
-
কোড থেকে diagram বর্ণনা করুন (কাল্পনিক গ্রন্থাগার): নিচের Python কোডের class diagram বর্ণনা করুন — প্রতিটি class (ধরনসহ), attribute আর operation (visibility-সহ, অংশ ৭-এর নামের রীতিতে), প্রতিটি সম্পর্কের ধরন, তীরের দিক আর multiplicity. কোথায় কোড থেকে নিশ্চিত হওয়া যায় না, সেটাও বলুন।
from abc import ABC, abstractmethod from typing import Protocol class Notifier(Protocol): def send(self, to: str, text: str) -> None: ... class SmsNotifier(Notifier): def send(self, to: str, text: str) -> None: print(to, text) class Author: name: str class Book: def __init__(self, title: str, authors: list[Author]): self.title = title self.authors = authors # same Author on many books self._copies = [Copy(self, n) for n in (1, 2)] # Book creates its copies class Copy: def __init__(self, book: Book, n: int): self.book = book self.code = f"{book.title[:3]}-{n}" class Member(ABC): loans: list["Loan"] @abstractmethod def fee(self) -> int: ... class StudentMember(Member): def fee(self) -> int: return 50 class Loan: item: Copy due: str class OverdueReport: def build(self, loans: list[Loan], notifier: Notifier) -> int: ...- Interface আর realization:
«interface» Notifier—+ send(to : str, text : str).SmsNotifierড্যাশ রেখা আর ফাঁপা ত্রিভুজে Notifier-এর দিকে। - Book আর Author:
Book-এ+ title : str; Book থেকে Author-এর দিকে খোলা তীরের association, role+authors, Author প্রান্তে*(নিচের সীমা ০ না ১, কোড বলে না — বইয়ের অন্তত একজন লেখক থাকা যুক্তিসংগত, তাই প্রশ্ন করার জায়গা)। Book প্রান্তে*— মন্তব্য বলে একই লেখক অনেক বইয়ে। এটি composition নয়; চাইলে aggregation, কিন্তু সাধারণ association-ই পরিষ্কার। - Book আর Copy: Book নিজেই কপি বানায় (
_copies, এক underscore তাই#copies) — জীবনচক্রের মালিকানার জোরালো ইঙ্গিত, তাইBook ◆—— Copy, Book প্রান্তে1, Copy প্রান্তে1..*(এই কোডে সবসময় ২টি, কিন্তু নিয়মটি “অন্তত একটি” ধরে নেওয়া — নিশ্চিত হতে requirement দেখুন)। Copy-ওbookfield-এ Book-কে চেনে, তাই association-টি দুই দিকেই navigable. Copy-তে+ code : str. - Member আর StudentMember:
Memberabstract class (italic বা{abstract}), italic operation+ fee() : int;StudentMemberনিরেট রেখা আর ত্রিভুজে Member-এর দিকে, নিজের+ fee() : int(italic নয়)। Member থেকে Loan-এর দিকে association, role+loans, Loan প্রান্তে*— বর্ণনার “সর্বোচ্চ ৩” এখানে কোডে নেই! - Loan আর Copy: Loan থেকে Copy-র দিকে association, role
+item, Copy প্রান্তে1;+ due : strএকটি attribute. Loan-এর দিক থেকে Member-কে চেনার field নেই। - OverdueReport: কোনো field নেই, শুধু parameter — তাই Loan আর Notifier দুটির দিকেই ড্যাশ রেখার dependency,
«use»; operation+ build(loans : Loan [*], notifier : Notifier) : int. লক্ষ করুন, এটিSmsNotifier-এর ওপর নির্ভর করে না, interface-এর ওপর — পর্ব ২৮-এর dependency inversion.
- Interface আর realization:
-
নোটেশনের ভুল খুঁজুন: একজন সহকর্মী (বা একটি AI) বাস-টিকিট অ্যাপের জন্য এই diagram দিয়েছেন: (১) StudentTicket আর Ticket-এর মাঝের রেখায় ফাঁপা ত্রিভুজটি StudentTicket-এর প্রান্তে; (২) Bus আর Seat-এর composition-এ ভরাট হীরা Seat-এর প্রান্তে; (৩)
WalletPaymentথেকে«interface» PaymentMethod-এর দিকে নিরেট রেখা আর ফাঁপা ত্রিভুজ; (৪) FareCalculator FareTable থেকে ভাড়া পড়ে, অথচ ড্যাশ খোলা তীরটি FareTable থেকে FareCalculator-এর দিকে; (৫) Bus ◆—— Seat-এ Bus প্রান্তে0..*; (৬) conceptual domain model-এTicket-এর ভেতরে- tripId : Integerআর+ calculateFare() : Money, পাশাপাশি Ticket থেকে Trip-এ একটি রেখা; (৭)issuedCountstatic, তাই italic-এ লেখা; (৮)+ password : Stringএকটি public attribute. প্রতিটির সমস্যা আর ঠিক রূপ বলুন।- (১) ত্রিভুজ সাধারণ class-এর দিকে: StudentTicket ─▷ Ticket. উল্টো আঁকলে অর্থ “Ticket একটি বিশেষ StudentTicket”.
- (২) হীরা সমগ্রের প্রান্তে: Bus ◆—— Seat. Seat প্রান্তে হীরা মানে আসনই বাসের মালিক।
- (৩) Interface-এর বাস্তবায়ন realization — ড্যাশ রেখা। নিরেট রেখা মানে generalization, অর্থাৎ PaymentMethod একটি class যার subclass WalletPayment.
- (৪) Dependency-র তীর ব্যবহারকারী থেকে যাকে ব্যবহার করা হয় তার দিকে: FareCalculator ┄┄> FareTable.
- (৫) Composition-এ একটি অংশ এক সময়ে সর্বোচ্চ একটি সমগ্রের, তাই Bus প্রান্তে
1(বা0..1, যদি খোলা আসন রাখা যায়)।0..*হলে সেটি composition হতে পারে না। - (৬) দুটি সমস্যা: conceptual স্তরে operation থাকে না; আর
tripIdএকই association-কে foreign key দিয়ে দ্বিতীয়বার দেখায়।tripIdবাদ, রেখা রাখুন; ভাড়ার হিসাব design স্তরে কার দায়িত্ব, পরে ঠিক হবে। - (৭) Static মানে নিচে দাগ; italic মানে abstract.
- (৮) নোটেশনে ভুল নেই, কিন্তু নকশায় আছে: password public attribute হলে যে কেউ পড়তে বা বদলাতে পারে।
- passwordHash : Stringআর+ checkPassword(p : String) : Boolean— এবং plain password কখনো রাখা নয়। Diagram এভাবে নিরাপত্তার ভুলও চোখে আনে।
-
আউটপুট অনুমান করুন: অংশ ৪-এর
lifecycle.js-এর class আরalive,count,showএকই রেখে শেষের অংশটি নিচের কোড দিয়ে বদলানো হলো। প্রোগ্রামটি কী ছাপবে? শেষ লাইনের ফল কেন অবাক করার মতো, আর এটিdestroy()-এর কোন ত্রুটি দেখায়?const b1 = new Bus("DHA-11", 3), b2 = new Bus("DHA-12", 2); const gabtoli = new Stop("Gabtoli"), savar = new Stop("Savar"), aricha = new Stop("Aricha"); const r1 = new Route("Dhaka-Aricha", [gabtoli, savar, aricha]); const r2 = new Route("Dhaka-Savar", [gabtoli, savar]); const spare = new Seat("X1"); b2.attach(spare); r2.destroy(); b2.destroy(); show("end"); console.log("spare alive?", alive.has(spare), "; r1 stops:", r1.stops.length); try { b1.attach(spare); console.log("b1 seats:", b1.seats.length); } catch (e) { console.log("refused:", e.message); }end buses 1 seats 3 routes 1 stops 3 spare alive? false ; r1 stops: 3 refused: X1 already belongs to DHA-12শুরুতে আসন ৩ + ২, তারপর
spareমিলে ৬;b2-তে এখন ৩টি আসন।r2.destroy()রুট ২ থেকে ১ করে, কিন্তু stop ৩-ই থাকে, কারণ এটি aggregation.b2.destroy()তার তিনটি আসন (spare-সহ) মুছে দেয় — বাস ১, আসন ৬ − ৩ = ৩। তাইspare alive? false, আরr1-এর stop এখনো ৩টি।শেষ লাইনটিই আসল শিক্ষা: মুছে যাওয়া আসনটিকে
b1-এ লাগানোর চেষ্টা প্রত্যাখ্যাত হলো “X1 already belongs to DHA-12” বলে — অথচ DHA-12 আর নেই! কারণdestroy()আসনগুলোকেaliveথেকে সরায়, কিন্তু প্রতিটি আসনেরseat.busইঙ্গিতটি মোছে না; বাইরেরsparevariable তখনো সেই “মৃত” object ধরে রেখেছে। ঠিক করার উপায়:destroy()-এs.bus = nullবসানো, আরattach()-এ মৃত আসন প্রত্যাখ্যান করা (alive.has(seat)যাচাই)। Diagram-এর composition একটি প্রতিশ্রুতি; কোডে সেটি রাখার দায়িত্ব পুরোপুরি আপনার। -
Multiplicity-র হিসাব: একটি কাল্পনিক গ্রন্থাগারের class diagram: Member
1——0..3Loan (চলতি ধার); Loan0..1——1Copy (প্রতিটি ধার ঠিক একটি কপির; একটি কপি এক সময়ে সর্বোচ্চ একটি ধারে); Book1◆——1..*Copy. এখন সদস্য ১২০ জন, বই ২০০টি, কপি ৪৫০টি। (ক) এক সময়ে সর্বোচ্চ কতগুলো চলতি ধার সম্ভব? (খ) সব ৪৫০টি কপি একসাথে ধারে থাকতে হলে অন্তত কতজন সদস্য লাগবে? (গ) ৪৫০টি কপি কি multiplicity-র সাথে মেলে, আর বই ২০০ থাকলে কপির সর্বনিম্ন সংখ্যা কত? (ঘ) ৪টি কপিসহ একটি বই catalogue থেকে বাদ দেওয়া হলো, যার ২টি কপি এখন ধারে। Model অনুযায়ী কী ঘটবে, আর কোন নিয়মে টান পড়বে?(ক) সদস্যের দিক থেকে সীমা ১২০ × ৩ = ৩৬০; কপির দিক থেকে প্রতিটি কপি সর্বোচ্চ ১টি ধারে, তাই ৪৫০। দুটির ছোটটি: ৩৬০টি চলতি ধার।
(খ) প্রতি সদস্য সর্বোচ্চ ৩টি, তাই ⌈৪৫০ ÷ ৩⌉ = ১৫০ জন. এখন ১২০ জন, অর্থাৎ অন্তত ৯০টি কপি সবসময় তাকে থাকবে।
(গ) প্রতিটি বইয়ের অন্তত ১টি কপি (
1..*), তাই সর্বনিম্ন ২০০টি কপি; ওপরের সীমা নেই। ৪৫০ ≥ ২০০, তাই মেলে — গড়ে বই প্রতি ২.২৫টি কপি। আর প্রতিটি কপির ঠিক ১টি বই, তাই কোনো কপি দুটি বইয়ে গোনা হয়নি।(ঘ) Composition-এর নিয়মে বই মুছলে তার ৪টি কপিই মুছে যাবে (কপি থাকবে ৪৪৬টি, বই ১৯৯টি)। কিন্তু ধারে থাকা ২টি Loan-এর প্রতিটির ঠিক
1টি Copy থাকার কথা — কপি মুছে গেলে সেই দুটি Loan-এর কপি হবে ০, multiplicity ভাঙবে। তাই নকশায় সিদ্ধান্ত লাগবে: (১) চলতি ধার থাকলে বই বাদ দেওয়া আটকানো, (২) আগে ধারগুলো বন্ধ করা, বা (৩) কপিকে “withdrawn” অবস্থায় রাখা, মুছে না ফেলা। Model নিজেই এই প্রশ্নটি তুলে দিল — কোড লেখার আগেই।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি লেখা বর্ণনা থেকে class diagram আঁকার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- বর্ণনার বিশেষ্য থেকে class-এর প্রার্থী তোলা আর অ-ধারণা বাদ দেওয়া
- প্রতিটি class-এ বর্ণনায় থাকা attribute বসানো
- Association আঁকা আর প্রতিটি প্রান্তে multiplicity লেখা
- অংশ-সমগ্র (composition) আর “is-a” (generalization) সম্পর্ক চিহ্নিত করা
- Design স্তরে navigability, role name আর operation যোগ করা
- একটি object diagram এঁকে নিয়মগুলো বর্ণনার সাথে মিলিয়ে দেখা
একটি class diagram-এ Bus আর Seat-এর মাঝের রেখায় Bus প্রান্তে একটি ভরাট হীরা আর 1, Seat প্রান্তে 1..*. এর সঠিক অর্থ কোনটি?
- একটি আসন একাধিক বাসে ভাগ করে রাখা যায়, আর বাস মুছলেও আসন থাকে
- প্রতিটি আসন ঠিক একটি বাসের অংশ; বাস মুছলে তার আসনগুলোও মুছে যায়
- আসনই বাসের মালিক, আসন মুছলে বাস মুছে যায়
- Seat হলো Bus-এর একটি subclass
একটি class diagram-এ WalletPayment থেকে «interface» PaymentMethod-এর দিকে একটি ড্যাশ রেখা, যার PaymentMethod প্রান্তে ফাঁপা ত্রিভুজ। Java-তে এর সবচেয়ে কাছের রূপ কোনটি?
class WalletPayment { private PaymentMethod method; }class WalletPayment extends PaymentMethodvoid pay(PaymentMethod m)নামে WalletPayment-এর একটি method- class WalletPayment implements PaymentMethod
UML class diagram-এ একটি attribute লেখা আছে: # fare : Money [0..1]. এর সঠিক অর্থ কোনটি?
- fare একটি public attribute, যার মান সবসময় থাকে
- fare private, আর এতে শূন্য থেকে একাধিক মান রাখা যায়
- fare protected; মান না-ও থাকতে পারে, থাকলে একটি Money
- fare একটি static attribute, পুরো class-এর একটি
Java কোড: class Trip { private Bus bus; } আর class FareCalc { int fare(Trip t) { return 0; } } — FareCalc-এ কোনো field নেই। Class diagram-এ কোনটি সঠিক?
- Trip থেকে Bus-এর দিকে association, আর FareCalc থেকে Trip-এর দিকে ড্যাশ রেখার dependency
- Trip থেকে Bus আর FareCalc থেকে Trip — দুটিই নিরেট রেখার association
- Trip ◆—— Bus composition, আর FareCalc ─▷ Trip generalization
- Bus থেকে Trip-এর দিকে association, আর Trip থেকে FareCalc-এর দিকে dependency
একটি গ্রন্থাগারের class diagram-এ Member আর Loan-এর association: Member প্রান্তে 1, Loan প্রান্তে 0..3. নিচের কোন object diagram এই নিয়ম ভাঙে?
- একজন সদস্য, কোনো ধার নেই
- একটি ধার, যা দুজন সদস্যের সাথে link-এ যুক্ত
- একজন সদস্য, তিনটি ধার
- দুজন সদস্য, প্রত্যেকের একটি করে আলাদা ধার
UML 2.5.1-এ shared aggregation (ফাঁপা হীরা) সম্পর্কে কোন কথাটি সঠিক?
- সমগ্র মুছলে অংশও অবশ্যই মুছে যায়
- একটি অংশ এক সময়ে সর্বোচ্চ একটি সমগ্রের হতে পারে
- এর সুনির্দিষ্ট অর্থ প্রয়োগক্ষেত্র আর modeler-ভেদে আলাদা — মানদণ্ড কোনো নির্দিষ্ট আচরণ বাধ্য করে না
- ফাঁপা হীরা সবসময় অংশের প্রান্তে আঁকা হয়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Class diagram কাঠামো দেখায়: class-এর তিন ঘর — নাম,
visibility name : Type [mult] = default,visibility op(p : Type) : Return;+ - # ~, static = নিচে দাগ, abstract = italic বা{abstract}. - Association-এর প্রান্তে multiplicity, role আর navigability; সংখ্যা পড়ুন প্রান্ত ধরে — এগুলো ব্যবসার নিয়ম।
- হীরা সমগ্রের প্রান্তে: ভরাট = composition (একক মালিক, একসাথে মোছা), ফাঁপা = aggregation (দুর্বল, অর্থ নিয়ে বিতর্ক)।
- ত্রিভুজ সাধারণের দিকে: নিরেট = generalization, ড্যাশ = realization; ড্যাশ খোলা তীর = dependency (field নয়, শুধু ব্যবহার)।
- কোড রূপ দেয়, উদ্দেশ্য দেয় না; দৃষ্টিকোণ বেছে আঁকুন, object diagram দিয়ে পরীক্ষা করুন, আর AI-এর প্রতিটি তীর নিজে যাচাই করুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩০ · UML behaviour diagram — sequence, activity আর state machine diagramপরবর্তী পর্বএকই class-গুলো সময়ের সাথে কীভাবে একে অপরকে ডাকে।
- পর্ব ২৩ · Requirement modeling — domain model, use case diagram, activity আর state diagramআগের ধাপConceptual স্তরের domain model আর UML-এর ইতিহাস।
- সফটওয়্যার ইঞ্জিনিয়ারিং ও Git · কোর্স হোম৭০টি পর্বপুরো কোর্সের রোডম্যাপ; মডিউল ৬-এর design pattern-গুলো class diagram দিয়েই বোঝানো।
- DBMS ও SQL · ER modelingপাশের ট্র্যাকClass diagram-এর data-জগতের আত্মীয় — entity, relationship আর cardinality.
- Python · Abstract class আর Protocolগভীরে যানInterface আর abstract class কোডে কীভাবে লেখা হয়।