পাঠ ১৯ · ৫৮-এর মধ্যে · মডিউল ৪
Home / Courses / Software Engineering Principles & Git / UML বেসিকস

UML বেসিকস — ক্লাস ও সিকোয়েন্স ডায়াগ্রাম

UML basics — class & sequence diagrams
৮ মিনিট পড়া মাঝারি · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ক্লাস ডায়াগ্রামের তিন-কম্পার্টমেন্ট বক্স নোটেশন ও তিন ধরনের সম্পর্ক-অ্যারো
  • সিকোয়েন্স ডায়াগ্রামের লাইফলাইন ও সময়ানুক্রমিক মেসেজ-অ্যারো নোটেশন
  • কখন ক্লাস ডায়াগ্রাম (স্ট্যাটিক স্ট্রাকচার) বনাম সিকোয়েন্স ডায়াগ্রাম (গতিশীল আচরণ) ব্যবহার করবেন
  • কোড দিয়ে দুই ধরনের ডায়াগ্রামের একটি টেক্সট-রেন্ডারার বানানো

১ · UML কী ও কেন

UMLUnified Modeling Languageসফটওয়্যার ডিজাইন বর্ণনার জন্য একটি স্ট্যান্ডার্ডাইজড ভিজ্যুয়াল নোটেশন — বিভিন্ন ধরনের ডায়াগ্রাম দিয়ে সিস্টেমের স্ট্রাকচার ও আচরণ দেখায়। (Unified Modeling Language) হলো সফটওয়্যার ডিজাইন বর্ণনার একটি স্ট্যান্ডার্ডাইজড ভিজ্যুয়াল নোটেশন। M4-M5 জুড়ে এতক্ষণ যে ক্লাস-সম্পর্ক ও ইন্টারঅ্যাকশনগুলো অনানুষ্ঠানিকভাবে প্রোজে বর্ণনা করা হয়েছে, UML সেগুলো বর্ণনার একটি আনুষ্ঠানিক ভাষা দেয় — এবং এটি L12-এর ইউজ কেস ডায়াগ্রামেরই (অ্যাক্টর, ইউজ কেস) সম্প্রসারণ, এখন দুটি আরও বহুল-ব্যবহৃত ডায়াগ্রাম টাইপে।

২ · ক্লাস ডায়াগ্রাম — স্ট্যাটিক স্ট্রাকচার

ক্লাস ডায়াগ্রামে প্রতিটি ক্লাস একটি বক্স, যা তিনটি কম্পার্টমেন্টে ভাগ করা: উপরে ক্লাসের নাম, মাঝে অ্যাট্রিবিউট/ফিল্ড, নিচে মেথড। ক্লাসগুলোর মধ্যে সম্পর্ক তিন ধরনের রেখায় দেখানো হয় —

ইনহেরিটেন্স ("is-a")
ওপেন-ট্রায়াঙ্গেল অ্যারো — সাবক্লাস থেকে সুপারক্লাসের দিকে। L17-এর LSP আলোচনার সাথে সরাসরি যুক্ত।
কম্পোজিশন/অ্যাগ্রিগেশন ("has-a")
ডায়মন্ড-এন্ডেড লাইন — একটি হোল অবজেক্ট তার অংশগুলো ধারণ করে।
অ্যাসোসিয়েশন
সাধারণ লাইন — দুটি ক্লাসের মধ্যে একটি সাধারণ সংযোগ, মালিকানা ছাড়াই।

নিচের কোড সেলে একটি ছোট, তিন-ক্লাসের মডেল — Order, LineItem, ও PaidOrder — এবং তাদের মধ্যেকার সম্পর্ক টেক্সট-রেন্ডারার দিয়ে জেনারেট করে দেখানো হচ্ছে।

Python
# ক্লাস ডায়াগ্রামের একটি সরল টেক্সট-রেন্ডারার

class ClassDiagramNode:
    def __init__(self, class_name, attributes, methods, relationships=None):
        self.class_name = class_name
        self.attributes = attributes
        self.methods = methods
        # relationships: (relationship_type, target_class_name) টাপলের লিস্ট
        self.relationships = relationships or []


