5.1.3 Methods on <time-offset>
Because a <time-offset> can represent future time or past time, it will be useful to provide a convenient way to determine whether a <time-offset> is in the past. We define a new predicate named past? as follows:
define method past? (time :: <time-offset>) => (past? :: <boolean>) time.total-seconds < 0; end method past?;
The past? method returns an instance of <boolean>, which is #t if the time offset is in the past, and otherwise is #f. Here is an example:
? past?(*my-time-offset*) #f ? past?(*your-time-offset*) #t
We need a method to describe instances of <time-offset>. The output should look like this:
? say-time-offset(*my-time-offset*); plus 15:20 ? say-time-offset(*your-time-offset*); minus 6:45
We might define the method in this way:
define method say-time-offset (time :: <time-offset>) => ()
let(hours, minutes) = decode-total-seconds(time);
format-out("%s %d:%s%d",
if (past?(time)) "minus" else "plus" end,
hours,
if (minutes < 10) "0" else "" end,
minutes);
end method say-time-offset;
If we test this method in a listener, however, the result is different:
? say-time-offset(*my-time-offset*);
ERROR: No applicable method for decode-total-seconds with argument {instance <time-offset>}
"No applicable method" means that there is no method for this generic function that is appropriate for the arguments. To understand this error, we can look at the methods for decode-total-seconds in Figure 4.1, page 49. One method takes an argument of the type <integer>. Another method takes an argument of the type <time-of-day>. There is no method for instances of <time-offset>, so Dylan signals an error. There are three possible approaches to solving this problem.
As a first approach, we could define the say-time-offset method to call decode-total-seconds with an integer.
// First approach: Call decode-total-seconds with an integer
define method say-time-offset (time :: <time-offset>) => () // 1
let(hours, minutes) = decode-total-seconds(abs(time.total-seconds)); // 2
format-out("%s %d:%s%d", // 3
if (past?(time)) "minus" else "plus" end, // 4
hours, // 5
if (minutes < 10) "0" else "" end, // 6
minutes); // 7
end method say-time-offset; // 8
We changed only the call to decode-total-seconds on line 2. Here, we call it with the absolute value (returned by the abs function) of the total-seconds slot.
This approach works, but it is awkward because we need to remember what kinds of arguments decode-total-seconds can take. The convenient calling syntax that we introduced for calling decode-total-seconds with an instance of <time-of-day> is not available for other kinds of time.
As a second approach, we could to define a third method for decode-total-seconds that takes as its argument an instance of <time-offset>:
// Second approach: Define a method on <time-offset> define method decode-total-seconds (time :: <time-offset>) => () decode-total-seconds(abs(time.total-seconds)); end method decode-total-seconds;
The method for say-time-offset can then call decode-total-seconds, as we did in the first place:
define method say-time-offset (time :: <time-offset>) => ()
let(hours, minutes) = decode-total-seconds(time);
format-out("%s %d:%s%d",
if (past?(time)) "minus" else "plus" end,
hours,
if (minutes < 10) "0" else "" end,
minutes);
end method say-time-offset;
This approach works, and it preserves the flexibility of calling decode-total-seconds on instances of <integer>, <time-of-day>, and <time-offset>. However, the body of the method on <time-offset> (defined in this section) is nearly identical to the body of the method on <time-of-day> (defined in Section 4.6.5, page 48). The only difference is that we use abs in the method on <time-offset> but not in the method on <time-of-day>. If we used it in the method on <time-of-day>, it would be harmless. Duplication of code is ugly, adds maintenance overhead, and is particularly undesirable when programming in an object-oriented language, where it may indicate a flaw in the overall design.
The best solution to the problem lies in a third approach — to rethink the classes and methods in a more object-oriented style, using inheritance. We show this solution in the next section.




