Adapter Pattern: පැරණි කේත නවීන ඇප් වලට ගලපමු SC | Software Guide

Adapter Pattern: පැරණි කේත නවීන ඇප් වලට ගලපමු SC | Software Guide

කොහොමද යාලුවනේ?

අද අපි කතා කරන්නේ Software Design Patterns ලෝකයේ තියෙන හරිම වැදගත් සහ ප්‍රයෝජනවත් Pattern එකක් ගැන. ඒ තමයි Adapter Design Pattern එක. මේක අපිට ගොඩක් වෙලාවට එදිනෙදා වැඩවලදී හම්බවෙන ප්‍රශ්නයකට සුපිරි විසඳුමක් සපයනවා. අපි හිතමුකෝ ඔයාලා අලුත් Software එකක් හදනවා කියලා, ඒකට තියෙනවා පැරණි සේවාවක් (legacy service) සම්බන්ධ කරන්න. හැබැයි අලුත් Software එක බලාපොරොත්තු වෙන විදියට නෙවෙයි අර පරණ සේවාව වැඩ කරන්නේ. එහෙම වෙලාවක තමයි Adapter Pattern එක අපිට අත දෙන්නේ. හරියට ඔයාලා රටකින් ගෙනාපු plug එකක් අපේ ගෙදර socket එකට ගහන්න බැරි වුණාම adapter එකක් පාවිච්චි කරනවා වගේ වැඩක් මේක.

අද මේ ලිපියෙන් අපි බලමු:

  • Adapter Pattern එක කියන්නේ මොකක්ද?
  • මේක අපිට වැදගත් වෙන්නේ ඇයි?
  • මේකේ ප්‍රධාන කොටස් මොනවද?
  • ප්‍රායෝගික උදාහරණයක් එක්ක කොහොමද මේක implement කරන්නේ කියලා.
  • ඒ වගේම මේක පාවිච්චි කිරීමේ වාසි සහ අවාසි.

පටන් ගමු එහෙනම්!

ඇඩැප්ටර් පැටර්න් එක කියන්නේ මොකක්ද? (What is the Adapter Pattern?)

සරලව කිව්වොත්, Adapter Design Pattern එක කියන්නේ එකිනෙකට නොගැලපෙන Interfaces තියෙන Objects දෙකක් අතර සම්බන්ධතාවක් හදන්න උදව් කරන ක්‍රමවේදයක්. මේක Structural Design Pattern ගණයට වැටෙනවා. මොකද මේක Objects වල Structure එක වෙනස් කරලා ඒවා අතර සහයෝගීතාවක් ඇති කරනවා.

හිතලා බලන්නකෝ, ඔයාලා ලංකාවට එනවා විදේශ රටක හදපු Laptop එකක් අරගෙන. ඒකේ power plug එක ලංකාවේ socket වලට ගහන්න බෑ. මොකද ලංකාවේ socket වලට ගැලපෙන විදියට ඒ plug එකේ කටු පිහිටලා නෑ. එතකොට ඔයාලා මොකද කරන්නේ? Adapter එකක් ගන්නවා, නේද? ඒ adapter එකට පුළුවන් Laptop එකේ plug එකත්, ලංකාවේ socket එකත් අතරේ සම්බන්ධයක් හදන්න. එතකොට ඔයාලට පුළුවන් Laptop එක charge කරගන්න. Software ලෝකයේදීත් මේක හරියටම එහෙමයි.

අපි අලුතින් හදන application එකක් තියෙනවා. ඒක බලාපොරොත්තු වෙන්නේ payment process කරන්න payment gateway එකක specific method එකක් (interface එකක්) විදියට. හැබැයි අපිට තියෙනවා අවුරුදු ගානකට කලින් හදපු පරණ payment system එකක්. ඒකේ method name වෙනස්, parameter වෙනස්, return type වෙනස්. ඒ කියන්නේ අලුත් app එකට ඒක කෙලින්ම connect කරන්න බෑ. මෙන්න මේ වෙලාවට තමයි Adapter Pattern එක එන්නේ. අපි Adapter Class එකක් හදනවා. මේ Adapter එක අලුත් app එක බලාපොරොත්තු වෙන interface එක implement කරනවා, ඒ වගේම ඒ ඇතුලෙන් පරණ system එකේ methods call කරලා, data convert කරලා දෙන්න පුළුවන්. එතකොට අලුත් app එකට කිසිම දැනුමක් නෑ, ඒක පරණ system එකක් එක්කද වැඩ කරන්නේ, අලුත් එකක් එක්කද වැඩ කරන්නේ කියලා. ඒක Adapter එකත් එක්ක විතරයි කතා කරන්නේ.

ඇඩැප්ටර් පැටර්න් එකේ කොටස් (Components of the Adapter Pattern)

