YouTip LogoYouTip

Python Adapter

Adapter pattern is a structural design pattern that allows incompatible interfaces to work together. Just like a real-life power adapter allows electrical plugs from different countries to connect to local sockets, in programming, the adapter pattern acts as a bridge between two incompatible interfaces.\n\n### Core Concepts\n\nThe adapter pattern mainly solves the problem of interface mismatch. When we need to use an existing class but its interface does not meet our requirements, the adapter pattern comes in handy. It creates an intermediate layer (adapter) to convert the interface, enabling originally incompatible classes to work together.\n\n### Pattern Structure\n\nThe adapter pattern contains three main roles:\n\n* **Target**: The interface expected by the client\n* **Adapter**: Implements the target interface and wraps the adaptee\n* **Adaptee**: The existing class that needs to be adapted\n\n* * *\n\n## Why We Need Adapter Pattern\n\n### Real-world Problem Scenario\n\nImagine you have an existing logging system that uses specific methods to log messages:\n\n## Example\n\nclass LegacyLogger:\n\ndef write_log(self, message):\n\nprint(f" {message}")\n\nBut now you want to use a new logging interface:\n\n## Example\n\nclass NewLogger:\n\ndef log(self, level, message):\n\nprint(f"[{level.upper()}] {message}")\n\nWithout an adapter, you would need to modify all code that calls the old logging system, which is time-consuming and error-prone.\n\n### Advantages of Adapter\n\n1. **Code Reuse**: Can reuse existing classes without modifying source code\n2. **Decoupling**: Separates interface conversion logic from business logic\n3. **Flexibility**: Can easily switch between different implementations\n4. **Open/Closed Principle**: Open for extension, closed for modification\n\n* * *\n\n## Implementation Methods of Adapter Pattern\n\n### Class Adapter (Using Inheritance)\n\nClass adapters implement interface adaptation through multiple inheritance:\n\n## Example\n\nclass LegacyLogger:\n\n"""The legacy logging class to be adapted"""\n\ndef write_log(self, message):\n\nprint(f" {message}")\n\nclass NewLoggerInterface:\n\n"""Target interface"""\n\ndef log(self, level, message):\n\npass\n\nclass LoggerAdapter(NewLoggerInterface, LegacyLogger):\n\n"""Class adapter - implemented through inheritance"""\n\ndef log(self, level, message):\n\n# Convert new interface to old interface\n\n formatted_message = f"{level}: {message}"\n\nself.write_log(formatted_message)\n\n# Usage example\n\n logger = LoggerAdapter()\n\n logger.log("INFO","System started successfully")\n\n logger.log("ERROR","Database connection failed")\n\n**Output:**\n\n INFO: System started successfully ERROR: Database connection failed\n### Object Adapter (Using Composition)\n\nObject adapters are implemented through composition, which is the more commonly used approach:\n\n## Example\n\nclass LegacyPayment:\n\n"""The legacy payment system to be adapted"""\n\ndef process_payment(self, amount_in_dollars):\n\nprint(f"Processing payment: ${amount_in_dollars}")\n\nreturn True\n\nclass NewPaymentInterface:\n\n"""New payment interface"""\n\ndef pay(self, amount, currency="CNY"):\n\npass\n\nclass PaymentAdapter(NewPaymentInterface):\n\n"""Object adapter - implemented through composition"""\n\ndef __init__ (self, legacy_payment):\n\nself.legacy_payment= legacy_payment\n\ndef pay(self, amount, currency="CNY"):\n\n# Currency conversion logic\n\nif currency =="CNY":\n\n amount_in_dollars = amount / 7.0# Simplified exchange rate conversion\n\nelse:\n\n amount_in_dollars = amount\n\n# Call the old system's interface\n\nreturn self.legacy_payment.process_payment(amount_in_dollars)\n\n# Usage example\n\n legacy_payment = LegacyPayment()\n\n payment = PaymentAdapter(legacy_payment)\n\n# Call old system using new interface\n\n payment.pay(700,"CNY")# Pay 700 RMB\n\n payment.pay(100,"USD")# Pay 100 USD\n\n**Output:**\n\nProcessing payment: $100.0Processing payment: $100\n\n* * *\n\n## Practical Application Cases\n\n### Case 1: Data Format Adapter\n\n## Example\n\nimport json\n\nclass XMLData:\n\n"""XML data source"""\n\ndef get_xml_data(self):\n\nreturn"""\n\n\n\n\n\nZhang San\n\n25\n\n\n\n\n\nLi Si\n\n30\n\n\n\n\n\n """\n\nclass JSONProcessor:\n\n"""JSON processor (expects JSON format)"""\n\ndef process_json(self, json_data):\n\n data = json.loads(json_data)\n\nfor user in data['users']:\n\nprint(f"Name: {user['name']}, Age: {user['age']}")\n\nclass XMLToJSONAdapter:\n\n"""XML to JSON adapter"""\n\ndef __init__ (self, xml_data):\n\nself.xml_data= xml_data\n\ndef convert_to_json(self):\n\n# Simplified XML to JSON conversion\n\n xml_content =self.xml_data.get_xml_data()\n\n# In real applications, xml.etree.ElementTree or similar libraries should be used for parsing\n\n json_data ={\n\n"users": [\n\n{"name": "Zhang San","age": 25},\n\n{"name": "Li Si","age": 30}\n\n]\n\n}\n\nreturn json.dumps(json_data, ensure_ascii=False)\n\n# Using the adapter\n\n xml_source = XMLData()\n\n adapter = XMLToJSONAdapter(xml_source)\n\n json_processor = JSONProcessor()\n\njson_data = adapter.convert_to_json()\n\n json_processor.process_json(json_data)\n\n**Output:**\n\nName: Zhang San, Age: 25Name: Li Si, Age: 30\n### Case 2: Third-party Service Adapter\n\n## Example\n\nclass WeChatPay:\n\n"""WeChat Pay SDK"""\n\ndef wechat_pay(self, amount, user_id):\n\nprint(f"WeChat Pay: User {user_id} paid {amount} yuan")\n\nreturn{"status": "success","transaction_id": "wx_123456"}\n\nclass AliPay:\n\n"""Alipay SDK"""\n\ndef alipay(self, money, user_account):\n\nprint(f"Alipay: Account {user_account} paid {money} yuan")\n\nreturn{"code": 200,"trade_no": "ali_789012"}\n\nclass UnifiedPaymentAdapter:\n\n"""Unified payment adapter"""\n\ndef __init__ (self, payment_service):\n\nself.payment_service= payment_service\n\ndef pay(self, amount, user_info):\n\n# Unified payment interface\n\nif isinstance(self.payment_service, WeChatPay):\n\nreturn self.payment_service.wechat_pay(amount, user_info)\n\nelif isinstance(self.payment_service, AliPay):\n\nreturn self.payment_service.alipay(amount, user_info)\n\nelse:\n\nraise ValueError("Unsupported payment method")\n\n# Client code can call uniformly\n\ndef process_order(payment_adapter, amount, user_info):\n\n"""Process order payment"""\n\n result = payment_adapter.pay(amount, user_info)\n\nprint(f"Payment result: {result}")\n\nreturn result\n\n# Usage example\n\n wechat_pay = WeChatPay()\n\n ali_pay = AliPay()\n\nwechat_adapter = UnifiedPaymentAdapter(wechat_pay)\n\n ali_adapter = UnifiedPaymentAdapter(ali_pay)\n\n# Unified calling method\n\n process_order(wechat_adapter,100,"user_001")\n\n process_order(ali_adapter,200,"user_002")\n\n**Output:**\n\nWeChat Pay: User user_001 paid 100 yuanPayment result: {'status': 'success', 'transaction_id': 'wx_123456'}Alipay: Account user_002 paid 200 yuanPayment result: {'code': 200, 'trade_no': 'ali_789012'}\n\n* * *\n\n## Best Practices for Adapter Pattern\n\n### 1. Choose the Right Implementation Method\n\n| Implementation | Applicable Scenarios | Advantages | Disadvantages |\n| --- | --- | --- | --- |\n| Class Adapter | Few adaptee classes, simple target interface | Simple implementation, less code | Requires multiple inheritance, may cause complex class hierarchy |\n| Object Adapter | Most scenarios, especially when adapting multiple classes | More flexible, follows composition over inheritance principle | Need to create adapter objects |\n\n### 2. Keep Adapter's Single Responsibility\n\n## Example\n\n# Good practice: single responsibility\n\nclass DataFormatAdapter:\n\n"""Only responsible for data format conversion"""\n\ndef convert_format(self, data):\n\npass\n\n# Bad practice: too many responsibilities\n\nclass BadAdapter:\n\n"""Handles format conversion, validation, caching, etc."""\n\ndef convert_format(self, data):\n\npass\n\ndef validate_data(self, data):\n\npass\n\ndef cache_data(self, data):\n\npass\n\n### 3. Use Interface Abstraction\n\n## Example\n\nfrom abc import ABC, abstractmethod\n\nclass PaymentInterface(ABC):\n\n"""Payment interface abstraction"""\n\n@abstractmethod\n\ndef pay(self, amount, user_info):\n\npass\n\nclass LoggerInterface(ABC):\n\n"""Logger interface abstraction"""\n\n@abstractmethod\n\ndef log(self, level, message):\n\npass\n\n* * *\n\n## Common Problems and Solutions\n\n### Problem 1: Too Many Adapters Leading to Increased Complexity\n\n**Solution**: Use factory pattern to create adapters\n\n## Example\n\nclass AdapterFactory:\n\n"""Adapter factory"""\n\n@staticmethod\n\ndef create_payment_adapter(service_type):\n\nif service_type =="wechat":\n\nreturn UnifiedPaymentAdapter(WeChatPay())\n\nelif service_type =="alipay":\n\nreturn UnifiedPaymentAdapter(AliPay())\n\nelse:\n\nraise ValueError(f"Unsupported payment type: {service_type}")\n\n# Create adapters using factory\n\n wechat_adapter = AdapterFactory.create_payment_adapter("wechat")\n\n ali_adapter = AdapterFactory.create_payment_adapter("alipay")\n\n### Problem 2: Performance Considerations\n\n**Solution**: Implement lazy loading and caching\n\n## Example\n\nclass CachingAdapter:\n\n"""Adapter with caching"""\n\ndef __init__ (self, adaptee):\n\nself.adaptee= adaptee\n\nself._cache ={}\n\ndef process_data(self, key):\n\nif key not in self._cache:\n\n# Simulate time-consuming operation\n\n result =self.adaptee.expensive_operation(key)\n\nself._cache= result\n\nreturn self._cache\n\n* * *\n\n## Practice Exercises\n\n### Exercise 1: Create File Format Adapter\n\nPlease implement a file format adapter that converts CSV format data to JSON format:\n\n## Example\n\nclass CSVReader:\n\n"""CSV file reader"""\n\ndef read_csv(self, filepath):\n\n# Simulate reading CSV file\n\nreturn[\n\n["name","age","city"],\n\n["Alice","25","Beijing"],\n\n["Bob","30","Shanghai"]\n\n]\n\nclass JSONExporter:\n\n"""JSON exporter"""\n\ndef export_json(self, data):\n\nimport json\n\nprint("Exporting JSON data:")\n\nprint(json.dumps(data, indent=2, ensure_ascii=False))\n\n# Please implement the CSVToJSONAdapter class here\n\n# Your code...\n\n# Test code\n\n csv_reader = CSVReader()\n\n json_exporter = JSONExporter()\n\n# Create adapter and test\n\n adapter = CSVToJSONAdapter(csv_reader)\n\n json_data = adapter.convert_to_json()\n\n json_exporter.export_json(json_data)\n\n### Exercise 2: Design Thinking\n\nConsider the following scenarios, how would you use the adapter pattern:\n\n1. Your application needs to support multiple databases (MySQL, PostgreSQL, SQLite), but each database has different connection methods\n2. You need to integrate multiple third-party APIs, but their authentication methods and return formats vary\n3. Your system needs to support multiple message queues (RabbitMQ, Kafka, Redis)\n\nPlease design an adapter pattern solution for one of these scenarios.\n\n* * *\n\n## Summary\n\nThe adapter pattern is an effective tool for solving interface incompatibility problems. Through this article, you should master:\n\n* Basic concepts and applicable scenarios of adapter pattern\n* Implementation methods of class adapters and object adapters\n* Application techniques in real projects\n* Best practices and considerations for adapter pattern\n\nRemember, the core idea of adapter pattern is "conversion" rather than "modification". It helps us integrate new functionality without changing existing code, and is an important design pattern when maintaining large systems.\n\nIn actual development, reasonable use of the adapter pattern can make your code more flexible, maintainable, and easy to extend.
← Python ProxyPython Builder β†’