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
endWhy 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 placeExample
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
endclassdef 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
endclassdef 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
ende = 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, AddressHexCommon 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)