def render_class_box(node):
    lines = []
    width = max(len(node.class_name), *(len(a) for a in node.attributes), *(len(m) for m in node.methods), 20) + 4
    border = "+" + "-" * width + "+"
    lines.append(border)
    lines.append("| " + node.class_name.center(width - 2) + " |")
    lines.append("+" + "-" * width + "+")
    for a in node.attributes:
        lines.append("| " + a.ljust(width - 2) + " |")
    lines.append("+" + "-" * width + "+")
    for m in node.methods:
        lines.append("| " + m.ljust(width - 2) + " |")
    lines.append(border)
    return "\n".join(lines)


def render_relationships(node):
    symbol_map = {
        "inheritance": "------|>",   # ওপেন-ট্রায়াঙ্গেল অ্যারো ("is-a")
        "composition": "----<>",     # ডায়মন্ড-এন্ডেড লাইন ("has-a", অংশ মালিকানাধীন)
        "association": "------",     # সাধারণ সংযোগ
    }
    lines = []
    for rel_type, target in node.relationships:
        symbol = symbol_map.get(rel_type, "------")
        lines.append(f"  {node.class_name} {symbol} {target}   ({rel_type})")
    return "\n".join(lines)


# --- Order "has-a" list of LineItem (composition), PaidOrder "is-a" Order (inheritance) ---

order = ClassDiagramNode(
    "Order",
    attributes=["- order_id: int", "- items: list<LineItem>"],
    methods=["+ total(): float", "+ add_item(item)"],
    relationships=[("composition", "LineItem"), ("association", "Customer")],
)

line_item = ClassDiagramNode(
    "LineItem",
    attributes=["- product_name: str", "- price: float", "- qty: int"],
    methods=["+ subtotal(): float"],
)

paid_order = ClassDiagramNode(
    "PaidOrder",
    attributes=["- payment_ref: str"],
    methods=["+ receipt(): str"],
    relationships=[("inheritance", "Order")],
)

print(render_class_box(order))
print()
print(render_class_box(line_item))
print()
print(render_class_box(paid_order))
print()
print("সম্পর্কসমূহ:")
print(render_relationships(order))
print(render_relationships(paid_order))

    
লক্ষ্য করুন প্রতিটি বক্সে তিনটি কম্পার্টমেন্ট স্পষ্ট — নাম, অ্যাট্রিবিউট (- প্রিফিক্স মানে প্রাইভেট), মেথড (+ প্রিফিক্স মানে পাবলিক)। Order ----<> LineItem কম্পোজিশন দেখাচ্ছে (Order একাধিক LineItem "ধারণ" করে), আর PaidOrder ------|> Order ইনহেরিটেন্স দেখাচ্ছে।

৩ · সিকোয়েন্স ডায়াগ্রাম — গতিশীল আচরণ

ক্লাস ডায়াগ্রাম সিস্টেমের স্ট্যাটিক স্ট্রাকচার দেখায় — সিকোয়েন্স ডায়াগ্রাম সম্পূর্ণ ভিন্ন কিছু দেখায়: একটি নির্দিষ্ট ইন্টারঅ্যাকশনে অবজেক্টগুলো সময়ের সাথে সাথে কীভাবে মেসেজ আদান-প্রদান করে। প্রতিটি অবজেক্ট/অ্যাক্টরকে একটি উল্লম্ব "লাইফলাইন" হিসেবে দেখানো হয়, বাম থেকে ডানে সাজানো — সময় উপর থেকে নিচে নামে — এবং অনুভূমিক অ্যারো দিয়ে মেসেজ কল (মেথড ইনভোকেশন) ঠিক যে ক্রমে ঘটেছে, সেই ক্রমে দেখানো হয়। এটি L12-এর ইউজ কেস মূল ফ্লো-এর ধাপগুলোকে একটি নির্দিষ্ট ইন্টারঅ্যাকশনের জন্য ভিজ্যুয়ালি প্রতিরূপিত করার একটি স্বাভাবিক উপায়।

Python
# সিকোয়েন্স ডায়াগ্রামের একটি সরল টেক্সট-রেন্ডারার -- সময় উপর থেকে নিচে নামে

