Abstract Classes and Interfaces

Abstract Classes

An abstract class serves as a basis (i.e., a superclass) for a group of related subclasses. It forms the abstractions that are common to all subclasses by specifying the common properties and methods that all subclasses must implement. However, the abstract class cannot itself be instantiated. Only the subclasses can be instantiated. These subclasses are sometimes referred to as concrete classes.

Abstract classes are useful for describing functionality that is common to a group of classes, but requires unique implementations within each class. This approach is often called an interface because the abstract class defines the interface of each subclass without specifying the actual implementation.

Defining Abstract Classes

Define an abstract class by setting the Abstract attribute on one or more methods of a class to true. You do not use a function...end block to define an abstract method, use only the method signature.

For example, the group abstract class defines two methods that take two input arguments and return a result:

classdef group
% Both methods must be implemented so that
% the operations are communtative
   methods (Abstract)
      rsult = add(numeric,polynom)
      rsult = times(numeric,polynom)
   end
end

The subclasses must implement methods with the same names. The names and number of arguments can be different. However, the abstract class typically conveys details about the expected implementation via its comments and argument naming.

Abstract Properties

For properties that have Abstract attributes set to true:

Abstract Methods

For methods that have Abstract attributes set to true:

Interfaces and Abstract Classes

The properties and methods defined by a class form the interface that determines how users interact with objects of the class. When you are creating a group of related classes, it makes sense to define a common interface to all these classes, even though the actual implementations of this interface might differ from one class to another. Abstract properties and methods provide a mechanism to create interfaces for groups of classes.

For example, consider a set of classes designed to represent a variety of graphs (e.g., line plots, bar graphs, pie charts, and so on). Suppose all classes need to implement a Data property to contain the data used to generate the graph. However, the form of the data might differ considerably from one type of graph to another. Consequently, the way each class implements the Data property needs to be different.

The same differences apply to methods. All classes might have a draw method that creates the graph, but the implementation of this method changes with the type of graph.

The basic idea of an interface class is to specify the properties and methods that each subclass must implement without defining the actual implementation. This enables you to enforce a consistent interface to a group of related objects. As more classes are added in the future, the interface is maintained.

Example — Interface for Classes Implementing Graphs

This example creates an interface for classes used to display specialized graphs. The interface is an abstract class that defines properties and methods that the subclasses need to implement, but does not specify how to implement these components. This enforces the use of a consistent interface while at the same time provides the necessary flexibility to implement the internal workings of each specialized graph subclass differently.

In this example, the interface, derived subclasses, and a utility function are contained in a package directory:

+graphics/graph.m      % abstract interface class
+graphics/linegraph.m  % concrete subclass
+graphics/addButtons.m % static method of graph class

Interface Properties and Methods

The graph class specifies the following properties to be defined by subclasses:

The graph class names three abstract methods that subclasses need to implement. The graph class also suggests in comments that each subclass constructor needs to accept the data to be plotted and property name/property value pairs for all class properties.

Interface Guides Class Design

The package of classes that derive from the graph abstract class implement the following behaviors:

Defining the Interface

The graph class is an abstract class that defines the methods and properties used by the derived classes. Comments in the abstract class suggest the intended implementation:

classdef graph < handle
% Abstract class for creating data graphs
% Subclass constructor should accept
% the data that is to be plotted and
% property name/property value pairs 
   properties (SetAccess = protected, GetAccess = protected)
      Primitive % HG primitive handle
      AxesHandle % Axes handle 
   end
   properties % Public access
      Data
   end
   methods (Abstract)
      draw(obj)
      % Use a line, surface,
      % or patch HG primitive 
      zoom(obj,factor)
      % Change the CameraViewAngle
      % for 2D and 3D views 
      % use camzoom for consistency 
      updateGraph(obj)
      % Called by the set.Data method
      % to update the drawing primitive
      % whenever the Data property is changed 
   end
   methods
      function set.Data(obj,newdata)
         obj.Data = newdata;
         updateGraph(obj)
      end
   end
   methods (Static)
      addButtons(obj)
   end
end

The graph class implements the property set method (set.Data) to monitor changes to the Data property. An alternative is to define the Data property as Abstract and enable the subclasses to determine whether to implement a set access method for this property. However, by defining the set access method that calls an abstract method (updateGraph, which must be implemented by each subclass), the graph interface more effectively imposes a specific design on the whole package of classes, without limiting flexibility.

