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 (see class), 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 (see 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: disp, class, subsref, 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.

© 1996–2020 John W. Eaton
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions.
https://octave.org/doc/v6.3.0/Inheritance-and-Aggregation.html