class SequenceStep:
    def __init__(self, source, target, message):
        self.source = source
        self.target = target
        self.message = message


def render_sequence(steps):
    lines = []
    for i, step in enumerate(steps, start=1):
        arrow = f"{step.source} --> {step.target}"
        lines.append(f"{i}. {arrow} : {step.message}")
        lines.append("   " + " " * len(str(i) + ". ") + "|")
    return "\n".join(lines)


steps = [
    SequenceStep("Customer", "OrderService", "place_order()"),
    SequenceStep("OrderService", "PaymentGateway", "charge()"),
    SequenceStep("PaymentGateway", "OrderService", "confirmed"),
    SequenceStep("OrderService", "Customer", "order_confirmed()"),
]

print("সিকোয়েন্স ডায়াগ্রাম -- সময় উপর থেকে নিচে (প্রতিটি ধাপ ঘটার ক্রম অনুযায়ী):\n")
print(render_sequence(steps))

print("\nলক্ষ্য করুন: প্রথম ধাপে Customer -> OrderService বার্তা পাঠায় (রিকোয়েস্ট),")
print("শেষ ধাপে OrderService -> Customer বার্তা ফেরত পাঠায় (রেসপন্স) -- ঠিক L12-এর")
print("'Reset Password' ইউজ কেসের মূল ফ্লো-র মতোই একটি নির্দিষ্ট ইন্টারঅ্যাকশনের চিত্র।")

    
মূল কথা · Key takeaway

ক্লাস ডায়াগ্রাম ও সিকোয়েন্স ডায়াগ্রাম একে অপরের পরিপূরক — একটি সিস্টেমের "কী দিয়ে তৈরি" (স্ট্যাটিক স্ট্রাকচার) ও "কীভাবে কাজ করে" (গতিশীল আচরণ) দুটোই বোঝা দরকার একটি সম্পূর্ণ ডিজাইন যোগাযোগ করতে। M4-এর SOLID প্রিন্সিপল ও UML নোটেশন একসাথে এখন M5-এর ডিজাইন প্যাটার্নের জন্য প্রস্তুত ভিত্তি তৈরি করেছে — প্রতিটি প্যাটার্ন নিজেই একটি নাম-করা ক্লাস-সম্পর্ক ও ইন্টারঅ্যাকশন কাঠামো, যা এই দুই ডায়াগ্রাম টাইপ দিয়ে সরাসরি বর্ণনা করা যায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একটি টিমকে যদি শুধু একটি ডায়াগ্রাম টাইপ বেছে নিতে হয় একটি নতুন ফিচারের ডিজাইন যোগাযোগ করতে, কখন ক্লাস ডায়াগ্রাম আর কখন সিকোয়েন্স ডায়াগ্রাম বেশি উপযোগী হবে?

যদি প্রশ্ন হয় "সিস্টেমে কী কী ক্লাস আছে ও তারা কীভাবে সংগঠিত" (যেমন একটি নতুন মডিউলের ডেটা মডেল ডিজাইন করা), ক্লাস ডায়াগ্রাম বেশি উপযোগী — এটি স্ট্যাটিক স্ট্রাকচার দেখায়। যদি প্রশ্ন হয় "একটি নির্দিষ্ট অপারেশনে কোন অবজেক্ট কাকে, কোন ক্রমে কল করে" (যেমন একটি পেমেন্ট ফ্লো ডিবাগ করা বা ডিজাইন করা), সিকোয়েন্স ডায়াগ্রাম বেশি উপযোগী — এটি সময়ানুক্রমিক আচরণ দেখায়। বাস্তবে জটিল ফিচারের জন্য প্রায়ই দুটোই একসাথে ব্যবহৃত হয়, একে অপরের পরিপূরক হিসেবে।

প্র ০২ উপরের ক্লাস ডায়াগ্রাম কোড সেলে Order ----<> LineItem কম্পোজিশন কেন, অ্যাসোসিয়েশন নয় — কম্পোজিশন ও অ্যাসোসিয়েশনের পার্থক্য কী?