Static Method to Work with All Subclasses

The addButtons method simply adds push buttons for the zoom methods that each subclass must implement. Using a static method instead of an ordinary function enables the addButtons method to access the protected class data (the axes handle). Notice how the particular object's zoom method is defined as the buttons' callback.

function addButtons(gobj)
   hfig = get(gobj.AxesHandle,'Parent');
   uicontrol(hfig,'Style','pushbutton','String','Zoom Out',...
      'Callback',@(src,evnt)zoom(gobj,.5));
   uicontrol(hfig,'Style','pushbutton','String','Zoom In',...
      'Callback',@(src,evnt)zoom(gobj,2),...
      'Position',[100 20 60 20]);
end 

Deriving a Concrete Class — linegraph

This example defines only a single subclass used to represent a simple line graph. It derives from graph, but provides implementations for the abstract methods draw, zoom, updateGraph, and its own constructor. The base class (graph) and subclass are all contained in a package (graphics), which must be used to reference the class name:

classdef linegraph < graphics.graph

Adding Properties

The linegraph class implements the interface defined in the graph class and adds two additional properties—LineColor and LineType. This class defines initial values for each property in case a linegraph object is created without specifying these properties (which might happen when reloading an object from a MAT-file). Specifying property values in the constructor is optional. Data is required to produce a plot, but not to create a linegraph object):

properties
   LineColor = [0 0 0];
   LineType = '-';
end

The linegraph Constructor

The constructor accepts a struct with x and y coordinate data, as well as property name/property value pairs:

function gobj = linegraph(data,varargin)
   if nargin > 0
      gobj.Data = data;
      if nargin > 2
         for k=1:2:length(varargin)
            gobj.(varargin{k}) = varargin{k+1};
         end
      end
   end
end

Implementing the draw Method

The linegraph draw method uses property values to create a line object. The line handle is stored as protected class data. The linegraph class does not create the line primitive and display its plot until the draw method is called. To support the use of no input arguments for the constructor, draw checks the Data property to determine if it is empty before proceeding:

function gobj = draw(gobj)
   if isempty(gobj.Data)
      error('The linegraph object contains no data')
   end
   h = line(gobj.Data.x,gobj.Data.y,...
      'Color',gobj.LineColor,...
      'LineStyle',gobj.LineType);
    gobj.Primitive = h;
    gobj.AxesHandle = get(h,'Parent');
end

Implementing the zoom Method

The linegraph zoom method follows the directions in the graph class and uses the camzoom function, which provides a convenient interface to zooming and operates correctly with the push buttons created by the addButtons method.

Defining the Property Set Methods

Property set methods provide a convenient way to execute code automatically when the value of a property is changed or assigned for the first time in a constructor (see Property Set Methods). The linegraph class uses set methods to update the line primitive data (which causes a redraw of the plot) whenever a property value changes. The use of property set methods provides a way to update the data plot quickly without requiring a call to the draw method, which updates the plot by resetting all values to match the current property values.

Three properties use set methods LineColor, LineType, and Data. LineColor and LineType are properties added by the linegraph class and are specific to the line primitive used by this class. Other subclasses might define different properties unique to their specialization (e.g., FaceColor).

The Data property set method is implemented in the graph class. However, the graph class requires each subclass to define a method called updateGraph, which handles the update of plot data for the specific drawing primitive used.

Using the linegraph Class

The linegraph class defines the simple API specified by the graph base class and implements its specialized type of graph:

d.x = 1:10;
d.y = rand(10,1);
lg = graphics.linegraph(d,'LineColor','b','LineType',':');
lg.draw;
graphics.graph.addButtons(lg);

Clicking the Zoom In button shows the zoom method providing the callback for the button.

Changing properties updates the graph:

d.y = rand(10,1); % new set of random data for y
lg.Data = d;
lg.LineColor = [.9 .1 .6]; % LineColor can be char or double

Now click Zoom Out and see the new results:

  


 © 1984-2008- The MathWorks, Inc.    -   Site Help   -   Patents   -   Trademarks   -   Privacy Policy   -   Preventing Piracy   -   RSS