Loren on the Art of MATLAB

Turn ideas into MATLAB

Note

Loren on the Art of MATLAB has been archived and will not be updated.

Subclasses in MATLAB

I'm pleased to have Dave Foti back for a discussion of subclassing and class hierarchies in MATLAB. Dave manages the group responsible for object-oriented programming features in MATLAB.

In computer science, a class represents a set of objects that share a common definition, usually including data and functions or behaviors. A subclass represents a subset of objects that share a more specific definition usually by adding or specializing data and/or functions defined in a superclass. In practice subclassing has been used to reuse functionality for a new and more specialized purpose. In recent years, this usage has become largely discouraged due to the tendency for introducing errors in programs that reuse superclasses (often called base classes) for applications not anticipated by the original class. A class author hides the inner workings of a class to make the class useful as an abstract representation. That same hiding of details can also hinder the ability of a programmer to anticipate how the class will function in a new role. Let's explore some cases where subclassing does make sense in MATLAB and how to avoid some common programming pitfalls.

Contents

Modeling Physical Systems

One of the most obvious uses for subclassing in MATLAB is a situation requiring a model of a real physical system that has already been classified. In such a situation, it might make sense to build a software model that closely resembles the real physical classification system. For example, one could create objects based on the Standard Model of particle physics [1]. In this way, the classification system already exists and can simply be represented in software. There is still design work to be done in how best to represent a physical system in software for a specific purpose, but all the classes can be built at roughly the same time by the same person or team and thus avoid the pitfalls of reusing an existing class later on after its details have been forgotten. Here is what a superclass might look like for elementary particles in the Standard Model:

type ElemParticle
classdef ElemParticle
    % Intrinsic properties of the particle
    properties(SetAccess = immutable)
        Spin
        Charge
        Mass
    end
    
    % State variables for a particular particle
    properties
        Position = [0 0 0];
        Velocity = [0 0 0];
    end
    
    methods
        function p = ElemParticle(spin, charge, mass)
            p.Spin = spin;
            p.Charge = charge;
            p.Mass = mass;
        end
    end
end

We can then create subclasses for fermions and bosons:

type Fermion
type Boson
classdef Fermion < ElemParticle
    methods 
        function p = Fermion(charge, mass)
            p@ElemParticle(.5, charge, mass);
        end
    end
end

classdef Boson < ElemParticle
    methods
        function p = Boson(mass)
            p@ElemParticle(0, 0, mass);
        end
    end
end

We can divide the Fermions into Leptons and Quarks:

type Lepton
type Quark
classdef Lepton < Fermion
    methods
        function p = Lepton(charge, mass)
            p@Fermion(charge, mass);
        end
    end
end

classdef Quark < Fermion
    properties
        Color
    end
    
    methods
        function p = Quark(charge, mass, color)
            p@Fermion(charge, mass);
            p.Color = color;
        end
    end
end

In the above example, the superclasses and subclasses were designed as a single collection of classes so there is no need for superclasses to anticipate the needs of unknown future subclasses. Any future evolution of the superclasses needs to consider the whole system, but it is a bounded list of classes.

Systems of Abstract Data Structures

Even if one is not modeling a physical system with an existing classification model that makes sense in software, there are still other examples where a system of classes can be designed at roughly the same time to fulfill some purpose such as managing a variety of data formats or anaysis techniques. For example, we might have a need to visualize some data as points. We might know that we have two kinds of data sets: one that is two-dimensional and one that is three-dimensional. We design a class that handles 2-D data and a sub-class that adds support for a third dimension. Here is what the 2-D class looks like:

type PointList
classdef PointList
    properties
        X
        Y
    end
    
    methods(Sealed)
        function show(pc)
            figure;
            data = getData(pc);
            opts = {'+', 'MarkerSize', 3};
            if numel(data) == 3
                plot3(data{:},opts{:});
            else
                plot(data{:},opts{:});
            end
        end
    end
    
    methods(Access = protected)
        function data = getData(pc)
            data = {pc.X, pc.Y};
        end
    end
end

Because we know that we want to support 3-D data, we design our plotting routine to be able to handle 2-D or 3-D data. This is an advantage of knowing about all the subclasses in the beginning. Then when we write the 3-D subclass:

type PointList3D
classdef PointList3D < PointList
    properties
        Z
    end
    
    methods(Access = protected)
        function data = getData(pc)
            data = {pc.X, pc.Y, pc.Z};
        end
    end
end

it is very easy to support 3-D plots without rewriting the whole show method. Here we produce a 3-D point cloud for the 3rd harmonic of the L-shaped membrane:

L = membrane(3);
[X, Y] = meshgrid(1:length(L), 1:length(L));
pts = PointList3D;
pts.Z = L(:);
pts.X = X(:);
pts.Y = Y(:);
show(pts)

Mixins

Another good reason to use subclassing is when a superclass is designed specifically for reuse. A common design pattern [2] is called the mixin pattern [3]. In this pattern a class is often used to implement one specialized piece of functionality. This class represents all objects that have this common functionality even though such classes may do other things very differently. A mixin class should expect nothing of a subclass except that it uses the mixin functionality. A common example of a mixin class in MATLAB is the class called matlab.mixin.CustomDisplay which is used to customize the MATLAB default object display while retaining appropriate consistency with other objects. Here is an example of this mixin being used on our ElemParticle superclass. To separate this version from the previous example, I've put the new version of the classes in a package called particle.

