Object-Oriented Programming with classdef

MATLAB classes are defined in a classdef file with the class name:

Concept

MATLAB classes are defined in a classdef file with the class name:

Properties with default (MATLAB also allows a type: Name char = '', not supported by Octave)

properties (SetAccess = private), (Access = protected) Dependent (computed) properties

properties (Dependent) + function v = get.Prop(obj) obj.method(args) or method(obj, args)

methods (Static) — called as ClassName.method() classdef BatteryEcu < Ecu; call the superclass constructor with obj@Ecu(...) classdef CanLog < handle Events, listeners, enumerations

events, addlistener, enumeration (MATLAB) Value vs handle classes — the key decision:

A value class behaves like numbers: assignment copies; a method that changes the object must return the modified copy (e2 = e.flash('2.5.0')).

A handle class behaves like a reference: all variables refer to the same object; methods modify it in place (instruments, connections, logs, GUIs).

classdef Ecu
    properties              % data
    end
    methods                 % functions that operate on objects
        function obj = Ecu(...)   % constructor
        end
    end
end

Why It Matters

Larger MATLAB tools — test frameworks, instrument drivers, data pipelines, apps — are easier to maintain as classes. MATLAB's own graphics and many toolboxes are object-oriented.

Syntax

e = Ecu('VCU', 2016, '2.4.1');
e = e.flash('2.5.0');                    % value class: reassign
log = CanLog(); log.add(id, data);       % handle class: in place

Example

28.1 A value class, a subclass and a handle class

classdef Ecu
%ECU A value class describing an electronic control unit.
    properties
        Name = ''            % MATLAB also allows a type: Name char = ''
        Address = 0
        Firmware = '0.0.0'
    end
    properties (Dependent)
        AddressHex
    end
    methods
        function obj = Ecu(name, address, firmware)
            if nargin > 0
                obj.Name = name;
                obj.Address = address;
                obj.Firmware = firmware;
            end
        end
        function h = get.AddressHex(obj)
            h = sprintf('0x%X', obj.Address);
        end
        function obj = flash(obj, newVersion)
            % value class: returns a MODIFIED COPY
            obj.Firmware = newVersion;
        end
        function tf = isNewerThan(obj, version)
            a = sscanf(obj.Firmware, '%d.%d.%d')';
            b = sscanf(version, '%d.%d.%d')';
            d = find(a ~= b, 1);
            tf = ~isempty(d) && a(d) > b(d);
        end
    end
    methods (Static)
        function e = fromString(s)
            p = strsplit(s, ':');
            e = Ecu(p{1}, hex2dec(p{2}), p{3});
        end
    end
end
classdef BatteryEcu < Ecu
%BATTERYECU Inheritance: an ECU that also knows its cell voltages.
    properties
        CellVoltages = []
    end
    methods
        function obj = BatteryEcu(name, address, firmware, cells)
            obj@Ecu(name, address, firmware);      % call the superclass constructor
            obj.CellVoltages = cells;
        end
        function v = packVoltage(obj)
            v = sum(obj.CellVoltages);
        end
        function tf = isBalanced(obj, tolerance)
            tf = (max(obj.CellVoltages) - min(obj.CellVoltages)) <= tolerance;
        end
    end
end
classdef CanLog < handle
%CANLOG A handle class: every variable refers to the SAME object.
    properties (SetAccess = private)
        Frames = {}
    end
    methods
        function add(obj, id, data)
            obj.Frames{end + 1} = struct('id', id, 'data', data);
        end
        function n = count(obj)
            n = numel(obj.Frames);
        end
    end
end
e = Ecu('VCU', hex2dec('7E0'), '2.4.1');
fprintf('%s at %s runs %s\n', e.Name, e.AddressHex, e.Firmware)
e2 = e.flash('2.5.0');                        % value class: a new object
fprintf('after flash: original %s, copy %s\n', e.Firmware, e2.Firmware)
fprintf('2.5.0 newer than 2.4.9: %d, 2.4.1 newer than 2.10.0: %d\n', e2.isNewerThan('2.4.9'), e.isNewerThan('2.10.0'))
g = Ecu.fromString('GW:710:3.2.0');           % static factory method
fprintf('fromString: %s %s %s\n', g.Name, g.AddressHex, g.Firmware)

