Class Inheritance ================= In this chapter, we continue to develop the time library by defining another kind of time to represent time offsets, such as 2 hours ago, and 30 minutes from now. We find an opportunity to use inheritance to good advantage, so we redefine some classes and a method to take advantage of inheritance. We also show how to define a generic function explicitly. The ```` class and methods --------------------------------------- In this section, we define a class to represent time offsets, and a method that describes a time offset. We start by defining the ```` class: .. code-block:: dylan // A relative time between -24:00 and +24:00 define class () slot total-seconds :: , init-keyword: total-seconds:; end class ; Reasons for defining two similar classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ```` class is similar to the ```` class. They both define a ``total-seconds`` slot. Why do we need to have two classes that are so similar? - A ```` is conceptually different from a ````. If the ``total-seconds`` slot of a ```` is ``180``, that means the time of day at 0:03 (that is, 3 minutes past midnight). If the ``total-seconds`` slot of a ```` is ``180``, that means 3 minutes in the future. If you ask what time it is, the answer is a ````. If you ask how long it takes to wash the dog, the answer is a ````. - A ```` can represent time in the past by having a negative value of ``total-seconds``. A ````, in contrast, should not have a negative value of ``total-seconds``. Later in this book, we provide methods that guarantee that the ``total-seconds`` slot of ```` instances is not negative; see :ref:`slots-setter-methods`, and :ref:`slots-initialize-methods`. - We need different methods for describing instances of ```` and instances of ````. The ```` method prints ``8:30``, and the ```` method should print ``minus 8:30`` or ``plus 8:30``. - Eventually, we will need to be able to add a ```` to a ````. For example, we can add the ```` 9:03 to the ```` 2:50 and get the ```` 11:53. We will also need to add two ```` instances. For example, 2 minutes plus 8 minutes is equal to 10 minutes. But we cannot add two ```` instances, because it does not make sense to add three o’clock to four o’clock. Creation of instances of ```` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can create an instance of ```` representing 15:20:10 in the future: .. code-block:: dylan-console ? define variable *my-time-offset* :: = make(, total-seconds: encode-total-seconds(15, 20, 10)); We can create an instance of ```` representing 6:45:30 in the past, by using the unary minus function, ``-``, which returns the negative of the value that follows it: .. code-block:: dylan-console ? define variable *your-time-offset* :: = make(, total-seconds: - encode-total-seconds(6, 45, 30)); .. _offset-methods-on-time-offset: Methods on ```` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Because a ```` can represent future time or past time, it will be useful to provide a convenient way to determine whether a ```` is in the past. We define a new predicate named ``past?`` as follows: .. code-block:: dylan define method past? (time :: ) => (past? :: ) time.total-seconds < 0; end method past?; The ``past?`` method returns an instance of ````, which is ``#t`` if the time offset is in the past, and otherwise is ``#f``. Here is an example: .. code-block:: dylan-console ? past?(*my-time-offset*) => #f ? past?(*your-time-offset*) => #t We need a method to describe instances of ````. The output should look like this: .. code-block:: dylan-console ? 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: .. code-block:: dylan define method say-time-offset (time :: ) => () 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: .. code-block:: dylan-console ? say-time-offset(*my-time-offset*); => ERROR: No applicable method for decode-total-seconds with argument => {instance } “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 :ref:`usr-class-second-method-decode-total-seconds`. One method takes an argument of the type ````. Another method takes an argument of the type ````. There is no method for instances of ````, 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. .. code-block:: dylan :linenos: // First approach: Call decode-total-seconds with an integer define method say-time-offset (time :: ) => () let (hours, minutes) = decode-total-seconds(abs(time.total-seconds)); 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; We changed only the call to ``decode-total-seconds`` on line 3. 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 ```` 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 ````: .. code-block:: dylan // Second approach: Define a method on define method decode-total-seconds (time :: ) => () 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: .. code-block:: dylan define method say-time-offset (time :: ) => () 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 ````, ````, and ````. However, the body of the method on ```` (defined in this section) is nearly identical to the body of the method on ```` (defined in :ref:`usr-class-second-method-decode-total-seconds`). The only difference is that we use ``abs`` in the method on ```` but not in the method on ````. If we used it in the method on ````, 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. Class inheritance ----------------- We have defined two simple classes, ```` and ````. We repeat the definitions here: .. code-block:: dylan // A specific time of day from 00:00 (midnight) to before 24:00 (tomorrow) define class () slot total-seconds :: , init-keyword: total-seconds:; end class ; // A relative time between -24:00 and +24:00 define class () slot total-seconds :: , init-keyword: total-seconds:; end class ; There is commonality between the two classes: - Both classes represent a kind of time — they have a conceptual basis in common. - Both classes have a ``total-seconds`` slot — they have structure in common. - Both classes need a ``decode-total-seconds`` method to convert the ``total-seconds`` slot to hours, minutes, and seconds — they have behavior in common. We can use inheritance to model the shared aspects of these two classes directly. We need to define a new class, such as ``