Smells: Couplers
Classes that know too much about each other — envying another's data, reaching into its internals, chaining through strangers, or just passing calls along.
Big idea
Couplers are smells of excessive coupling — or of over-correcting it. A method more interested in another class's data than its own; two classes that know each other's private details; long chains of a.b().c().d(); and, on the other side, a class that does nothing but forward. The fix is always to put behaviour next to the data it uses and let objects talk to their direct neighbours.
The couplers
| Smell | What it looks like | Usual fix |
|---|---|---|
| Feature Envy | cart.total() reads ten fields of product to compute a price | Move Method to the class whose data it uses |
| Inappropriate Intimacy | Two classes reach into each other's private fields | Move Method/Field, Hide Delegate, or merge them if they're one concept |
| Message Chains | order.customer().address().region().tax_zone() | Hide Delegate: give the first object a method that answers the question |
| Middle Man | A class where most methods just forward to another object | Remove Middle Man: call the delegate directly |
| Incomplete Library Class | A library lacks one method, so helpers for it are scattered | Wrap or extend it in one place (Adapter) |
Think of it like
Asking for directions. Good: you ask the receptionist, she tells you the room. Message chain: you ask the receptionist for the building manager's number, call him for the facilities office, ask them for the floor plan, then read the room off the plan yourself — and when facilities moves, your route breaks. Middle man: a receptionist who answers every question by reading it aloud to someone else.
Hands on: a message chain that breaks on pickup orders
Checkout reaches through order → customer → address to find the shipping zone. Pickup orders have no address, and the chain crashes. Give the objects a method that answers "what's your shipping zone?" themselves (handling the no-address case where that knowledge belongs), keeping the output shown.
class Address:
def __init__(self, country): self.country = country
class Customer:
def __init__(self, name, address=None): self.name, self.address = name, address
class Order:
def __init__(self, customer): self.customer = customer
class Checkout:
ZONES = {"DE": "EU", "FR": "EU", "US": "NA"}
def shipping_zone(self, order):
return self.ZONES.get(order.customer.address.country, "WORLD") # three hops
checkout = Checkout()
for order in [Order(Customer("Ana", Address("FR"))), Order(Customer("Bo", Address("US"))), Order(Customer("Cy"))]:
print(order.customer.name, checkout.shipping_zone(order))output
Ana EU
Bo NA
Cy PICKUPRun and edit it in the app.
Hands on: feature envy
Cart.line_total does all its work on Product's fields, and a second envious function elsewhere forgot the bulk discount. Move the pricing rule onto Product so every caller gets the same answer.
class Product:
def __init__(self, name, unit_price, bulk_qty, bulk_discount):
self.name, self.unit_price = name, unit_price
self.bulk_qty, self.bulk_discount = bulk_qty, bulk_discount
class Cart:
def line_total(self, product, qty):
price = product.unit_price * qty
if qty >= product.bulk_qty:
price *= 1 - product.bulk_discount
return round(price, 2)
class QuoteEmail:
def line(self, product, qty):
return f"{qty} x {product.name}: {product.unit_price * qty:.2f}" # no bulk rule
pens = Product("pen", 1.50, 10, 0.2)
print(f"cart: {Cart().line_total(pens, 12):.2f}")
print("quote:", QuoteEmail().line(pens, 12))output
cart: 14.40
quote: 12 x pen: 14.40Run and edit it in the app.
A ShippingService class has eight methods, and seven of them just call the same method on its carrier object. Which smell?
Fixing every message chain with Hide Delegate can create which opposite smell?
How it relates
- The Law of Demeter and coupling levels are the theory; these are the symptoms.
- Feature Envy often appears next to a Data Class — the logic left home.
- Facade is a middle man with a purpose: it simplifies several objects, not just one.
- The fixes live in Moving Features.
- Previous family: Dispensables. Next part: Refactoring basics.
Key terms
- Feature envy — A method that uses another object's data more than its own.
- Inappropriate intimacy — Two classes that depend on each other's internal details.
- Message chain — A sequence of calls navigating through several objects to reach the one you need.
- Middle man — A class that mostly forwards calls to another object.
- Hide delegate — Giving an object a method so callers don't have to reach through it.