OOP সিরিজ

Interface: Pathao জানে না bKash কীভাবে কাজ করে, তবু পেমেন্ট হয়

Pathao-এ ride নিলে শেষে payment দিতে হয়।

bKash, Nagad, Rocket, Credit Card, যেটা খুশি বেছে নাও। Pathao-এর app-এ সব কটা option আছে।

এখন ভাবো: Pathao-এর developer-রা কি bKash-এর পুরো system জানে? Nagad কীভাবে তাদের server-এ transaction process করে, সেটা কি Pathao-এর code-এ লেখা আছে?

না।

Pathao শুধু একটাই কথা জানে: “আমাকে processPayment(amount) বলো, বাকিটা তুমি সামলাও।” bKash সেটা নিজের মতো করে। Nagad নিজের মতো করে। Pathao-এর কিছু যায় আসে না।

এই চুক্তিটার নাম Interface।


১. Interface কী?

ধরো তুমি একটা নতুন রেস্টুরেন্টে কাজ করতে এসেছ। মালিক বললেন: “তোমাকে তিনটা কাজ করতে হবে: অর্ডার নাও, খাবার দাও, বিল নাও। কীভাবে করবে সেটা তোমার ব্যাপার।”

এই তিনটা কাজের তালিকাটাই হলো Interface। মালিক বলে দিচ্ছেন কী করতে হবে, কিন্তু কীভাবে করতে হবে সেটা বলছেন না।

প্রোগ্রামিং-এ Interface হলো একটা contract বা চুক্তি। এটা define করে কোনো class-কে কী কী method implement করতে হবে, কিন্তু সেই method-এর ভেতরে কী হবে সেটা Interface-এর কাজ না।

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass

    @abstractmethod
    def refund(self, transaction_id: str) -> bool:
        pass

    @abstractmethod
    def get_transaction_status(self, transaction_id: str) -> str:
        pass

PaymentGateway Interface বলছে: “যে class আমাকে implement করবে, তাকে অবশ্যই process_payment, refund, আর get_transaction_status রাখতে হবে।” ভেতরে কী হবে? সেটা implementer-এর স্বাধীনতা।

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F5F5F5', 'primaryTextColor': '#000', 'primaryBorderColor': '#333', 'lineColor': '#333', 'background': '#fff'}}}%% graph TD classDef box fill:#F5F5F5,color:#000,stroke:#333 I["PaymentGateway Interface (Contract) process_payment() refund() get_transaction_status()"]:::box B["BkashPayment (own implementation)"]:::box N["NagadPayment (own implementation)"]:::box R["RocketPayment (own implementation)"]:::box C["CardPayment (own implementation)"]:::box I -->|"implements"| B I -->|"implements"| N I -->|"implements"| R I -->|"implements"| C

২. Interface-এর মূল বৈশিষ্ট্য

শুধু কী, কীভাবে না। Interface-এ method-এর signature থাকে, implementation থাকে না। রেস্টুরেন্টের মেনু যেমন বলে “বিরিয়ানি আছে”, রান্নার recipe দেয় না।

একটা class একাধিক Interface implement করতে পারে। একজন waiter যেমন অর্ডার নেওয়া, বিল দেওয়া, টেবিল সাজানো সব কাজই করতে পারে। একটা class-ও একাধিক চুক্তি মানতে পারে।

Interface নিজে object তৈরি করতে পারে না। মেনু দেখে খাবার আসে না, রাঁধুনি রান্না করে। Interface থেকে directly object বানানো যায় না।

Loose coupling তৈরি হয়। Pathao-এর code bKash-এর উপর depend করে না, করে PaymentGateway Interface-এর উপর। bKash বদলে গেলেও Pathao-এর core code ছুঁতে হয় না।


৩. Code Example: Payment Gateway

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass

    @abstractmethod
    def refund(self, transaction_id: str) -> bool:
        pass


class WalletPayment(PaymentGateway):
    def process_payment(self, amount: float) -> bool:
        print(f"Wallet: processing payment of {amount}...")
        return True

    def refund(self, transaction_id: str) -> bool:
        print(f"Wallet: refunding transaction {transaction_id}...")
        return True


class DirectDebitPayment(PaymentGateway):
    def process_payment(self, amount: float) -> bool:
        print(f"Direct debit: processing payment of {amount}...")
        return True

    def refund(self, transaction_id: str) -> bool:
        print(f"Direct debit: refunding transaction {transaction_id}...")
        return True


