How to Reduce Coupling, Three Ways

Two objects are coupled if changing one requires us to change the other. (See Yourdon and Constantine.)

We’d like to reduce coupling, since it makes changing code more complicated. A change in a highly-coupled object requires a change in others, and that can create a cascade of changes.

Kent Beck, in Tidy First?, describes “Constantine’s Equivalence” (after Larry Constantine’s work on coupling):

   cost(software) ≈ cost(change) ≈ cost(big changes) ≈ coupling

that is:

   cost(software) ≈ coupling

We’ll look at three classic ways to reduce coupling between objects:

  1. Interfaces
  2. Closures
  3. Notifications

1. Interfaces

One classic way to reduce coupling is to split out an interface for a class. 

Before

We might start with this: 

Client depends on Supplier

Various methods on the client call various methods on the supplier.

What if those methods change? Then the client changes too. What if we want to test Client using a substitute Supplier? We can’t, because Client is coupled to the real Supplier.

After Introducing an Interface

Let’s make a classic encapsulation move: make an interface (aka protocol) that describes the public interface of the class, those methods or fields that are available to clients. 

Client and Supplier depend on ISupply

Now, the Client is coupled to a much smaller thing (the interface), less likely to change. This reduces the ripple effect where one change triggers others.

To make this separation complete, don’t let Client create a Supplier. Create some Supplier outside the client and pass it in (as an instance of the interface). This is sometimes called dependency injection, though I just say “connecting objects”.

Having a separate interface makes our client more valuable. We can create other classes that support the ISupply interface, and pass them to the client.

For example, I’m implementing a file system for an app. The interface is FileStore; we have implementations FakeFileStore (for testing), LocalFileStore (on the user’s disk), and we plan to have RemoteFileStore (using Dropbox or Google Drive). FileStore clients don’t care which one they get.

Naming Conventions

I’ve seen three common naming conventions for the interface:

  1. Name the interface after the simplest implementation. Here that might be Supplier (and we’d name Supplier something else).
  2. Use that name with an “I” in front. “I” is for “interface”, but you can also think of it as being “me”. We might favor the verb form, e.g., ISupply.
  3. Use ForVerbing[Nouns] form. In our case, this might be ForSupplyingFiles. (Alistair suggests this form for port names in hexagonal architecture.)

Summary – Interface

  • Introduce an interface that is less likely to change the original supplier class.
  • Make clients depend only on the interface.
  • You can implement many different suppliers if needed.

2. Closures

A second approach to decoupling is to use closures – functions as arguments. This turns a dependency on one or more objects into a dependency on a simpler function signature.

Before – DetailView is Coupled

Suppose we’re displaying a list of detail objects (ListView). When you select a Detail, you get a DetailView where you can update that object’s fields. 

When a Detail changes, there are several things that should happen:

  • Replace it in the in-memory list
  • Persist the changes
  • Display an error message if there’s a problem

What does the DetailView depend on it to make its changes?

  • The list
  • The persistence system
  • The errors that could happen
  • An error message view 

If anything on the right side changes, we have to update the DetailView to accommodate those changes (and update anything else that depends on them too).

A Circular Approach ☹️ [Don’t do this!]

We’ll assume DetailView has a save() function. We could move it up into the ListView. This would hide the persistence etc. from the DetailView.

But how will we call the function? We’ll need a reference (i.e.. a coupling) from the DetailView to the ListView. Its save() will look something like this:

listView.save(editedDetail)
dismiss()

But now we have a circular dependency – ListView and DetailView know each other. But this has a flaw: A change to either affects the other, so they’re inherently unstable. 

A Closure Approach 😊

A closure can reduce this coupling. 

Go ahead and put save() in the ListView class. Now Listview depends on persistence etc., but the DetailView no longer does.

But don’t pass ListView down to DetailView.

Instead, pass a reference to the save function to DetailView’s constructor to hold, saveFunction: (Detail) → Void.

Holding a function reference is like using a small, anonymous interface.

