(octave.info)Inheritance and Aggregation


Next: classdef Classes Prev: Overloading Objects Up: Object Oriented Programming
Enter node , (file) or (file)node

34.5 Inheritance and Aggregation
================================

Using classes to build new classes is supported by Octave through the
use of both inheritance and aggregation.

   Class inheritance is provided by Octave using the ‘class’ function in
the class constructor.  As in the case of the polynomial class, the
Octave programmer will create a structure that contains the data fields
required by the class, and then call the ‘class’ function to indicate
that an object is to be created from the structure.  Creating a child of
an existing object is done by creating an object of the parent class and
providing that object as the third argument of the class function.

   This is most easily demonstrated by example.  Suppose the programmer
needs a FIR filter, i.e., a filter with a numerator polynomial but a
denominator of 1.  In traditional Octave programming this would be
performed as follows.

     >> x = [some data vector];
     >> n = [some coefficient vector];
     >> y = filter (n, 1, x);

   The equivalent behavior can be implemented as a class ‘@FIRfilter’.
The constructor for this class is the file ‘FIRfilter.m’ in the class
directory ‘@FIRfilter’.

     ## -*- texinfo -*-
     ## @deftypefn  {} {} FIRfilter ()
     ## @deftypefnx {} {} FIRfilter (@var{p})
     ## Create a FIR filter with polynomial @var{p} as coefficient vector.
     ## @end deftypefn
     
     function f = FIRfilter (p)
     
       if (nargin > 1)
         print_usage ();
       endif
     
       if (nargin == 0)
         p = @polynomial ([1]);
       elseif (! isa (p, "polynomial"))
         error ("@FIRfilter: P must be a polynomial object");
       endif
     
       f.polynomial = [];
       f = class (f, "FIRfilter", p);
     
     endfunction

   As before, the leading comments provide documentation for the class
constructor.  This constructor is very similar to the polynomial class
constructor, except that a polynomial object is passed as the third
argument to the ‘class’ function, telling Octave that the ‘FIRfilter’
class will be derived from the polynomial class.  The FIR filter class
itself does not have any data fields, but it must provide a struct to
the ‘class’ function.  Given that the ‘@polynomial’ constructor will add
an element named POLYNOMIAL to the object struct, the ‘@FIRfilter’ just
initializes a struct with a dummy field POLYNOMIAL which will later be
overwritten.

   Note that the sample code always provides for the case in which no
arguments are supplied.  This is important because Octave will call a
constructor with no arguments when loading objects from saved files in
order to determine the inheritance structure.

   A class may be a child of more than one class (*note class:
XREFclass.), and inheritance may be nested.  There is no limitation to
the number of parents or the level of nesting other than memory or other
physical issues.

   For the ‘FIRfilter’ class, more control about the object display is
desired.  Therefore, the ‘display’ method rather than the ‘disp’ method
is overloaded (Note: Class Methods).  A simple example might be

     function display (f)
       printf ("%s.polynomial", inputname (1));
       disp (f.polynomial);
     endfunction

   Note that the ‘FIRfilter’’s display method relies on the ‘disp’
method from the ‘polynomial’ class to actually display the filter
coefficients.  Furthermore, note that in the ‘display’ method it makes
sense to start the method with the line ‘printf ("%s =", inputname (1))’
to be consistent with the rest of Octave which prints the variable name
to be displayed followed by the value.  In general it is not recommended
to overload the ‘display’ function.

 -- : display (OBJ)
     Display the contents of the object OBJ prepended by its name.

     The Octave interpreter calls the ‘display’ function whenever it
     needs to present a class on-screen.  Typically, this would be a
     statement which does not end in a semicolon to suppress output.
     For example:

          myclass (...)

     Or:

          myobj = myclass (...)

     In general, user-defined classes should overload the ‘disp’ method
     to avoid the default output:

          myobj = myclass (...)
            ⇒ myobj =

            <class myclass>

     When overloading the ‘display’ method instead, one has to take care
     of properly displaying the object’s name.  This can be done by
     using the ‘inputname’ function.

     See also: Note: disp, Note: class, Note:
     subsref, Note: subsasgn.

   Once a constructor and display method exist, it is possible to create