Adapter Pattern එකට ප්‍රධාන කොටස් හතරක් තියෙනවා. අපි බලමු ඒ මොනවද කියලා:

  1. Client (ක්ලයන්ට්): මේක තමයි අපේ අලුත් Application එක හරි, ඒකේ කොටසක් හරි. මේ Client එක බලාපොරොත්තු වෙන්නේ Target Interface එකක් එක්ක වැඩ කරන්න. ඒකට අර Legacy Service එක ගැන කිසිම දෙයක් දැනගන්න අවශ්‍ය නෑ.
  2. Target Interface (ඉලක්ක අතුරුමුහුණත): මේක තමයි Client එකට අවශ්‍ය Interface එක. ඒ කියන්නේ Client එක බලාපොරොත්තු වෙන Methods set එක. අපේ අලුත් Application එකේ standards වලට අනුව තියෙන Interface එක.
  3. Adaptee (ඇඩැප්ටී): මේක තමයි අපේ පැරණි සේවාව හරි, අපේ Client එකට කෙලින්ම ගලපන්න බැරි Third-Party Library එක හරි. මේකේ Interface එක Client එක බලාපොරොත්තු වෙන Target Interface එකට වඩා වෙනස්.
  4. Adapter (ඇඩැප්ටර්): මේක තමයි අපේ කතාවේ වීරයා. Adapter Class එක Client එක බලාපොරොත්තු වෙන Target Interface එක Implement කරනවා. ඒ වගේම ඒ ඇතුලෙන් Adaptee Object එකක් තියාගෙන, Target Interface එකේ Methods call වුණාම, ඒ Methods call එක Adaptee එකේ Methods වලට හරවනවා. මෙතන තමයි data transformation හරි, method renaming හරි සිද්ධ වෙන්නේ.

හිතාගන්න පුළුවන් නේ, මේ කොටස් ටික එකට වැඩ කරලා කොහොමද නොගැලපෙන interfaces දෙකක් අතර පාලමක් විදියට Adapter එක වැඩ කරන්නේ කියලා.

ප්‍රායෝගික උදාහරණයක්: පැරණි සේවාවක් නව ඇප් එකට ගලපමු (Practical Example: Adapting a Legacy Service to a New App)

අපි හිතමු අපිට අලුත් E-commerce app එකක් තියෙනවා කියලා. ඒකේ checkout process එකක් තියෙනවා, ඒකට නවීන Payment Gateway එකක් එක්ක සම්බන්ධ වෙන්න අවශ්‍යයි. හැබැයි අපේ company එකට තියෙනවා අවුරුදු ගානකට කලින් හදපු පරණ Payment Processor එකක්. ඒකේ API calls වෙනස්, parameters වෙනස්. ඒක අලුත් app එකට කෙලින්ම ගලපන්න බෑ. අපි Python Programming language එක පාවිච්චි කරලා මේක කොහොමද Adapter Pattern එකෙන් විසඳන්නේ කියලා බලමු.

1. Target Interface එක හදමු (Defining the Target Interface)

මුලින්ම, අපේ අලුත් E-commerce app එක බලාපොරොත්තු වෙන Interface එක අපි හදාගමු. මේක තමයි NewPaymentGateway එක. අපේ app එකට අවශ්‍යයි ගෙවීමක් process කරන්න පුළුවන් method එකක්. අපි මේක abstract base class එකක් විදියට හදමු.

from abc import ABC, abstractmethod

class NewPaymentGateway(ABC):
    @abstractmethod
    def process_payment(self, amount: float, card_number: str, expiry_date: str, cvv: str) -> bool:
        pass

මේ NewPaymentGateway එකේ තියෙන්නේ process_payment කියන method එක විතරයි. මේකෙන් amount, card_number, expiry_date, cvv වගේ details අරගෙන, ගෙවීම සාර්ථකද අසාර්ථකද කියලා boolean අගයක් return කරනවා.

2. Adaptee එක (Legacy Service) හඳුනා ගනිමු (Identifying the Adaptee/Legacy Service)

දැන් අපි බලමු අපේ පරණ Payment Processor එක කොහොමද කියලා. මේකේ method names සහ parameters අපේ අලුත් NewPaymentGateway එකට වඩා වෙනස්.

class OldPaymentProcessor:
    def make_transaction(self, total_amount: float, cc_num: str, exp_date_str: str, security_code: str) -> str:
        # Imagine this connects to an actual legacy system
        print(f"පැරණි පද්ධතිය මගින් ගනුදෙනුවක් සිදු කරයි: {total_amount} LKR, CC: {cc_num}, Exp: {exp_date_str}, CVV: {security_code}")
        if len(cc_num) == 16 and len(security_code) == 3:
            print("පැරණි පද්ධතියෙන් සාර්ථකයි (SUCCESS)")
            return "SUCCESS"
        else:
            print("පැරණි පද්ධතියෙන් අසාර්ථකයි (FAILED)")
            return "FAILED"