b = BatteryEcu('BMS', hex2dec('7E4'), '1.9.0', [3.61 3.62 3.60 3.65]);
fprintf('%s pack voltage %.2f V, balanced within 30 mV: %d, isa Ecu: %d\n', b.Name, b.packVoltage(), b.isBalanced(0.03), isa(b, 'Ecu'))

log1 = CanLog();
log2 = log1;                                  % handle: both names refer to one object
log1.add(hex2dec('7E0'), [2 16 3]);
log2.add(hex2dec('7E8'), [6 80 3]);
fprintf('handle class: log1 has %d frames, log2 has %d frames\n', log1.count(), log2.count())
try
    log1.Frames = {};
catch err
    fprintf('SetAccess = private blocks outside writes: %s\n', err.message)
end
fprintf('class(e) = %s, ismethod(e, ''flash'') = %d, properties: %s\n', class(e), ismethod(e, 'fl
…

Expected Result

flash on the value class returned a new object; the original kept firmware 2.4.1.

The version comparison treats "2.10.0" as newer than "2.4.1" — numeric, not text, comparison.

BatteryEcu inherited everything from Ecu and added pack-level methods; isa confirms the relationship.

The two CanLog variables refer to one object: both report 2 frames.

SetAccess = private prevented modifying Frames from outside the class (the exact error wording differs between MATLAB and Octave).

VCU at 0x7E0 runs 2.4.1
after flash: original 2.4.1, copy 2.5.0
2.5.0 newer than 2.4.9: 1, 2.4.1 newer than 2.10.0: 0
fromString: GW 0x710 3.2.0
BMS pack voltage 14.48 V, balanced within 30 mV: 0, isa Ecu: 1
handle class: log1 has 2 frames, log2 has 2 frames
SetAccess = private blocks outside writes: subsasgn: property 'Frames' has private access and cannot be set in this context
class(e) = Ecu, ismethod(e, 'flash') = 1, properties: Name, Address, Firmware, AddressHex

Common Mistakes

Calling a value-class method that "modifies" the object without assigning the result.

Using a handle class where copies were expected (or vice versa).

Forgetting to call the superclass constructor in a subclass.

Putting a class file in a folder not on the path, or naming the file differently from the class.

Best Practices

Default to value classes for data; use handle classes for resources with identity (files, instruments, connections).

Validate property types in the properties block.

Keep methods small; use static factory methods for alternative constructors.

Interview Relevance

Medium: "value vs handle class?", "how do you define a class in MATLAB?", "what is a dependent property?", "how does inheritance work?".

Practice Questions

Add a disp method to Ecu that prints a one-line summary.

Create a Sensor handle class with calibrate(gain, offset) and read(raw) methods.

Add a protected property to Ecu and show that a subclass can use it.

Write a static method Ecu.compareVersions(a, b) returning −1, 0 or 1.

Quick Quiz

Q1. (Multiple choice) After log2 = log1 for a handle object, log2.add(...) affects: A) Only log2 B) Both, because they refer to the same object C) Neither D) It errors

Answer: B (verified: both show 2 frames).

Q2. (Predict the output) After e2 = e.flash('2.5.0') on a value class, what is e.Firmware?

Answer: Still 2.4.1 (verified).

Q3. (True/False) A subclass must call its superclass constructor explicitly if the superclass constructor needs arguments.

Answer: True (obj@Ecu(...) in the example).

Q4. (Find the bug) e.flash('3.0.0'); disp(e.Firmware) still shows the old version.

Answer: Ecu is a value class: assign the result, e = e.flash('3.0.0');.

Coding Challenge

Write a handle class Counter with a method increment() and a Count property readable but not settable from outside; show that two variables referring to the same counter see the same count.

% Counter.m
classdef Counter < handle
    properties (SetAccess = private)
        Count = 0
    end
    methods
        function increment(obj)
            obj.Count = obj.Count + 1;
        end
    end
end
% script
a = Counter();
b = a;
a.increment(); b.increment(); b.increment();
fprintf('a.Count = %d, b.Count = %d\n', a.Count, b.Count)