Separate Roles and Contracts
If order processing directly chooses how email is sent, changing notifications also changes order code. Start by separating order handling from message delivery.
Receive the required capability
class EmailNotifier:
def send(self, message):
print("email:", message)
class OrderService:
def __init__(self, notifier):
self.notifier = notifier
def accept(self, order_id):
self.notifier.send(f"Order {order_id} accepted")
OrderService(EmailNotifier()).accept(7)
The output is email: Order 7 accepted. OrderService relies on a notifier offering send(message). Supplying another notifier changes delivery without rewriting the order flow. Providing required objects from outside is dependency injection.
Inheritance and composition
- Inheritance: Extend behavior from a parent class. Child objects should still honor the parent’s behavioral promises.
- Composition: Hold another object and delegate work to it, as OrderService does here.
- Contract: Matching a method name is insufficient; input, result, and error expectations must also agree.
Composition is useful for replacing changing roles. Inheritance can suit extensions to a stable shared procedure. Neither is universally superior.
Check your understanding
What must change to replace email notification with screen output?
Show explanation
Provide another object honoring the same send(message) contract. Separating responsibilities lets order processing ignore delivery details.