Message Chains
Description (What)
Message chains are simply multiple chained calls to functions to retrieve data. Message chains create a chain of dependency throughout the program which can be harder to keep track of as the chain increases and any changes made at any part of the chain might require refactoring in other parts of the chain
How to Locate It (Where)
Look for calls resembling a().b().c().d() where data is being retrieved through a chain of function calls where each function in the call calls another function and retrieves data from it.
How It Manifests (Why)
While writing code in silos, it comes naturally that when you need to interact between Classes or Methods, it will require using chains to bring information to the client surface.
How to Fix It (Possible Treatments)
In Example 1, Hide Delegate is used to push the chain to an inner class which leads the message chain being hidden behind a single public method which itself contains a chain of private or internal methods. Hide Delegate simplifies the end call for the client.
In Example 2, Move Methods is used when the message chain comes into form due to the placements of methods.
We could address the code smell by moving the methods back to which they belong to.
Other treatments are also possible based on the specific scenario, they can be found here
Examples
Example 1
Before:
For the vehicle to get the address for the latest order to be delivered, it needs to go through a message chain of going to the Distributor object which returns a Warehouse object which returns an Order object from which the address can be retrieved.
Observed Code Smells:
- Message Chains (line 117)
After:
Applied Hide Delegate by adding a new public method getLatestOrderDestination to Distributor class to make the calls internal
Removed getWarehouse() method from Distributor so it remains private and reduces its public exposure.
Refactoring Applied:
- Message Chains
- Hide Delegate (getWarehouse())
Observed Code Smells After Refactoring:
- None
Example 2
Before:
Here the message chains arise when, an attendance manager wants to know if an employee is on shift at a particular
point in time. The attendance manager has to get the employee instance first, and then get the schedule of that employee
finally arrives at the boolean if the employee is on shift: AttendanceManager -> Employee -> Schedule, where the class
Employee functions like an agent, but it should not be.
Attendance Manager should have this method directly inside its own class.
In other words, Attendance Manager should not go through an employee to know if they are on shift.
Observed Code Smells:
- Message Chains (line 78, line 82)
After:
The two methods of isOnShift() should sit in the class Attendance Manager because to know if an employee is on shift
is the duty of the attendance manager.
Refactoring Applied:
- Message Chains:
- Move Method (lines 30-33, lines 35-38)
Observed Code Smells After Refactoring:
- None
When to Ignore
In cases where solving this code smell might inadvertently cause the Middle Man code smell.
More
- Example 1
- Example 2
- Before
- After
001 import java.util.LinkedList;002 import java.util.Queue;003004 class Order {005 private String item;006 private String destination;007 private boolean isDelivered;008009 public Order(String item, String destination) {010 this.item = item;011 this.destination = destination;012 this.isDelivered = false;013 }014015 public String getOrderDetails() {016 return "Item: " + this.item + " to: " + this.destination + "\nDelivered: " + this.isDelivered;017 }018019 public String getDestination() {020 return destination;021 }022023 public void markOrderDelivered() {024 this.isDelivered = true;025 }026027 public boolean getDeliveredStatus() {028 return this.isDelivered;029 }030 }031032 class Warehouse {033 private String address;034 private String owningEntity;035 private Queue<Order> orders;036037 public Warehouse(String address, String owningEntity) {038 this.address = address;039 this.owningEntity = owningEntity;040 this.orders = new LinkedList<>();041 }042043 public void addOrder(int orderId, Order newOrder) {044 orders.add(newOrder);045 }046047 public Order getLatestOrder() {048 return orders.peek();049 }050051 public String getWarehouseDetails() {052 return "Owned by " + this.owningEntity + "located at " + this.address;053 }054055 public int getPendingOrderSize() {056 return orders.size();057 }058059 public void checkAndEmptyWarehouse() throws Error {060 for (Order order : orders) {061 if (!order.getDeliveredStatus()) {062 throw new Error("Warehouse is not empty!");063 }064 }065 orders.clear();066 }067 }068069 class Distributor {070 private String owner;071 private String address;072 private Warehouse warehouse;073074 public Distributor(String owner, String address, Warehouse warehouse) {075 this.owner = owner;076 this.address = address;077 this.warehouse = warehouse;078 }079080 public Warehouse getWarehouse() {081 return this.warehouse;082 }083084 public Order getLatestOrder() {085 return this.warehouse.getLatestOrder();086 }087088 public String getDistributorDetails() {089 return "Owned by " + this.owner + " located at " + this.address;090 }091092 }093094 class Vehicle {095 private String vehicleNumber;096 private String model;097 private Distributor distributor;098099 public Vehicle(Distributor distributor, String vehicleNumber, String model) {100 this.vehicleNumber = vehicleNumber;101 this.model = model;102 this.distributor = distributor;103 }104105 public String getVehicleDetails() {106 return this.vehicleNumber + "\n" + this.model;107 }108109 public void stationVehicle() {110 System.out.println(this.vehicleNumber + " is now stationed");111 }112113 public void startVehicle() {114 System.out.println("Vehicle has been started");115 }116117 public void getOrderRoute() {118 String destination = distributor.getWarehouse().getLatestOrder().getDestination();119 System.out.println(vehicleNumber + " inbound to " + destination);120 }121122 }123124 public class MCBE1 {125126 public static void main(String[] args) {127 Order boots = new Order("Timberlands", "1191 Boylston St");128 Order hat = new Order("Red Sox Cap", "1193 Boylston St");129 Warehouse fenway = new Warehouse("1203 Akron St", "Fenway Goods Storage");130 fenway.addOrder(1, boots);131 fenway.addOrder(2, hat);132 Distributor redSox = new Distributor("Red Sox Co.", "42 Chanice Blvd", fenway);133 Vehicle jeep = new Vehicle(redSox, "9XJ3F", "Mercedes GLX");134 jeep.getOrderRoute();135 }136 }137