Documentation Center

  • Trial Software
  • Product Updates

Supporting Both Handle and Value Subclasses

Basic Knowledge

The material presented in this section builds on knowledge of the following information.

Key Concepts

Handle-compatible class is a class that you can combine with handle classes when defining a set of superclasses.

  • All handle classes are handle-compatible.

  • All superclasses of handle-compatible classes must also be handle compatible.

HandleCompatible — the class attribute that defines nonhandle classes as handle compatible.

Handle Compatibility Rules

Handle-compatible classes (that is, classes whose HandleCompatible attribute is set to true) follow these rules:

  • All superclasses of a handle-compatible class must also be handle compatible

  • If a class explicitly sets its HandleCompatibility attribute to false, then none of the class's superclasses can be handle classes.

  • If a class does not explicitly set its HandleCompatible attribute and, if any superclass is a handle, then all superclasses must be handle compatible.

  • The HandleCompatible attribute is not inherited.

A class that does not explicitly set its HandleCompatible attribute to true is:

  • A handle class if any of its superclasses are handle classes

  • A value class if none of the superclasses are handle classes

Defining Handle-Compatible Classes

A class is handle compatible if:

  • It is a handle class

  • Its HandleCompatible attribute is set to true

The HandleCompatible class attribute identifies classes that you can combine with handle classes when specifying a set of superclasses.

Handle compatibility provides greater flexibility when defining abstract superclasses, such as mixin and interface classes, in cases where the superclass is designed to support both handle and value subclasses. Handle compatibility removes the need to define both a handle version and a nonhandle version of a class.

A Handle Compatible Class

For example, consider a class named Utility that defines functionality that is useful to both handle and value subclasses. In this example, the Utility class defines a method to reset property values to the default values defined in the respective class definition:

classdef (HandleCompatible) Utility
   methods
      function obj = resetDefaults(obj)
         % Reset properties to default and return object
         mc = metaclass(obj);  % Get meta.class object
         mp = mc.PropertyList; % Get meta.property objects
         for k=1:length(mp)
            % For each property, if there is a default defined,
            % set the property to that value
           if mp(k).HasDefault  && ~strcmp(mp(k).SetAccess,'private')
              obj.(mp(k).Name) = mp(k).DefaultValue;
           end
         end
      end
   end
end

The Utility class is handle compatible. Therefore, you can use it in the derivation of classes that are either handle classes or value classes. See Getting Information About Classes and Objects for information on using meta-data classes.

Return Modified Objects

The resetDefaults method defined by the Utility class returns the object it modifies, which is necessary when you call resetDefaults with a nonhandle object. It is important to implement methods that work with both handle and value objects in a handle compatible superclass. See Modifying Objects for more information on modifying handle and value objects.

Consider the behavior of a value class that subclasses the Utility class. The PropertyDefaults class defines three properties, all of which have default values:

classdef PropertyDefaults < Utility
   properties
      p1 = datestr(rem(now,1)); % Current time
      p2 = 'red';               % Character string
      p3 = pi/2;                % Result of division operation
   end
end

Create a PropertyDefaults object. MATLAB® evaluates the expressions assigned as default property values when the class is first loaded, and uses these same default values whenever you create an instance of this class in the current MATLAB session.

pd = PropertyDefaults with properties:

    p1: ' 4:54 PM'
    p2: 'red'
    p3:  1.5708

Assign new values that are different from the default values:

pd.p1 = datestr(rem(now,1));
pd.p2 = 'green';
pd.p3 = pi/4;

All pd object property values now contain values that are different from the default values originally defined by the class:

pd = 

  PropertyDefaults with properties:
:
    p1: ' 5:36 PM'
    p2: 'green'
    p3: 0.7854

Call the resetDefaults method, which is inherited from the Utility class. Because the PropertyDefaults class is not a handle class, you must return the modified object for reassignment in the calling function's workspace.

pd = pd.resetDefaults
pd = 

  PropertyDefaults with properties:

    p1: ' 4:54 PM'
    p2: 'red'
    p3: 1.5708

If the PropertyDefaults class was a handle class, then you would not need to save the object returned by the resetDefaults method. However, to design a handle compatible class like Utility, you need to ensure that all methods work with both kinds of classes.

Subclassing Handle-Compatible Classes

According to the rules described in Handle Compatibility Rules, when you combine a handle superclass with a handle-compatible superclass, the result is a handle subclass, which is handle compatible.

