Skip to main content
Engineering LibreTexts

9.2: Move Behavior Close to Data

  • Page ID
    32415
  • \( \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}}\)

    Intent Strengthen encapsulation by moving behavior from indirect clients to the class containing the data it operates on.

    Problem

    How do you transform a class from being a mere data container into a real service provider?

    This problem is difficult because:

    • Data containers offer only accessor methods or public instance variables, and not real behavior, forcing clients to define the behavior themselves instead of just using it. New clients typically have to reimplement this behavior.

    • If the internal representation of a data container changes, many clients have to be updated.

    • Data containers cannot be used polymorphically since they define no behavior and their interfaces consist mainly of accessor methods. As a consequence, clients will be responsible for deciding which behavior is called for in any given context.

    Yet, solving this problem is feasible because:

    • You know what operations clients perform with the data.

    Solution

    Move behavior defined by indirect clients to the container of the data on which it operates.

    Detection

    Look for:

    • Data containers, i.e., classes defining mostly public accessor methods and few behavior methods (i.e., the number of methods is approximately 2 times larger than the number of attributes.
    • Duplicated client code that manipulates data of separate provider classes. If multiple clients implement different behavior, consider instead applying Transform Client Type Checks.

    • Methods in client classes that invoke a sequence of accessor methods (see Eliminate Navigation Code).

    Classes that were mere data containers are transformed into real service providers.
    Figure \(\PageIndex{1}\): Classes that were mere data containers are transformed into real service providers.

    Steps

    Move Behavior Close to Data makes use of the refactorings Extract Method and Move Method, since the behavior in question will have to be extracted from a client method and then moved to a provider class.

    1. Identify the client behavior that you want to move, i.e., the complete method or a part of a method that accesses provider data.

      1. Look for the invocations of the accessor methods of the data container.

      2. Look for duplicated code in multiple clients that access the same provider data.

    2. Create the corresponding method in the provider class, if it does not already exist. Be sure to check that moving the code will not introduce any naming conflicts. Tools like the Refactoring Browser [RBJ97] automate these steps:

      1. If the extracted functionality is a complete method with arguments, check that the arguments do not conflict with attributes of the provider class. If so, rename the arguments.

      2. If the extracted functionality uses temporary variables, check that the local variables do not conflict with attributes or variables in the target scope. If so, rename the temporary variables.

      3. Check if the extracted functionality accesses local variables of the client classes (attributes, temporary variables, ...), if so, add arguments to the method to represent these client variables.

    3. Give an intention-revealing name to the new method. Among others, intention revealing names do not contain references to the class they belong to, because this makes the method less reusable. For instance, instead of defining a method addToSet() on a class Set, it is better to name it simply add(). Similarly, it is not such a good idea to define a method binarySearch() on a class Array, because the method name implies a sorted random access collection, while the name search() does not have such implications.

    4. In the client invoke the new provider method with the correct parameters.

    5. Clean up the client code. In the case the moved functionality was a complete method of the client class:

      1. check all the methods that invoke the old, moved method and ensure that they now call the new provider method instead, and

      2. remove the old method from the client or deprecate it. (Deprecate Obsolete Interfaces).

      It may be the case that the calling methods defined on the same object have to be also moved to the provider. In such a case repeat the steps for the methods.

    6. Repeat for multiple clients. Note that duplicated code in multiple clients will be removed in step 2, since there is no need to move code that has already been transferred to the provider. In case many similar, but not identical methods are introduced to the provider, consider factoring out the duplicated fragments as protected helper methods.

    Tradeoffs

    Pros

    • Data containers are converted to service providers with clear responsibilities.
    • The service providers become more useful to other clients.
    • Clients are no longer responsible for implementing provider behavior.
    • Clients are less sensitive to internal changes of the provider.
    • Code duplication in the system decreases.

    Cons

    • If the moved behavior also accesses client data, turning these accesses into parameters will make the interface of the provider more complex and introduce explicit dependencies from the provider to the client.

    Difficulties

    • It may not be clear whether client code really should be moved to the data provider. Some classes like Stream or Set are really designed as data providers. Consider moving the code to the provider if:
      • the functionality represents a responsibility of the provider. For example, a class Set should provide mathematical operations like union and intersection. On the other hand, a generic Set should not be responsible for operations on sets of Employees.
      • the functionality accesses the attributes of the provider,
      • the functionality is defined by multiple clients.
    • If the provider is really designed as a data container, consider defining a new provider class that wraps an instance of the data provider and holds the associated behavior. For example, an EmployeeSet might wrap a Set instance and provide a more suitable interface.

    When the legacy solution is the solution

    Data containers may have been automatically generated from a database schema to provide an object interface to an existing database. It is almost always a bad idea to modify generated classes, since you will lose your changes if the code ever needs to be regenerated. In this case, you may decide to implement wrapper classes to hold the behavior that should be associated with the generated classes. Such a wrapper would function as an Adapter that converts the generated data container to a real service provider.

    Sometimes you know that a class defined in a library is missing crucial functionality. For example, an operation convertToCapitals that is missing for class String. In such a case it is typically impossible to add code to the library, so you may have to define it in client class. In C++ for example, it may be the only way to avoid recompilation or to extend a class when the code is not available [ABW98]. In Smalltalk you have the possibility to extend or modify the library, however you should pay particular attention to separate the additional code so you can easily merge it with future releases of the library, and quickly detect any conflicts.

    The intent of the Visitor design pattern states: “Represent an operation to be performed on the elements of an object structure in a class separate from the elements themselves. Visitor lets you define a new operation without changing the classes of the elements on which it operates” [GHJV95]. The Visitor pattern is one of the few cases where you want to have classes access the data of a separate provider class. Visitor allows one to dynamically add new operations to a set of stable classes without having to change them.

    Configuration classes are classes that represent the configuration of a system (e.g., global parameters, language dependent representation, policies in place). For example, in a graphic tool the default size of the boxes, edges, width of the lines can be stored in a such class and other classes refer to it when needed.

    Mapping classes are classes used to represent mappings between objects and their user interface or database representation. For example, a software metric tool should graphically represent the available metrics in a widget-list so that the user can select the metrics to be computed. In such a case the graphical representation of the different metrics will certainly differ from their internal representation. A mapping class keeps track of the association.

    Example

    One of the recurring complaints of the customers is that it takes too much time to change the reports generated by the information system. By talking to the maintainers you learn that they find generating the reports quite boring. “Its’s always the same code you have to write,” says Chris, one of the maintainers. “You fetch a record out of the database, print its fields and then proceed to the next record.”

    The Payroll and Telephone classes access the internal representation of the class Employee to print a representation.
    Figure \(\PageIndex{2}\): The Payroll and Telephone classes access the internal representation of the class Employee to print a representation.

    You strongly suspect a case of data-containers and a closer examination of the code confirms your suspicion. Almost all of the classes interfacing with the database contain accessor methods only, and the programs generating reports are forced to use these accessors. One striking example is the case of the Payroll application, which has lots in common with the TelephoneGuide application and you decide to try to move the common functionality to the Employee class.

    Before

    As shown in Figure \(\PageIndex{2}\), both the Payroll and TelephoneGuide classes print labels, treating Employee instances as data containers. Thus, Payroll and TelephoneGuide are indirect clients of the attributes of Employee, and define printing code that should have been provided by the Employee class. The following code show how this would look like in Java.

    public class Employee {
        public String[] telephoneNumbers = {};
        ...
        public String name() {
            return name;}
            
        public String address() {
            return address;}
    }
    
    public class Payroll { 
    
        public static Employee currentEmployee;
    
        public static void printEmployeeLabel () {
            System.out.println(currentEmployee.name());
            System.out.println(currentEmployee.address());
            for (int i=0; i < currentEmployee.telephoneNumbers.length; i++) {
                System.out.print(currentEmployee.telephoneNumbers[i]);
                System.out.print(" ");}
            System.out.println("");}
    ...
    }
    
    public class TelephoneGuide {
    
        public static void printEmployeeTelephones (Employee emp) {
            System.out.println(emp.name());
            System.out.println(emp.address());
            for (int i=0; i < emp.telephoneNumbers.length -- 1; i++) {
                System.out.print(emp.telephoneNumbers[i]);
                System.out.print(" ---- ");}
            System.out.print(emp.telephoneNumbers[
                emp.telephoneNumbers.length -- 1]);
            System.out.println("");}
    ...
    }
    

    Note that although both print methods implement essentially the same functionality, there are some slight differences. Among others, TelephoneGuide.printEmployeeTelephones uses a different separator while printing out the telephone numbers.

    Steps

    The different separators can easily be dealt with by defining a special parameter representing the separator to be used. Thus TelephoneGuide.printEmployeeTelephones gets rewritten as follows.

    public static void printEmployeeTelephones (Employee emp, String separator) {
        ...
        for (int i=0; ...
            System.out.print(separator);}
        ...}
    ...
    

    Next, move the printEmployeeTelephones method from TelephoneGuide to Employee. Thus, copy the code and replace all references to the emp parameter with a direct reference to the attributes and methods. Also, ensure that the new method has an intention revealing name, thus omit the Employee part from the method name, resulting in a method printLabel.

    public class Employee {
        ...
        public void printLabel (String separator) {
        
            System.out.println(name);
            System.out.println(address);
            for (int i=0; i < telephoneNumbers.length -- 1; i++) {
                System.out.print(telephoneNumbers[i]);
                System.out.print(separator);
            }
        System.out.print(telephoneNumbers[telephoneNumbers.length -- 1]);
        System.out.println("");
    }
    

    Then replace the method bodies of Payroll.printEmployeeLabel and TelephoneGuide.printEmployeeTelephones with a simple invocation of the Employee.printLabel method.

    public class Payroll {
        ...
        public static void printEmployeeLabel () {
            currentEmployee.printLabel(" ");
        ...}
    
    public class TelephoneGuide {
        ...
        public static void printEmployeeTelephones (Employee emp) {
            emp.printLabel(" ---- ");}
        ...}
    

    Finally, verify which other methods refer to the name(), address() and telephoneNumbers. If no such methods exist, consider to declare those methods and attributes as private.

    After

    After applying Move Behavior Close to Data the class Employee now provides a printLabel method which takes one argument to represent the different separators (see Figure \(\PageIndex{3}\)). This is a better situation because now clients do not rely on the internal representation of Employee. Moreover, by moving the behavior near the data it operates, the class represents a conceptual entity with an emphasis on the services it provides instead of structure it implements.

    The Payroll class uses the public interface of the class Employee to print a representation of Employee; data accessors became private.
    Figure \(\PageIndex{3}\): The Payroll class uses the public interface of the class Employee to print a representation of Employee; data accessors became private.

    Rationale

    Keep related data and behavior in one place.

    — Arthur Riel, Heuristic 2.9 [Rie96]

    Data containers impede evolution because they expose structure and force clients to define their behavior rather than sharing it. By promoting data containers to service providers, you reduce coupling between classes and improve cohesion of data and behavior.

    Related Patterns

    Encapsulate Field offers heuristics that help determine where methods should be defined during a design phase. The text offers rationale for applying Move Behavior Close to Data.


    This page titled 9.2: Move Behavior Close to Data 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?