কম্পোজিশন বোঝায় "হোল" অবজেক্ট তার "পার্ট" অবজেক্টগুলোর মালিক — LineItem-গুলো Order-এর অংশ হিসেবেই অস্তিত্বশীল, Order ছাড়া তাদের স্বাধীন কোনো অর্থ নেই (একটি অর্ডার মুছে গেলে তার লাইন-আইটেমও থাকার কথা না)। অ্যাসোসিয়েশন এর চেয়ে দুর্বল — শুধু দুটি ক্লাসের মধ্যে একটি সংযোগ, মালিকানা ছাড়া (যেমন Order ------ Customer — একজন কাস্টমার Order ছাড়াও স্বাধীনভাবে অস্তিত্বশীল, শুধু তার সাথে সংযুক্ত)। এই পার্থক্যই কোড সেলে দুটি ভিন্ন সিম্বল ব্যবহারের কারণ।

প্র ০৩ সিকোয়েন্স ডায়াগ্রাম কোড সেলে ধাপ ২ ও ৩-এ OrderService ও PaymentGateway-এর মধ্যে অ্যারোর দিক উল্টে গেছে কেন?

ধাপ ২-তে OrderService --> PaymentGateway : charge() — অর্থাৎ OrderService একটি রিকোয়েস্ট পাঠাচ্ছে (পেমেন্ট চার্জ করার জন্য)। ধাপ ৩-এ PaymentGateway --> OrderService : confirmed — অর্থাৎ PaymentGateway সেই রিকোয়েস্টের একটি রেসপন্স ফেরত পাঠাচ্ছে (পেমেন্ট সফল হয়েছে নিশ্চিত করে)। এই রিকোয়েস্ট-রেসপন্স জোড়াই স্বাভাবিক — প্রতিটি "কল" (রিকোয়েস্ট) সাধারণত একটি "রিটার্ন" (রেসপন্স) মেসেজের সাথে যুক্ত থাকে, যা সিকোয়েন্স ডায়াগ্রামে বিপরীত দিকের অ্যারো হিসেবে দেখানো হয়।

অনুশীলন

  1. চিন্তা করুন: L12-এর "Reset Password" ইউজ কেসের মূল ফ্লো মনে করুন। এই ফ্লোকে একটি সিকোয়েন্স ডায়াগ্রামে রূপান্তর করলে কোন কোন অবজেক্ট/অ্যাক্টর লাইফলাইন হিসেবে থাকবে, তা লিখে ফেলুন।

    অন্তত এই কয়টি লাইফলাইন যুক্তিসঙ্গত: User (অ্যাক্টর, যে রিসেট শুরু করে), AuthService (রিকোয়েস্ট গ্রহণ ও যাচাই করে), EmailService (রিসেট লিংক পাঠায়), এবং সম্ভবত Database (নতুন পাসওয়ার্ড সংরক্ষণ করে)। মেসেজ ক্রম হবে মোটামুটি: User → AuthService (request_reset), AuthService → EmailService (send_reset_link), User → AuthService (submit_new_password), AuthService → Database (save_new_password) — L12-এর মূল ফ্লো-এর প্রতিটি ধাপ একটি করে মেসেজ-অ্যারোতে রূপান্তরিত হয়।

  2. পরীক্ষা করুন: উপরের ক্লাস ডায়াগ্রাম কোড সেলে একটি চতুর্থ ClassDiagramNode যোগ করুন — Customer, যার একটি অ্যাট্রিবিউট (- name: str) ও একটি মেথড (+ place_order(): Order) আছে — এবং সেটির জন্যও render_class_box() কল করে প্রিন্ট করুন।

    নতুন Customer নোডের জন্য render_class_box() ঠিক আগের তিনটি বক্সের মতোই একই তিন-কম্পার্টমেন্ট স্ট্রাকচার (নাম/অ্যাট্রিবিউট/মেথড) দিয়ে একটি বক্স প্রিন্ট করবে, স্বয়ংক্রিয়ভাবে বক্সের প্রস্থ নতুন কনটেন্টের সাথে মানানসই করে হিসাব করে — কারণ রেন্ডারার ফাংশনটি নির্দিষ্ট কোনো ক্লাসের জন্য হার্ডকোড করা নয়, যেকোনো ClassDiagramNode-এর জন্যই কাজ করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
SOLID প্রিন্সিপল — DIP