Low coupling principle aims to reduce the dependency within a software system. Minimise the impact of changing to one component which affects the rest of the system.

Consider this library system example, the system has several main functionalities:

  • Manage books
  • Lending books to members
  • Sending notifications to members

Book

// represents a book
public class Book {
	private String title;
	private String author;
	// constructor, other behaviours
}

Member

// represents a library member
public class Member {
	private String name;
	private String email;
	private String phoneNum;
	// constructor, getters, setters
}

Email service

// manage email notifiaction to member
public class EmailNotificationService {
	public void sendNotification(Member member, String message) {
		System.out.println("Sending email to " + member.getEmail());
	}
	// other behaviours
}

SMS service

// manage SMS notifiaction to member
public class SMSNotificationService {
	public void sendNotification(Member member, String message) {
		System.out.println("Sending SMS to " + member.getPhoneNum());
	}
	// other behaviours
}

Lending manager

public class LendingManager {
	private EmailNotificationService emailService = new EmailNotificationService();
		private SMSNotificationService SMSService = new SMSNotificationService();
	
	public void lendBookEmail(Book book, Member member) {
		emailService.sendNotification(member, "You borrowed a book!");
	}
	
	public void lendBookSMS(Book book, Member member) {
		SMSService.sendNotification(member, "You borrowed a book!");
	}
}

Client

public class LibrarySystem {
    public static void main(String[] args) {
        Book book = new Book("Effective Java", "Joshua Bloch", "123456789");
        Member member = new Member("John Doe", "john@example.com, 123456");
        
        LendingManager lendingManager = new LendingManager();
        // send notifiaction with email and SMS
        lendingManager.lendBookEmail(book, member);
        lendingManager.lendBookSMS(book,member);
    }
}

Design issue

Observe the diagram, the LendingManger class is directly dependent on EmailNotificationService and SMSNotificiationService. This creates tightly coupled system. When there are changes to the notification services or new a notification service is added to the system, the LendingManager has to make changes to incorporate new responsibilities.

Low coupling design

To reduce the coupling and avoid direct dependency, we can create an interface for notifications. We retain original design for Book and Member, meanwhile we create an NotificationService interface that defines a sendNotification() method.

Notification service interface

// Interface for notification services
public interface NotificationService {
    void sendNotification(Member member, String message);
}

Concrete notification services

We now arrange EmailNotificationService and SMSNotificationService to implement the interface.

// Email notification service implementation
public class EmailNotificationService implements NotificationService {
	@Override
	public void sendNotification(Member member, String message) {
		System.out.println("Sending email to " + member.getEmail());
	}
	// other behaviours
}
// SMS notification service implementation
public class SMSNotificationService implements NotificationService {
	@Override
	public void sendNotification(Member member, String message) {
		System.out.println("Sending SMS to " + member.getPhoneNum());
	}
	// other behaviours
}

Modified lending manager

We modify the LedingManger to not create dependency on the concrete notification services. It uses the NotificationService interface to dynamically accept notification type from the client.

// Manages lending of books
public class LendingManager {
    private NotificationService notificationService;
 
    public LendingManager(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
 
    public void lendBook(Book book, Member member) {
        // Lending logic...
        notificationService.sendNotification(member, "You've borrowed: " + book.getTitle());
    }
}

Modified client

public class LibrarySystem {
    public static void main(String[] args) {
        Book book = new Book("Effective Java", "Joshua Bloch", "123456789");
        Member member = new Member("John Doe", "john@example.com");
		
		// dynamically inject notification service
        NotificationService emailService = new EmailNotificationService();
        LendingManager lendingManager = new LendingManager(emailService);
        
        lendingManager.lendBook(book, member);
    }
}

After the redesign, the LendingManager no longer dependent on the notification services. We can easily extend new notification services or modify notification services behaviour without making changes to the LendingManager class. The high-level component is not directly depending on concrete notifications, but rather depend on an abstraction, which reduces the system coupling.


Design_PrincipleResponsibility_driven_designRDDLow_couplingGRASP_design_principlesSOFT2201