The Adapter pattern lets classes with incompatible interfaces work together. The adapter implements the interface the client expects and translates every call to the adapted class. It is how you plug third-party libraries into your code without letting their shapes spread everywhere.
The problem: incompatible interfaces
Our checkout code wants gateway.pay(amount, card). A payment SDK offers create_charge(cents, currency, source) and returns a dict. An old bank API offers make_payment(euros, account_no) and returns "OK" or "FAIL". We cannot edit either.
class StripeLikeSdk: # third-party code, we cannot edit it
def create_charge(self, cents, currency, source):
return {"id": "ch_1", "status": "succeeded", "amount": cents}
class OldBankApi: # another vendor, another shape
def make_payment(self, euros, account_no):
return "OK" if euros > 0 else "FAIL"
def checkout(gateway, amount, card):
print("paid" if gateway.pay(amount, card) else "declined")
try:
checkout(StripeLikeSdk(), 12.5, "tok_1")
except AttributeError as e:
print(e)'StripeLikeSdk' object has no attribute 'pay'Object adapter
An adapter holds the vendor object (composition) and implements our interface. It converts euros to cents, adds the currency and turns the vendor result into a bool. The client does not know which vendor stands behind pay.