Skip to main content
Engineering LibreTexts

9.3: Eliminate Navigation Code

  • Page ID
    32416
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Also Known As: Law of Demeter [LHR88].

    Intent Reduce the impact of changes by shifting responsibility down a chain of connected classes.

    Problem

    How do you reduce coupling due to classes that navigate through the object graph?

    This problem is difficult because:

    • Changes in the interfaces of a class will affect not only direct clients, but also all the indirect clients that navigate to reach it.

    Yet, solving this problem is feasible because:

    • Navigation code is typically a sign of misplaced responsibilities and violation of encapsulation.

    Solution

    Iteratively move behavior defined by an indirect client to the container of the data on which it operates.

    Note that actual reengineering steps are basically the same as those of Move Behavior Close to Data, but the manifestation of the problem is rather different, so different detection steps apply.

    Detection

    Look for indirect providers:

    • Each time a class changes, e.g., by modifying its internal representation or collaborators, not only its direct but also indirect client classes have to be changed.

    • Look for classes that contain a lot public attributes, accessor methods or methods returning as value attributes of the class.

    • Big aggregation hierarchies containing mostly data classes often play the role of indirect provider.

    Look for indirect clients that contain a lot of navigation code. Navigation code is of two kinds:

    • a sequence of attribute accesses, e.g., a.b.c.d where b is an attribute of a, c is an attribute of b and d an attribute of c. The result of such a sequence can be assigned to variable or a method of the last object can be invoked, e.g., a.b.c.d.op(). Such a sequence navigation does not occur in Smalltalk where all the attributes are protected.

    • a sequence of accessor method calls. In Java and C++ such a sequence has the form object.m1().m2().m3() where object is an expression returning an object, m1 is a method of object, m2 a method of the object returned by the invocation of m1, m3 a method of the object returned by the invocation of m2 and so on. In Smalltalk navigation code has the following form receiver m1 m2 ... mn The same navigation code sequence is repeated in different methods on the same or different clients.

    Navigation code can be detected by simple pattern matching. However, to really detect a method call navigation sequence leading to coupled classes, you should filter out sequences of calls converting one object to another one. For example, the following two Java expressions are not problematic because they deal with object conversion.

    leftSide().toString()
    i.getValue().isShort()
    

    To deal with this case you can:

    • look for more than two calls, or
    • eliminate from consideration known object conversion calls, including standard method invocations for converting to and from primitive types.

    The use of additional variables, can sometimes disguise navigation code, so reading the code is often necessary. For instance, the following Java code does not contain a chain of invocations.

    Token token;
    token = parseTree.token();
    if (token.identifier() != null) {
    ...
    

    However, it is equivalent to the following code, which does contain a chain of invocations

    if (parseTree.token().identifier() != null) {
        ... 
    

    Smalltalk. Simply searching for sequences of calls in Smalltalk code can create a lot of noise because Smalltalk does not have predefined control structures but uses messages even for implementing control structures. The above example with the disguised navigation code would read as follows in Smalltalk. (Note the messages isNil and ifFalse:[...])

    | token |
    token := parseTree token.
    token identifier isNil ifFalse:[...]
    

    The equivalent version with navigation code becomes.

    parseTree token identifier isNil ifFalse: [...]
    

    The following code segments contain a sequence of invocations but do not pose any problems because the first deals with boolean testing and the second with conversion (abuse of conversion, in fact).

    (a isNode) & (a isAbstract) ifTrue: [...]
    aCol asSet asSortedCollection asOrderedCollection
    

    Java. For Java or C++, primitives data types and control structures are not implemented using objects, so simple pattern matching produces less noise. For example, a simple Unix command like:

    egrep '.*\(\).*\(\).*\(\).' *.java
    egrep '.*\..*\..*\..' *.java
    

    identifies lines of code like the following ones, which are examples of navigation code coupling between classes, and filters out the conversions mentioned above.

    a.getAbstraction().getIdentifier().traverse(this)
    a.abstraction.identifier.traverse(this)
    

    More sophisticated matching expressions can reduce the noise produced by the parentheses of casts or other combinations.

    Chains of data containers can be converted into service providers, thereby eliminating navigation code and reducing coupling between classes.
    Figure \(\PageIndex{1}\): Chains of data containers can be converted into service providers, thereby eliminating navigation code and reducing coupling between classes.

    AST Matching. If you have a way to express tree matching, you can detect navigation code. For example, the Rewrite Rule Editor that comes with the Refactoring Browser [RBJ97] can detect navigation code using the pattern ’@object ’mess1 ’mess2 ’mess3. To narrow the analysis of the results you should only consider messages that belong to the domain objects and eliminate all the method selectors of libraries objects like (isNil, not, class, ...).

    Steps

    The recipe for eliminating navigation code is to recursively Move Behavior Close to Data. Figure \(\PageIndex{1}\) illustrates the transformation.

    1. Identify the navigation code to move.
    2. Apply Move Behavior Close to Data to remove one level of navigation. (At this point your regression tests should run.)
    3. Repeat, if necessary.

    Caution. It is important to note that the refactoring process relies on pushing code from the clients to the providers. In the example, from Car to Engine and from Engine to Carburetor. A common mistake is to try to eliminate navigation code by defining accessors at the client class level that access the attributes of the provider attribute values, e.g., defining an accessor getCarburetor in the class Car. Instead of reducing coupling between the classes, it just increases the number of public accessors and makes the system more complex.

    Tradeoffs

    Pros

    • Chains of dependencies between classes are eliminated, so changes in classes at the lowest level will impact fewer clients.

    • Functionality that was implicit in the system is now named and explicitly available to new clients.

    Cons

    • The systematic application of Eliminate Navigation Code may lead to large interfaces. In particular, if a class defines many instance variables that are collections, then Eliminate Navigation Code would force you to define a large number of additional methods to shield the underlying collections.

    Difficulties

    • Deciding when to apply Eliminate Navigation Code can be difficult. Defining methods that merely delegate requests to class collaborators may not always be the solution. It may happen that giving away internal information can reduce the interface of a class. For example, if a class implements some well-defined behaviors but also serves as a Facade to other collaborators, it may be simpler to give access to the collaborator directly to reduce the interface of the class.

    When the legacy solution is the solution

    Navigation code may be the best solution when objects are graphically presented or mapped to a database. In such cases the goal is to really expose and mimic the structural relationships between classes. Eliminating navigation code will be a futile exercise.

    How to remove the unnecessary dependencies between the Reports class and the File and Employee Classes.
    Figure \(\PageIndex{2}\): How to remove the unnecessary dependencies between the Reports class and the File and Employee Classes.

    It is sometimes necessary for a client to talk with its indirect providers. This is true when direct providers play the role of an object server that returns certain objects given certain properties (OOID, keys...). In this situation the client calls the object server (a direct provider) that returns objects (indirect providers) to which the client sends messages.

    Example

    After having modified the Employee, Payroll and TelephoneGuide classes, you noticed that it took 1/2 an hour to rebuild the whole project. Next time you see Chris (one of the maintainers) you ask him why this build took so long. “You probably changed the Employee class” he answers, “we don’t dare to touch that class anymore since so many classes depend on it”.

    You decide to examine this Employee class in further detail and find many unnecessary dependencies. For instance (as shown in Figure \(\PageIndex{2}\)) there is a class Reports, implementing one method countHandledFiles, which counts for each Department the number of files that are handled by all of its employees. Unfortunately, there is no direct relationship between Department and File and consequently the ReportHandledFiles must navigate over a department’s employees to enumerate all the files and access the handled() status.

    The Java code below shows the situation before and after applying Eliminate Navigation Code. The bold textual elements highlight problems and the solutions in the before and after situation.

    Before

    public class Reports {
    ...
        public static void countHandledFiles(Department department) {
            int nrHandled = 0, nrUnhandled = 0;
            
            for (int i=0; i < department.employees.length; i++) {
                for (int j=0; j < department.employees[i].files.length; j++) {
                    if (department.employees[i].files[j].handled()) {
                        nrHandled++;}
                    else {
                        nrUnhandled++;}}}  
    ...}
    

    The method countHandledFiles counts the number of handled files, by asking the current department its employees and for each of these files. The classes Department and Employee have to declare those attributes public. With this implementation, two problems occur:

    1. The Reports class must know how to enumerate the associations between Department, Employee and File, and this information must be accessible in the public interface of each of the classes. If one of these public interfaces change, then this change will affect all associated classes.

    2. The method countHandledFiles is implemented by directly accessing the variables employees and files. This unnecessarily couples the class Reports and the classes Department and Employee. If the class Department or Employee change the data-structure used to gold the associated objects, then all the methods in class Reports will have to be adapted.

    Steps

    The solution is to extract the nested for loops as separate methods and move them on the appropriate classes. This is actually a two step process.

    First extract the outer for loop from Reports.countHandledFiles as a separate method (name it countHandledFiles as well) and move it to the class Department.

    public class Department {
    ...
        public void countHandledFiles (Counter nrHandled, Counter nrUnhandled) {
            for (int i=0; i < this.employees.length; i++) {
                for (int j=0; j < this.employees[i].files.length; j++) {
                    if (this.employees[i].files[j].handled()) {
                        nrHandled.increment();}
                    else {
                        nrUnhandled.increment();}}}}
    ...}
    
    public class Reports {
    ...
        private static void countHandledFiles(Department department) {
            Counter nrHandled = new Counter (0), nrUnhandled = new Counter(0);
            department.countHandledFiles(nrHandled, nrUnhandled);
    ...}
    

    Next, extract the inner for loop from Department.countHandledFiles (also named countHandledFiles) and move it to the class Employee.

    public class Employee {
        ...
        public void countHandledFiles (Counter nrHandled, Counter nrUnhandled) {
            for (int j=0; j < this.files.length; j++) {
                if (this.files[j].handled()) {
                    nrHandled.increment();}
                else {
                    nrUnhandled.increment();}}}
    ...}
    
    public class Department {
    ...
        public void countHandledFiles (Counter nrHandled, Counter nrUnhandled) {
            for (int i=0; i < this.employees.length; i++) {
                this.employees[i].countHandledFiles(nrHandled, nrUnhandled);}}
    ...}
    

    If all direct accesses to the employees and files variables are removed, these attributes can be declared private.

    Rationale

    A method “M” of an object “O” should invoke only the methods of the following kinds of objects.

    1. itself
    2. its parameters
    3. any object it creates/instantiates

    4. its direct component objects

    — Law of Demeter

    Navigation code is a well-known symptom of misplaced behavior [LK94] [Sha97] [Rie96] that violates the Law of Demeter [LHR88]. It leads to unnecessary dependencies between classes and as a consequence changing the representation of a class requires all clients to be adapted.

    Related Patterns

    Eliminate Navigation Code and Compare Code Mechanically reinforce each other: Navigation code that is spread across different clients spreads duplicated code over the system. Compare Code Mechanically helps to detect this phenomenon. Eliminate Navigation Code brings the duplicated code together, where it is easier to refactor and eliminate.


    This page titled 9.3: Eliminate Navigation Code is shared under a CC BY-SA license and was authored, remixed, and/or curated by Serge Demeyer, Stéphane Ducasse, Oscar Nierstrasz.

    • Was this article helpful?