|On this page…|
The material presented in this section builds on knowledge of the following information.
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-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
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.
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.
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.
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.
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
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
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.
Use the isa function to determine if an object is a handle object:
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
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.
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.
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