However, subclassing a handle-compatible class does not necessarily result in the subclass being handle compatible. Consider the following two cases, which demonstrate two possible results.

Combine Nonhandle Utility Class with Handle Classes

Suppose you define a class that subclasses a handle class, as well as the handle compatible Utility class discussed in A Handle Compatible Class. The HPropertyDefaults class has these characteristics:

  • It is a handle class (it derives from handle).

  • All of its superclasses are handle compatible (handle classes are handle compatible by definition).

classdef HPropertyDefaults < handle & Utility
   properties
      GraphPrim = line;
      Width = 1.5;
      Color = 'black';
   end
end

The HPropertyDefaults class is handle compatible:

hpd = HPropertyDefaults;

mc = metaclass(hpd);
mc.HandleCompatible
ans =

     1

Nonhandle Subclasses of a Handle-Compatible Class

If you subclass a value class that is not handle compatible in combination with a handle compatible class, the subclass is a nonhandle compatible value class. The ValueSub class:

  • Is a value class (It does not derive from handle.)

  • One of its superclasses is handle compatible (the Utility class).

classdef ValueSub < MException & Utility
   % ValueSub class is-a value class that is not
   % itself a handle-compatibile class
   methods
      function obj = ValueSub(str1,str2)
         obj = obj@MException(str1,str2);
      end
   end
end

The ValueSub class is a nonhandle-compatible value class because the MException class does not define the HandleCompatible attribute as true:

hv = ValueSub('MATLAB:narginchk:notEnoughInputs',...
      'Not enough input arguments.');
mc = metaclass(hv);
mc.HandleCompatible

ans =

     0

Methods for Handle Compatible Classes

Objects passed to methods of handle compatible classes can be either handle or value objects. There are two different behaviors to consider when implementing methods for a class that operate on both handles and values:

  • If an input object is a handle object, then the method can alter the handle object and these changes are visible to all workspaces that have the same handle.

  • If an input object is a value object, then changes to the object made inside the method affect only the value inside the method workspace.

Handle compatible methods generally do not alter input objects because the effect of such changes are not the same for handle and nonhandle objects.

See Modifying Objects for information about modifying handle and value objects.

Identifying Handle Objects

Use the isa function to determine if an object is a handle object:

isa(obj,'handle')

Modifying Value Objects in Methods

If a method operates on both handle and value objects, the method must return the modified object. For example, the TimeStamp property returns the object it modifies:

classdef (HandleCompatible) Util
   % Utility class that adds a time stamp
   properties
      TimeStamp
   end
   methods
      function obj = setTime(obj)
         % Return object after modification
         obj.TimeStamp = now;
      end
   end
end

Handle-Compatible Classes and Heterogeneous Arrays

A heterogeneous array contains objects of different classes. Members of a heterogeneous array have a common superclass, but might belong to different subclasses. See the matlab.mixin.Heterogeneous class for more information on heterogeneous arrays. The matlab.mixin.Heterogeneous class is a handle-compatible class.

Methods Must Be Sealed

You can invoke only those methods that are sealed by the common superclass on heterogeneous arrays (Sealed attribute set to true). Sealed methods prevent subclasses from overriding those methods and guarantee that methods called on heterogeneous arrays have the same definition for the entire array.

Subclasses cannot override sealed methods. In situations requiring subclasses to specialize methods defined by a utility class, you can employ the design pattern referred to as the template method.

Using the Template Technique

Suppose you need to implement a handle compatible class that is intended to work with heterogeneous arrays. The following approach enables you to seal public methods, while providing a way for each subclass to specialize how the method works on each subclass instance:

  • In the handle compatible class:

    • Define a sealed method that accepts a heterogeneous array as input.

    • Define a protected, abstract method that each subclass must implement.

    • Within the sealed method, call the overridden method for each array element.

  • Each subclass in the heterogeneous hierarchy implements a concrete version of the abstract method, which provides specialized behavior required by the particular subclass.

The Printable class shows how to implement a template method approach:

classdef (HandleCompatible) Printable
   methods(Sealed)
      function print(aryIn)
         % Print elements of a potentially 
         % heterogeneous array
         n = numel(aryIn);
         for k=1:n
            % Call subclass concrete implementation
            printElement(aryIn(k));
         end
      end
   end
   methods(Access=protected, Abstract)
      % Define protected, abstract method
      % Each subclass implements a concrete version
      printElement(objIn)
    end
end
Was this topic helpful?