class RideCheckout:
    def __init__(self, payment_gateway: PaymentGateway):
        self.gateway = payment_gateway

    def complete_ride(self, fare: float):
        success = self.gateway.process_payment(fare)
        if success:
            print("Ride complete! Payment processed.")
        else:
            print("Payment failed.")

এখন দেখো RideCheckout class কতটা স্বাধীন:

# bKash দিয়ে
checkout = RideCheckout(WalletPayment())
checkout.complete_ride(85.0)
# Wallet: processing payment of 85.0...
# Ride complete! Payment processed.

# Nagad দিয়ে
checkout = RideCheckout(DirectDebitPayment())
checkout.complete_ride(85.0)
# Direct debit: processing payment of 85.0...
# Ride complete! Payment processed.

RideCheckout-এর একটা অক্ষরও বদলাতে হয়নি। কাল যদি Rocket যোগ হয়, RocketPayment class বানাও, RideCheckout ছুঁতে হবে না।


বাস্তব উদাহরণ: Notification Service

Shohoz Food-এ একটা order confirm হলে customer-কে notify করতে হয়। SMS দিয়ে, Email দিয়ে, আর app-এ Push notification দিয়ে।

তিনটা আলাদা service, তিনটা আলাদা company-র API। কিন্তু কাজ একটাই: “এই message-টা user-কে পাঠাও।”

from abc import ABC, abstractmethod
from typing import List

class NotificationService(ABC):
    @abstractmethod
    def send(self, recipient: str, message: str) -> bool:
        pass


class SmsNotification(NotificationService):
    def send(self, recipient: str, message: str) -> bool:
        print(f"SMS to {recipient}: {message}")
        return True


class EmailNotification(NotificationService):
    def send(self, recipient: str, message: str) -> bool:
        print(f"Email to {recipient}: {message}")
        return True


class PushNotification(NotificationService):
    def send(self, recipient: str, message: str) -> bool:
        print(f"Push notification to {recipient}: {message}")
        return True


class OrderProcessor:
    def __init__(self, notifiers: List[NotificationService]):
        self.notifiers = notifiers

    def confirm_order(self, user_id: str, order_id: str):
        message = f"Order #{order_id} confirmed!"
        for notifier in self.notifiers:
            notifier.send(user_id, message)
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F5F5F5', 'primaryTextColor': '#000', 'primaryBorderColor': '#333', 'lineColor': '#333', 'background': '#fff'}}}%% graph LR classDef box fill:#F5F5F5,color:#000,stroke:#333 OP["OrderProcessor"]:::box NS["NotificationService Interface send()"]:::box SMS["SmsNotification"]:::box EM["EmailNotification"]:::box PN["PushNotification"]:::box OP -->|"depends on"| NS NS -->|"implements"| SMS NS -->|"implements"| EM NS -->|"implements"| PN
processor = OrderProcessor([
    SmsNotification(),
    EmailNotification(),
    PushNotification()
])

processor.confirm_order("01711-XXXXX", "SHZ-4521")
# SMS to 01711-XXXXX: Order #SHZ-4521 confirmed!
# Email to 01711-XXXXX: Order #SHZ-4521 confirmed!
# Push notification to 01711-XXXXX: Order #SHZ-4521 confirmed!

কাল যদি WhatsApp notification যোগ করতে হয়, শুধু WhatsAppNotification class বানাও। OrderProcessor-এর কিছু বদলাতে হবে না।


সারসংক্ষেপ

গল্পের ভাষায় প্রযুক্তির ভাষায়
রেস্টুরেন্টের মেনু Interface
মেনুতে লেখা তিনটা কাজ Abstract methods
ওয়েটার যে সেই কাজ করে Implementing class
Pathao-এর payment চুক্তি PaymentGateway interface
bKash, Nagad নিজের মতো করে Concrete implementations
নতুন payment method যোগ করা সহজ Loose coupling

Interface বলে কী করতে হবে, কীভাবে করতে হবে সেটা implementing class-এর কাজ।

Interface-এর কারণে নতুন implementation যোগ করতে পুরনো code ছুঁতে হয় না।

একটা class একাধিক Interface implement করতে পারে, flexibility অনেক বেশি।


পরবর্তী প্রশ্ন: Object-এর ভেতরের data কি সবার জন্য উন্মুক্ত থাকা উচিত? কিছু জিনিস কি লুকিয়ে রাখা উচিত না? সেই ধারণার নাম Encapsulation।

OOP সিরিজের পরবর্তী পর্ব: Encapsulation, যা সবাইকে দেখানো যায় না