type +particle/ElemParticle
classdef ElemParticle < matlab.mixin.CustomDisplay
    % Intrinsic properties of the particle
    properties(SetAccess = immutable)
        Spin
        Charge
        Mass
    end
    
    % State variables for a particular particle
    properties
        Position = [0 0 0];
        Velocity = [0 0 0];
    end
    
    methods
        function p = ElemParticle(spin, charge, mass)
            p.Spin = spin;
            p.Charge = charge;
            p.Mass = mass;
        end
    end
    
    methods(Access = protected, Sealed)
        function propgroups = getPropertyGroups(p)
            propgroups = matlab.mixin.util.PropertyGroup;
            propgroups(1).Title = 'Intrinsic';
            propgroups(1).PropertyList = getIntrinsicPropertyNames(p);
            propgroups(2).Title = 'State';
            propgroups(2).PropertyList = {'Position','Velocity'};
        end
    end
    
    methods(Access = protected)
        function pnames = getIntrinsicPropertyNames(~)
            pnames = {'Spin','Charge','Mass'};
        end        
    end
end

Note that whenever functionality is designed for a superclass such as this, it is important to implement the functionality for the whole class of objects including all instances of subclasses. Thus, the display should not just show what is common to ElemParticle, but should provide for specialization in subclasses. In this case, we add one method that can be specialized by subclasses to provide lists of intrinsic properties. Since we know about all the subclasses, we don't need to worry about what kinds of property groups a future subclass might need. We can see why it is important for superclasses to be designed with the knowledge that subclasses will be created and why it is very helpful if the subclasses are already known during this design. In this case, we know the family of subclasses of ElemParticle and thus we know that they will sometimes need to add new properties, but not whole new groups of properties. We choose the right amount of generality in the superclass so that each subclass can specialize display with minimal effort. In fact Quark is the only particle that needs additional code as shown here:

type +particle/Quark
classdef Quark < particle.Fermion
    properties
        Color
    end
    
    methods
        function p = Quark(charge, mass, color)
            p@particle.Fermion(charge, mass);
            p.Color = color;
        end
    end
    
    methods(Access = protected)
        function pnames = getIntrinsicPropertyNames(p)
            pnames = [getIntrinsicPropertyNames@particle.Fermion(p), 'Color'];
        end
    end
end

We can now create and display an electron and a top quark:

e = particle.Lepton(-1, 5.11e-4)
e = 
  Lepton with properties:

   Intrinsic
      Spin: 0.5
    Charge: -1
      Mass: 0.000511
   State
    Position: [0 0 0]
    Velocity: [0 0 0]
t = particle.Quark(2/3, 173.07, 'red')
t = 
  Quark with properties:

   Intrinsic
      Spin: 0.5
    Charge: 0.66667
      Mass: 173.07
     Color: 'red'
   State
    Position: [0 0 0]
    Velocity: [0 0 0]

Other Patterns

Other common patterns where classes are designed for unbounded subclassing include interfaces [4] and frameworks [5]. Interface classes define how two pieces of code can interact with each other without actually defining how those interactions are accomplished. Because MATLAB is a dynamically typed language, it is possible to write functions to an interface that isn't actually defined as a class. For example many functions in MATLAB can work with any object that implements the size, subsref, and subsasgn functions to perform reshaping operations on arrays. In some cases, it may be useful to define interfaces explicitly so that it is easier to determine what kinds of objects will work with a particular function and so that it is easier to see the capabilities of a class in the explicit interfaces it supports.

Design Considerations

When designing a class for extension in the future, it is a good idea to think about how subclasses might restrict evolution of the base class. Some questions to consider are:

  • Is it clear that there will be no need to add any properties or methods to the base class in the future?
  • Is it clear that only internal implementation details will need to change over time and not the fundamental meaning and behavior of the class?

When designing methods in the base class, it is important to consider how subclasses will reuse and as necessary adapt these methods while maintaining the principle that subclass instances can be substituted for base class instances [6]. Some questions to ask are:

  • What methods might need to be specialized?
  • How can base class methods be best organized to allow subclasses to reuse as much base class implementation as possible?
  • Are there ways to better ensure that subclasses still behave consistently with the intended behavior of the class?

Once a class has been presented as a base class, its fundamental definition should not change or else subclasses could become invalid or produce erroneous results. The new or changed behaviors and capabilities may not be appropriate for all subclasses. The subclass authors could not possibly anticipate changes to the superclass when the subclasses were created. This is one of the reasons for adhering to the open/closed design principle [7].

Preventing Unexpected Subclassing

Given the importance of considering how a class will be extended by subclasses, MATLAB provides ways to signal that a class wasn't designed for subclassing. Classes with the Sealed attribute set can't be used as superclasses. If an entire class hierarchy was designed together but not intended for extension with additional subclasses, then it can be sealed using the Sealed attribute on the leaf classes and the AllowedSubclasses attribute on the superclasses. MathWorks will at times seal classes or class hierarchies of widely used classes to indicate that these classes have not been designed to support subclasses.

These are some thoughts on subclassing in MATLAB, but I'd like to hear your thoughts as well. Have you used any of the mixin MATLAB classes like matlab.mixin.CustomDisplay or matlab.mixin.Heterogeneous? Do you have suggestions for mixin classes that you would like to see in MATLAB?

References




Published with MATLAB® R2014b


  • print

评论

要发表评论,请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。