Refactor function call – Introduce Parameter Object introduce parameter object nine

Refactoring function call-Introduce Parameter Object introduces parameter object nine

1. Introduce parameter object

1.1. Usage scenarios

Certain parameters always appear naturally together. replace these parameters with an object

You’ll often see a certain set of parameters always being passed together. There may be several functions that use this set of parameters, and these functions may belong to the same class or may belong to different classes. Such a set of parameters is the so-called Data Clumps (data mud ball), we can use an object to wrap all these data, and then replace them with this object. Even if it’s just to organize the data together, it’s worth it. The value of this refactoring is to shorten the parameter column, and you know, a long parameter column is always difficult to understand. In addition, the access functions defined by the new objects can make the code more consistent, which further reduces the difficulty of understanding and modifying the code.

This refactoring can bring you even more benefits. Once you’ve grouped these parameters together, it’s often quick to discover some behavior that can be moved to a newly created class. Usually, the functions that originally used those parameters have some common processing for this set of parameters. If you move these common behaviors to the new object, you can reduce a lot of repetitive code.

1.2. How to do it

  • Create a new class to represent the set of parameters you want to replace. Make this class immutable.
  • compile.
  • Implement Add Parameter (275) for all functions that use this group of parameters, pass in the instance object of the above-mentioned newly created class, and set the value of this parameter to null.
  • If the function you modify is called by many other functions, you can keep the old function before modification and make it call the new function after modification. You can refactor the old function first, then modify the caller one by one to call the new function, and finally delete the old function.
  • For each item in Data Clumps (parameters here), remove it from the function signature, and modify the caller and function body so that they both get the value via the new parameter object instead.
  • Compile and test each time a parameter is removed.
  • After removing all the original parameters, observe whether there is a suitable function that can be moved into the parameter object by using Move Method (142).
  • What is moved may be the entire function, or a paragraph in the function. If it is the latter, first use Extract Method (110) to extract the paragraph into a separate function, and then move this new function.

1.3. Example

Below is an example of Accounts and Items. The Entry representing “account” is actually just a simple data container

 class Entry...
   Entry (double value, Date chargeDate) {<!-- -->
       _value = value;
       _chargeDate = chargeDate;
   }
   Date getDate(){<!-- -->
       return_chargeDate;
   }
   double getValue(){<!-- -->
       return_value;
   }
   private Date _chargeDate;
   private double _value;

The focus of my attention is the Account used to represent the “account”, which saves a set of Entry objects, and has a function to calculate the total amount of account items between two dates:

 class Account...
   double getFlowBetween (Date start, Date end) {<!-- -->
       double result = 0;
       Enumeration e = _entries. elements();
       while (e.hasMoreElements()) {<!-- -->
           Entry each = (Entry) e. nextElement();
           if (each. getDate(). equals(start) ||
               each.getDate().equals(end) ||
                (each. getDate(). after(start) & & each.getDate().before(end)))
           {<!-- -->
               result += each. getValue();
           }
       }
       return result;
   }
   private Vector _entries = new Vector();
 client code...
   double flow = anAccount.getFlowBetween(startDate, endDate);

I can’t count how many times I’ve seen code represent “a range” as a “pair of values”, such as start and end for a date range, upper and lower for a numeric range, and so on. I know why this happens, after all I do it quite often myself. However, since I learned about the Range pattern [Fowler, AP], I try to replace it with “range objects”. My first step is to declare a simple data container to represent ranges:

 class DateRange {<!-- -->
   DateRange (Date start, Date end) {<!-- -->
       _start = start;
       _end = end;
   }
   Date getStart() {<!-- -->
       return_start;
   }
   Date getEnd() {<!-- -->
       return_end;
   }
   private final Date _start;
   private final Date_end;
 }

I set DateRange as immutable, that is, all fields in it are final and can only be assigned by the constructor, so no function can modify any field value in it. This is a wise decision, as it avoids the hassle of aliases. Java’s function parameters are passed by value, and immutable classes can just imitate the working method of Java parameters, so this approach is the most suitable for this refactoring.
Next I add the DateRange object to the parameter column of the getFlowBetween() function:

 class Account...
   double getFlowBetween (Date start, Date end, DateRange range) {<!-- -->
       double result = 0;
       Enumeration e = _entries. elements();
       while (e.hasMoreElements()) {<!-- -->
           Entry each = (Entry) e. nextElement();
           if (each. getDate(). equals(start) ||
               each.getDate().equals(end) ||
               (each. getDate(). after(start) & & each.getDate().before(end)))
           {<!-- -->
               result += each. getValue();
           }
       }
       return result;
   }
 client code...
   double flow = anAccount.getFlowBetween(startDate, endDate, null);

At this point, I only need to compile because I haven’t modified any behavior of the program. The next step is to remove one of the old parameters and replace it with a new object. First I remove the start parameter and modify the getFlowBetween() function and its callers to use the new object instead:

 class Account...
   double getFlowBetween (Date end, DateRange range) {<!-- -->
       double result = 0;
       Enumeration e = _entries. elements();
       while (e.hasMoreElements()) {<!-- -->
           Entry each = (Entry) e. nextElement();
           if (each. getDate(). equals(range. getStart()) ||
               each.getDate().equals(end) ||
               (each.getDate().after(range.getStart()) & & each.getDate().before(end)))
           {<!-- -->
               result += each. getValue();
           }
       }
       return result;
   }
 client code...
   double flow = anAccount.getFlowBetween(endDate, new DateRange (startDate, null));

I then remove the parameter as well

 class Account...
   double getFlowBetween (DateRange range) {<!-- -->
       double result = 0;
       Enumeration e = _entries. elements();
       while (e.hasMoreElements()) {<!-- -->
           Entry each = (Entry) e. nextElement();
           if (each. getDate(). equals(range. getStart()) ||
               each.getDate().equals(range.getEnd()) ||
               (each.getDate().after(range.getStart()) & & each.getDate().before(range.getEnd())))
           {<!-- -->
               result += each. getValue();
           }
       }
       return result;
   }
 client code...
       double flow = anAccount.getFlowBetween(new DateRange (startDate, endDate));

Now, I have introduced “parameter objects”. I can further benefit from this refactoring by moving the appropriate behavior from other functions into this new object. Here, I select the code in the conditional expression, implement Extract Method and Move Method, and finally get the following code:

 class Account...
   double getFlowBetween (DateRange range) {<!-- -->
       double result = 0;
       Enumeration e = _entries. elements();
       while (e.hasMoreElements()) {<!-- -->
           Entry each = (Entry) e. nextElement();
           if (range. includes(each. getDate())) {<!-- -->
               result += each. getValue();
           }
       }
       return result;
   }
 class DateRange...
   boolean includes (Date arg) {<!-- -->
       return (arg. equals(_start) ||
               arg. equals(_end) ||
                (arg. after(_start) & amp; & amp; arg. before(_end)));
   }

Such a simple refining and moving action, I usually complete it in one step. If I make a mistake along the way, I can go back to where I was before the refactor and start over in two smaller steps.