an instance of the class.  It is also possible to check the class type
and examine the underlying structure.

     octave:1> f = FIRfilter (polynomial ([1 1 1]/3))
     f.polynomial = 0.33333 + 0.33333 * X + 0.33333 * X ^ 2
     octave:2> class (f)
     ans = FIRfilter
     octave:3> isa (f, "FIRfilter")
     ans =  1
     octave:4> isa (f, "polynomial")
     ans =  1
     octave:5> struct (f)
     ans =

       scalar structure containing the fields:

     polynomial = 0.33333 + 0.33333 * X + 0.33333 * X ^ 2

   The only thing remaining to make this class usable is a method for
processing data.  But before that, it is usually desirable to also have
a way of changing the data stored in a class.  Since the fields in the
underlying struct are private by default, it is necessary to provide a
mechanism to access the fields.  The ‘subsref’ method may be used for
both tasks.

     function r = subsref (f, x)
     
       switch (x.type)
     
         case "()"
           n = f.polynomial;
           r = filter (n.poly, 1, x.subs{1});
     
         case "."
           fld = x.subs;
           if (! strcmp (fld, "polynomial"))
             error ('@FIRfilter/subsref: invalid property "%s"', fld);
           endif
           r = f.polynomial;
     
         otherwise
           error ("@FIRfilter/subsref: invalid subscript type for FIR filter");
     
       endswitch
     
     endfunction

   The "()" case allows us to filter data using the polynomial provided
to the constructor.

     octave:2> f = FIRfilter (polynomial ([1 1 1]/3));
     octave:3> x = ones (5,1);
     octave:4> y = f(x)
     y =

        0.33333
        0.66667
        1.00000
        1.00000
        1.00000

   The "."  case allows us to view the contents of the polynomial field.

     octave:1> f = FIRfilter (polynomial ([1 1 1]/3));
     octave:2> f.polynomial
     ans = 0.33333 + 0.33333 * X + 0.33333 * X ^ 2

   In order to change the contents of the object a ‘subsasgn’ method is
needed.  For example, the following code makes the polynomial field
publicly writable

     function fout = subsasgn (f, index, val)
     
       switch (index.type)
         case "."
           fld = index.subs;
           if (! strcmp (fld, "polynomial"))
             error ('@FIRfilter/subsasgn: invalid property "%s"', fld);
           endif
           fout = f;
           fout.polynomial = val;
     
         otherwise
           error ("@FIRfilter/subsasgn: Invalid index type")
       endswitch
     
     endfunction

so that

     octave:1> f = FIRfilter ();
     octave:2> f.polynomial = polynomial ([1 2 3])
     f.polynomial = 1 + 2 * X + 3 * X ^ 2

   Defining the FIRfilter class as a child of the polynomial class
implies that a FIRfilter object may be used any place that a polynomial
object may be used.  This is not a normal use of a filter.  It may be a
more sensible design approach to use aggregation rather than
inheritance.  In this case, the polynomial is simply a field in the
class structure.  A class constructor for the aggregation case might be

     ## -*- texinfo -*-
     ## @deftypefn  {} {} FIRfilter ()
     ## @deftypefnx {} {} FIRfilter (@var{p})
     ## Create a FIR filter with polynomial @var{p} as coefficient vector.
     ## @end deftypefn
     
     function f = FIRfilter (p)
     
       if (nargin > 1)
         print_usage ();
       endif
     
       if (nargin == 0)
         f.polynomial = @polynomial ([1]);
       else
         if (! isa (p, "polynomial"))
           error ("@FIRfilter: P must be a polynomial object");
         endif
     
         f.polynomial = p;
       endif
     
       f = class (f, "FIRfilter");
     
     endfunction

   For this example only the constructor needs changing, and all other
class methods stay the same.


automatically generated by info2www version 1.2.2.9