ARTICLE

Achieving Loose Coupling

This article covers

Defining coupling

Because this idea of coupling is such a big part of effective software development, it’s important to get a solid handle on what it means. What is coupling exactly? It’s useful to think of it as the connective tissue between the different areas of your code.

The connective tissue

Coupling can be a tricky concept at first because it’s not necessarily tangible. It’s a kind of mesh that runs throughout your code (figure 1). Where two pieces of code have high interdependency, that mesh is tightly woven and taut. Moving either piece of code around causes the other to move around too. The mesh between areas with little or no interdependence is flexible; maybe it’s made of rubber bands. You’d have to change the code in this looser part of the mesh much more drastically for it to impact the code around it.

Figure 1. Coupling is a measure of the interconnectedness of distinct pieces of software

Tight coupling

Coupling is considered tight between two pieces of code — these could be modules or classes — when those pieces of code are interconnected. What does interconnectedness look like? In your code, several things create interconnection:

  • A class whose methods call functions from another module
  • A function or method that does a lot of procedural work using methods from another object
class Book:     
def __init__(self, title, subtitle, author): ❶
self.title = title
self.subtitle = subtitle
self.author = author
def display_book_info(book):
print(f'{book.title}: {book.subtitle} by {book.author}') ❷
class Book:     
def __init__(self, title, subtitle, author):
self.title = title
self.subtitle = subtitle
self.author = author
def display_info(self): ❶
print(f'{self.title}: {self.subtitle} by {self.author}') ❷
import re

def remove_spaces(query): ❶
query = query.strip()
query = re.sub(r'\s+', ' ', query)
return query

def normalize(query): ❷
query = query.casefold()
return query

if __name__ == '__main__':
search_query = input('Enter your search query: ') ❸
search_query = remove_spaces(search_query) ❹
search_query = normalize(search_query)
print(f'Running a search for "{search_query}"') ❺
  1. Yes, because it calls some of the functions inside the search module
  2. Yes, because it’s likely to change if you change the way cleaning queries work
def remove_quotes(query):      
query = re.sub(r'"', '', query)
return query
if __name__ == '__main__':
...
search_query = remove_quotes(search_query)
...

Loose coupling

Loose coupling is the ability for two pieces of code to interact to accomplish a task, without either relying heavily on the details of the other. This is often achieved through the use of shared abstractions.

Figure 2. Imagining interconnections between classes as the message they send and receive
if __name__ == '__main__':     
search_query = input('Enter your search query: ')
search_query = remove_spaces(search_query) ❶
search_query = remove_quotes(search_query) ❷
search_query = normalize(search_query) ❸
print(f'Running a search for "{search_query}"') ❹
  1. Wrap the existing function calls in another function you can call anywhere
  2. Use a class to encapsulate the query cleaning logic
import redef _remove_spaces(query):  ❶     
query = query.strip()
query = re.sub(r'\s+', ' ', query)
return query

def _normalize(query):
query = query.casefold()
return query

def _remove_quotes(query):
query = re.sub(r'"', '', query)
return query

def clean_query(query): ❷
query = _remove_spaces(query)
query = _remove_quotes(query)
query = _normalize(query)
return query

if __name__ == '__main__':
search_query = input('Enter your search query: ')
search_query = clean_query(search_query) ❸
print(f'Running a search for "{search_query}"')
  1. Call the new function inside clean_query
  2. Call it a day, confident that consumers are all cleaning queries properly
Figure 3. Using encapsulation and separation of concerns to maintain loose coupling

Recognizing coupling

You’ve seen an example of tight and loose coupling now, but coupling can take on a few specific forms in practice. Giving a name to these forms and understanding the signs helps you mitigate tight coupling early on, keeping you more productive in the long term.

Feature envy

In the early version of your query-cleaning code, the consumer needed to call several functions from the search module. When code performs several tasks using mainly features from another area, it is said to have feature envy. Your main procedure feels like it wants to be the search module, because it uses all of its features explicitly. This is also common in classes, as shown in figure 4.

Figure 4. Feature envy from one class to another

Shotgun surgery

Shotgun surgery is often what happens as a result of tight coupling. You make one change to a class or module, and those changes have to ripple far and wide for other code to keep working. Peppering changes throughout your code each time you need to update behavior is tiresome!

Leaky abstractions

The goal of abstraction, as you’ve learned, is to hide the details of a particular task from the consumer. The consumer triggers the behavior and receives the result, but doesn’t care about what happens under the hood. If you start to notice feature envy, it might be because of leaky abstraction.

Figure 5. Abstractions occasionally leak the details they’re trying to hide

Follow Manning Publications on Medium for free content and exclusive discounts.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store