මේ OldPaymentProcessor එකේ තියෙන්නේ make_transaction කියන method එක. මේකේ parameter names (total_amount, cc_num, exp_date_str, security_code) සහ return type (str) අපේ අලුත් interface එකට වඩා වෙනස්. මෙන්න මේ දෙක තමයි අපිට ගලපගන්න ඕනේ.

3. Adapter එක නිර්මාණය කරමු (Creating the Adapter)

දැන් අපි අපේ Adapter එක හදමු. මේ OldPaymentProcessorAdapter එක NewPaymentGateway Interface එක implement කරනවා, ඒ වගේම ඒ ඇතුලෙන් OldPaymentProcessor Object එකක් පාවිච්චි කරනවා.

class OldPaymentProcessorAdapter(NewPaymentGateway):
    def __init__(self, old_processor: OldPaymentProcessor):
        self._old_processor = old_processor

    def process_payment(self, amount: float, card_number: str, expiry_date: str, cvv: str) -> bool:
        print("\nAdapter මගින් පැරණි සේවාව නව ඇප් එකට ගලපමින් පවතිනවා...")
        # Convert new interface parameters to old system's expected parameters
        # No specific conversion needed for expiry_date or cvv in this simple example,
        # but in a real scenario, you might need to reformat dates or other values.
        result = self._old_processor.make_transaction(amount, card_number, expiry_date, cvv)

        if result == "SUCCESS":
            print("Adapter: ගනුදෙනුව සාර්ථකයි.")
            return True
        else:
            print("Adapter: ගනුදෙනුව අසාර්ථකයි.")
            return False

මේ OldPaymentProcessorAdapter එකේ process_payment method එක ඇතුලේ, අපේ NewPaymentGateway එකෙන් එන parameters අරගෙන, ඒවා _old_processor.make_transaction method එකට ගැලපෙන විදියට යොදනවා. මෙතනදී අපි කරන්නේ, අලුත් method call එක පරණ method call එකට හරවන එක. return value එකත් (SUCCESS/FAILED string එක) True/False boolean එකකට convert කරනවා.

4. Client එක Adapter එකත් එක්ක වැඩ කරන හැටි (Client Interaction with the Adapter)

අපේ E-commerce app එකේ Client කොටස දැන් NewPaymentGateway interface එක බලාපොරොත්තු වෙනවා. ඒකට Adapter එකක් දුන්නත්, අලුත් Payment Gateway එකක් දුන්නත් ඒකට ප්‍රශ්නයක් නෑ. මොකද දෙකම NewPaymentGateway Interface එක implement කරන නිසා.

class ECommerceApp:
    def __init__(self, payment_gateway: NewPaymentGateway):
        self._payment_gateway = payment_gateway

    def checkout(self, amount: float, card_details: dict):
        print(f"\nඊ-කොමර්ස් ඇප් එක මගින් ගෙවීම් කරනවා: {amount} LKR")
        success = self._payment_gateway.process_payment(
            amount,
            card_details['card_number'],
            card_details['expiry_date'],
            card_details['cvv']
        )
        if success:
            print("ගෙවීම සාර්ථකයි! ඔබේ ඇණවුම තහවුරු විය.")
        else:
            print("ගෙවීම අසාර්ථකයි. කරුණාකර නැවත උත්සාහ කරන්න.")

දැන් අපි මේ හැමදේම එකට පාවිච්චි කරලා බලමු.

if __name__ == '__main__':
    # Initialize the legacy processor
    old_processor = OldPaymentProcessor()

    # Create an adapter for the legacy processor
    adapter = OldPaymentProcessorAdapter(old_processor)

    # Use the new E-Commerce app with the adapter
    app = ECommerceApp(adapter)

    # Simulate a successful payment
    print("\n--- සාර්ථක ගෙවීමක් --- ")
    app.checkout(
        1500.00,
        {
            'card_number': '1234567890123456',
            'expiry_date': '12/25',
            'cvv': '123'
        }
    )

    # Simulate a failed payment (e.g., wrong CVV length, as per old system's logic)
    print("\n--- අසාර්ථක ගෙවීමක් (වැරදි CVV) --- ")
    app.checkout(
        500.00,
        {
            'card_number': '9876543210987654',
            'expiry_date': '06/24',
            'cvv': '12' # Invalid CVV as per OldPaymentProcessor's logic
        }
    )

මේ උදාහරණයෙන් පැහැදිලි වෙනවා Adapter Pattern එක කොහොමද OldPaymentProcessor වැනි Legacy System එකක් අපේ අලුත් App එක බලාපොරොත්තු වෙන NewPaymentGateway Interface එකට ගලපන්නේ කියලා. මේ ක්‍රමයෙන් අපිට පුළුවන් පරණ code base එකේ කිසිම වෙනසක් නොකරම, අලුත් system එකට ඒක connect කරගන්න.