Now we can call the saved function to do the work:

saveFunction(editedDetail)
dismiss()

Since DetailView already depended on Detail, we’re not adding any coupling to it. And we can remove the persistence and other references from DetailView. 

After – DetailView is Less Coupled

DetailView is clearly better off. Is ListView better off too?

Well, ListView has to pass less information to Detail View. 

If ListView has the same dependencies as before, it’s no worse off. 

If it didn’t, we might be able to make the same move to push the work up to ListView’s parent view. Or we might migrate the saving code to a service object that coordinates persistence and the domain. 

Summary – Closures

  • Keep the work with dependencies in the caller, and only pass a function reference to the called object. 
  • Now the called object only depends on a function signature rather than many other things.

3. Notifications

A third way to reduce coupling is to use notifications – let objects announce when something interesting happens, and let other objects monitor those announcements.

Before – Highly Coupled

Let’s use the same DetailView example. (I won’t repeat the picture, but DetailView starts out coupled to many other things.)

DetailView has enough work on its own – why should it have to manage all these other things?

After – Using Notification

So, split the work: pull out other objects that handle the dependencies. When DetailView confirms changes, it notifies – announces the change. 

Those other objects listen for the notifications. Whey they hear about a change, they take responsibility for getting the detail information and doing what they need to (such as persistence).

This makes DetailView more focused, it lets us put notification-handling code in one place, and it separates DetailView from its former “partners” – they know about Detail, but they don’t have to know about DetailView at all.

Implementation – Notification Handlers

There are many approaches to building a notification handler.

One key distinction is whether it’s done on a per-object basis, or centralized into another object. Smalltalk and the Observer design pattern assume that an object will keep track and notify observers on its own. Other systems use a notification service – one object registers as a publisher (or just starts sending events), and observers register as subscribers. 

Here’s a typical conversation:

Some objects subscribe as observers; they receive notifications when their target changes.

Other design decisions include:

  • Can you have multiple observers for a given object, or only one?
  • Are you broadcasting just the object, or an event describing what happened?
  • Is this managed within one process or application, or spread across multiple?
  • Are notifications persistent? (That is, if you crash between sending and handling, will the notification be there when you restart?)
  • Are notifications synchronous or not?

You’ll hear variations of this concept described with many terms: Model-View-Controller (from Smalltalk), Observer design pattern, Observables (in Swift), Publish-Subscribe (Pub-Sub), Message Queues, Event Queues, and more.

Caution

One place to be cautious – if notifications can trigger other notification, be careful that you’re not introducing a destabilizing loop.

I ran into this while trying to constrain three variables to a particular relationship – one would change, triggering a change in the other, triggering a change in the third, which modified the first again. The view for this looked like some drunken Game of Life simulation until I broke the loop. 

Summary – Notifications

Rather than coupling an object to a number of dependents, have it emit a notification instead. Then the listeners are responsible to respond accordingly, and the original object has fewer dependencies.

Conclusion

We’ve looked at three approaches. All are useful.

The closure approach is probably my favorite. It’s easy to use locally, once you get past the syntax. It doesn’t introduce any new objects.

The interface has the advantage of putting related functions together. This works especially well when one object is coordinating relationships between several methods.

The notification approach has the broadest range; it can be used within or between applications. It takes a bit of work to set up, but it reduces coupling the most – publishers and subscribers don’t need any relationship between them. 

Unwanted coupling drives complexity and cost, so any moves that reduce it are important.

References

Beck, Kent. Tidy First?: A Personal Exercise in Empirical Software Design. O’Reilly Media, 2023.

Cockburn, Alistair, and Juan Manuel Garrido de Paz. Hexagonal Architecture Explained: How the Ports & Adapters architecture simplifies your life, and how to implement it. (Updated 1/e) Humans and Technology, Inc. 2025. 

Gamma, Erich, et al. Design patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. 

Yourdon, Edward, and Larry L. Constantine. Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. Prentice-Hall, 1979