octave: Inheritance and Aggregation

 
 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.
 
      octave:1> x = [some data vector];
      octave:2> n = [some coefficient vector];
      octave:3> 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 (Seeclass
 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.
 
    As before, a class requires a ‘display’ method.  A simple example
 might be
 
      function display (f)
        printf ("%s.polynomial", inputname (1));
        display (f.polynomial);
      endfunction
 
    Note that the FIRfilter’s display method relies on the display method
 from the polynomial class to actually display the filter coefficients.
 
    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.