ඇඩැප්ටර් පැටර්න් එකේ වාසි සහ අවාසි (Pros and Cons of the Adapter Pattern)

වාසි (Pros):

  1. Legacy Code Integration (පැරණි කේත ඒකාබද්ධ කිරීම): පැරණි Code Base එකක් වෙනස් නොකරම නවීන System එකක් සමඟ ඒකාබද්ධ කිරීමට මෙය කදිම විසඳුමකි. මෙය කාලය හා මුදල් ඉතිරි කරන අතර, පවතින ක්‍රියාකාරීත්වය කඩාකප්පල් වීම වළක්වයි.
  2. Code Reusability (කේත නැවත භාවිතා කිරීම): ඔබට දැනටමත් පවතින Code එකක් තිබේ නම්, එය ඉවත් කර අලුතින් ලිවීම වෙනුවට, Adapter එකක් මගින් එය නැවත භාවිතා කළ හැකිය.
  3. Client Code Decoupling (ක්ලයන්ට් කේතය විසංයෝජනය කිරීම): Client Code එක Adaptee (පැරණි සේවාව) ගැන කිසිවක් නොදනී. එය Target Interface එක සමඟ පමණක් කටයුතු කරයි. මෙය Client Code එක වඩාත් නම්‍යශීලී කරන අතර, Adaptee එක වෙනස් වුවහොත් Client එකට බලපෑමක් සිදු නොවේ.
  4. Flexibility (නම්‍යශීලී බව): විවිධ Adaptee Classes සමඟ වැඩ කිරීමට Client Class එකට හැකියාව ලැබේ. අවශ්‍ය නම්, විවිධ Legacy Systems සඳහා විවිධ Adapters නිර්මාණය කළ හැකිය.
  5. Clean Architecture (පිරිසිදු ගෘහ නිර්මාණ ශිල්පය): Interface එකක් හරහා Dependency එකක් හඳුන්වා දීමෙන්, System Architecture එක වඩාත් පිරිසිදු සහ නඩත්තු කිරීමට පහසු වේ.

අවාසි (Cons):

  1. Added Complexity (සංකීර්ණත්වය වැඩි වීම): සරල ගැටළු සඳහා Adapter Pattern එකක් භාවිතා කිරීමෙන් අනවශ්‍ය ලෙස අමතර Classes සහ Objects එකතු විය හැක, එමගින් System එකේ සංකීර්ණත්වය වැඩි වේ.
  2. Overhead (අතිරේක බර): Adapter එක හරහා Call කිරීමේදී සුළු Performance Overhead එකක් ඇති විය හැක. නමුත් මෙය බොහෝ විට නොසැලකිය හැකි තරම් කුඩා අගයකි.
  3. Debugging Difficulty (දෝෂ සොයා ගැනීම අපහසු වීම): සමහර අවස්ථාවලදී, Adapter Layer එක හරහා දත්ත පරිවර්තනය වන නිසා, දෝෂ සොයා ගැනීම තරමක් අපහසු විය හැක.

අවසන් වචන (Conclusion)

Adapter Design Pattern එක කියන්නේ Software Engineering වලදී අපිට ගොඩක් වෙලාවට එන Compatible නැති Interfaces සම්බන්ධ කරන ගැටලුවට තියෙන පට්ට විසඳුමක්. පැරණි Code Reuse කරද්දි, Third-Party Libraries පාවිච්චි කරද්දි, නැත්නම් System එකක Architecture එක clean කරගන්න ඕනෙ වුණාම මේ Adapter Pattern එක හරිම ප්‍රයෝජනවත් වෙනවා.

මේක තේරුම් ගන්නත්, Implement කරන්නත් ලොකු අමාරුවක් නෑ. හැබැයි මේකෙන් ලැබෙන වාසි නම් ගොඩයි. විශේෂයෙන්ම ඔයාලා Legacy Systems එක්ක වැඩ කරද්දි මේ Pattern එක ගැන දැනගෙන ඉන්න එක ලොකු වාසියක් වේවි.

මේ ලිපියෙන් Adapter Pattern එක ගැන ඔයාලට හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා මම හිතනවා. ඔයාලත් මේ Pattern එක ඔයාලගේ Project වලට Implement කරලා බලන්න. ඔයාලගේ අත්දැකීම් මොනවද? නැත්නම් මේ ගැන තව ප්‍රශ්න තියෙනවා නම්, අපිට පහලින් Comment එකක් දාගෙන යන්න අමතක කරන්න එපා. අපි ඊළඟ ලිපියකින් හමුවෙමු! Happy Coding!