Main Content

Maintain Class Compatibility

Rename Property

Suppose that you want to rename a property, but do not want to cause errors in existing code that refer to the original property. For example, rename a public property called OfficeNumber to Location. Here is the original class definition:

classdef EmployeeList
   properties
      Name
      Email
      OfficeNumber % Rename as Location
   end
end

Use of a hidden dependent property can achieve the desired results.

  • In the class definition, set the OfficeNumber property attributes to Dependent and Hidden.

  • Create a property set method for OfficeNumber that sets the value of the Location property.

  • Create a property get method for OfficeNumber that returns the value of the Location property.

While the OfficeNumber property is hidden, existing code can continue to access this property. The Hidden attribute does not affect access.

Because OfficeNumber is dependent, there is no redundancy in storage required by adding the new property. MATLAB® does not store or save dependent properties.

Here is the updated class definition.

classdef EmployeeList
   properties
      Name
      Email
      Location
   end
   properties (Dependent, Hidden)
      OfficeNumber
   end
   methods
      function obj = set.OfficeNumber(obj,val)
         obj.Location = val;
      end
      function val = get.OfficeNumber(obj)
         val = obj.Location;    
      end
   end
end

Saving and Loading EmployeeList Objects

You can load old instances of the EmployeeList class in the presence of the new class version. Code that refers to the OfficeNumber property continues to work.

Forward and Backward Compatibility

Suppose that you want to be able to load new EmployeeList objects into systems that still have the old version of the EmployeeList class. To achieve compatibility with old and new versions:

  • Define the OfficeNumber property as Hidden, but not Dependent.

  • Define the Location property as Dependent.

In this version of the EmployeeList class, the OfficeNumber property saves the value used by the Location property. Loading an object assigns values of the three original properties (Name, Email, and OfficeNumber), but does not assign a value to the new Location property. The lack of the Location property in the old class definition is not a problem.

classdef EmployeeList
   properties
      Name
      Email
   end
   properties (Dependent)
      Location
   end
   properties (Hidden)
      OfficeNumber
   end
   methods
      function obj = set.Location(obj,val)
         obj.OfficeNumber = val;
      end
      function val = get.Location(obj)
         val = obj.OfficeNumber;
      end
   end
end

Update Property When Loading

Suppose that you modify a class so that a property value changes in its form or type. Previously saved objects of the class must be updated when loaded to have a conforming property value.

Consider a class that has an AccountID property. Suppose that all account numbers must migrate from eight-digit numeric values to 12-element character arrays.

You can accommodate this change by implementing a loadobj method.

The loadobj method:

  • Tests to determine if the load function passed a struct or object. All loadobj methods must handle both struct and object when there is an error in load.

  • Tests to determine if the AccountID number contains eight digits. If so, change it to a 12-element character array by calling the padAccID method.

After updating the AccountID property, loadobj returns a MyAccount object that MATLAB loads into the workspace.

classdef MyAccount
   properties
      AccountID
   end
   methods
      function obj = padAccID(obj)
         ac = obj.AccountID;
         acstr = num2str(ac);
         if length(acstr) < 12
            obj.AccountID = [acstr,repmat('0',1,12-length(acstr))];
         end
      end
   end
   methods (Static)
      function obj = loadobj(a)
         if isstruct(a)
            obj = MyAccount;
            obj.AccountID = a.AccountID;
            obj = padAccID(obj);
         elseif isa(a,'MyAccount')
            obj = padAccID(a);
         end
      end
   end
end

You do not need to implement a saveobj method. You are using loadobj only to ensure that older saved objects are brought up to date while loading.

Maintaining Compatible Versions of a Class

The PhoneBookEntry class uses a combination of techniques to maintain compatibility with new versions of the class.

Suppose that you define a class to represent an entry in a phone book. The PhoneBookEntry class defines three properties: Name, Address, and PhoneNumber.

classdef PhoneBookEntry
   properties
      Name
      Address
      PhoneNumber
   end
end

However, in future releases, the class adds more properties. To provide flexibility, PhoneBookEntry saves property data in a struct using its saveobj method.

methods
   function s = saveobj(obj)
      s.Name = obj.Name;
      s.Address = obj.Address;
      s.PhoneNumber = obj.PhoneNumber;
   end
end

The loadobj method creates the PhoneBookEntry object, which is then loaded into the workspace.

methods (Static)
   function obj = loadobj(s)
      if isstruct(s)
         newObj = PhoneBookEntry;
         newObj.Name = s.Name;
         newObj.Address = s.Address;
         newObj.PhoneNumber = s.PhoneNumber;
         obj = newObj;
      else
         obj = s;
      end
   end
end

Version 2 of the PhoneBookEntry Class

In version 2 of the PhoneBookEntry class, you split the Address property into StreetAddress, City, State, and ZipCode properties.

With these changes, you could not load a version 2 object in a previous release. However, version 2 employs several techniques to enable compatibility:

  • Preserve the Address property (which is used in version 1) as a Dependent property with private SetAccess.

  • Define an Address property get method (get.Address) to build a char vector that is compatible with the version 2 Address property.

  • The saveobj method invokes the get.Address method to assign the object data to a struct that is compatible with previous versions. The struct continues to have only an Address field built from the data in the new StreetAddress, City, State, and ZipCode properties.

  • When the loadobj method sets the Address property, it invokes the property set method (set.Address), which extracts the substrings required by the StreetAddress, City, State, and ZipCode properties.

  • The Transient (not saved) property SaveInOldFormat enables you to specify whether to save the version 2 object as a struct or an object.

classdef PhoneBookEntry
   properties
      Name
      StreetAddress
      City
      State
      ZipCode
      PhoneNumber
   end
   properties (Constant)
      Sep = ', '
   end
   properties (Dependent, SetAccess=private)
      Address
   end
   properties (Transient)
      SaveInOldFormat = false;
   end
   methods (Static)
      function obj = loadobj(s)
         if isstruct(s)
            obj = PhoneBookEntry;
            obj.Name = s.Name;
            obj.Address = s.Address;
            obj.PhoneNumber = s.PhoneNumber;
         else
            obj = s;
         end
      end
   end
   methods
      function address = get.Address(obj)
         address = [obj.StreetAddress,obj.Sep,obj.City,obj.Sep,...
            obj.State,obj.Sep,obj.ZipCode];
      end
      function obj = set.Address(obj,address)
         addressItems = regexp(address,obj.Sep,'split');
         if length(addressItems) == 4
            obj.StreetAddress = addressItems{1};
            obj.City = addressItems{2};
            obj.State = addressItems{3};
            obj.ZipCode = addressItems{4};
         else
            error('PhoneBookEntry:InvalidAddressFormat', ...
               'Invalid address format.');
         end
      end
      function s = saveobj(obj)
         if obj.SaveInOldFormat
            s.Name = obj.Name;
            s.Address = obj.Address;
            s.PhoneNumber = obj.PhoneNumber;
         end
      end
   end
end

Related Topics