Smell Detail

Message Chains

过长的消息链

Couplers

The client needs to obtain the required object through a long chain of method calls.

Define

Introduction

Message Chains occur when a client must navigate through a sequence of method calls to get the final object it needs. This increases coupling to multiple classes and violates the Law of Demeter.

Key Identifiers

  • Long chains of successive method calls
  • The client must understand internal object structure
  • Changes to intermediate objects ripple into client code
  • Violates the "talk only to your immediate friends" principle

Refactoring Suggestions

  1. Hide Delegation: Provide convenience methods on the intermediate class
  2. Extract Method: Wrap the long navigation chain inside a single method
  3. Move Method: Relocate behavior to a class that can directly supply the needed data
  4. Introduce Local Extension: Add helper/convenience methods (e.g., via wrapper or extension) to shorten access

Explanation

The client traverses a long chain to obtain the office location. A direct method such as getManagerOfficeLocation() should be added to the Company class to shorten the chain and reduce coupling.

Code Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import java.util.List;
import java.util.ArrayList;
class Company {
private List<Department> departments;
private String name;
public Company() {
this.departments = new ArrayList<>();
}
public Department getDepartment(int index) {
return departments.get(index);
}
public String getName() { return name; }
public int getDepartmentCount() { return departments.size(); }
public void addDepartment(Department department) {
departments.add(department);
}
}
class Department {
private List<Employee> employees;
private Manager manager;
private String name;
public Department() {
this.employees = new ArrayList<>();
}
public Employee getEmployee(int index) {
return employees.get(index);
}
public Manager getManager() { return manager; }
public String getName() { return name; }
public void setManager(Manager manager) { this.manager = manager; }
public void addEmployee(Employee employee) { employees.add(employee); }
}
class Manager {
private String name;
private Office office;
private Employee assistant;
public String getName() { return name; }
public Office getOffice() { return office; }
public Employee getAssistant() { return assistant; }
public void setName(String name) { this.name = name; }
public void setOffice(Office office) { this.office = office; }
public void setAssistant(Employee assistant) { this.assistant = assistant; }
}
class Office {
private String location;
private String building;
private int floor;
public String getLocation() { return location; }
public String getBuilding() { return building; }
public int getFloor() { return floor; }
public void setLocation(String location) { this.location = location; }
public void setBuilding(String building) { this.building = building; }
public void setFloor(int floor) { this.floor = floor; }
}
class Employee {
private String name;
private String position;
public String getName() { return name; }
public String getPosition() { return position; }
public void setName(String name) { this.name = name; }
public void setPosition(String position) { this.position = position; }
}
// Client code that requires long call chains
class CompanyService {
// Long message chain example 1
public void printManagerOffice(Company company) {
String location = company.getDepartment(0).getManager().getOffice().getLocation();
System.out.println("Manager office: " + location);
}
// Long message chain example 2
public void printAssistantName(Company company) {
String assistantName = company.getDepartment(0).getManager().getAssistant().getName();
System.out.println("Assistant: " + assistantName);
}
// Even longer message chain example
public void printManagerOfficeFloor(Company company) {
int floor = company.getDepartment(0).getManager().getOffice().getFloor();
System.out.println("Manager office floor: " + floor);
}
// Multiple chained calls in one method
public void printDepartmentSummary(Company company) {
Department dept = company.getDepartment(0);
String managerName = dept.getManager().getName();
String officeLocation = dept.getManager().getOffice().getLocation();
String assistantName = dept.getManager().getAssistant().getName();
String assistantPosition = dept.getManager().getAssistant().getPosition();
System.out.println("Department Manager: " + managerName);
System.out.println("Office: " + officeLocation);
System.out.println("Assistant: " + assistantName + " (" + assistantPosition + ")");
}
// Another example showing violation of Law of Demeter
public boolean isManagerOfficeOnTopFloor(Company company) {
return company.getDepartment(0).getManager().getOffice().getFloor() > 10;
}
// Complex chain with conditional logic
public String getManagerContactInfo(Company company) {
Manager manager = company.getDepartment(0).getManager();
if (manager.getAssistant() != null) {
return "Contact " + manager.getAssistant().getName() +
" at " + manager.getOffice().getLocation();
} else {
return "Contact " + manager.getName() +
" directly at " + manager.getOffice().getLocation();
}
}
}