Skip to content
FollowtheDamnCode

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

SmellWhat it looks likeUsual fix
Feature Envycart.total() reads ten fields of product to compute a priceMove Method to the class whose data it uses
Inappropriate IntimacyTwo classes reach into each other's private fieldsMove Method/Field, Hide Delegate, or merge them if they're one concept
Message Chainsorder.customer().address().region().tax_zone()Hide Delegate: give the first object a method that answers the question
Middle ManA class where most methods just forward to another objectRemove Middle Man: call the delegate directly
Incomplete Library ClassA library lacks one method, so helpers for it are scatteredWrap 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.

A message chain versus asking your neighbour

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.

Fix it: reaching through three objects
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 PICKUP

Run 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.

Fix it: two classes computing another's price
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.40

Run 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

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.