01 · Object-Oriented Programming in MATLAB¶
Verification note
MATLAB was not available in the environment used to write this page.
classdef syntax and semantics (properties, methods, handle vs.
value classes, inheritance) are documented, version-stable MATLAB
language features, hand-traced against the MATLAB OOP documentation
rather than executed in MATLAB itself.
Everything through Level 2 used functions and structs to organize data
and behavior separately. classdef lets you bundle data (properties)
and the operations on that data (methods) into one reusable
definition — a class — and create as many independent instances
(objects) of it as needed.
A minimal class¶
classdef BankAccount
properties
Balance = 0
Owner = ""
end
methods
function obj = BankAccount(owner, initialBalance)
if nargin > 0
obj.Owner = owner;
obj.Balance = initialBalance;
end
end
function obj = deposit(obj, amount)
if amount <= 0
error('BankAccount:invalidAmount', 'Deposit amount must be positive.');
end
obj.Balance = obj.Balance + amount;
end
function obj = withdraw(obj, amount)
if amount > obj.Balance
error('BankAccount:insufficientFunds', 'Cannot withdraw %.2f from balance %.2f.', amount, obj.Balance);
end
obj.Balance = obj.Balance - amount;
end
end
end
A classdef file (BankAccount.m, filename must match the class name)
declares properties blocks for data fields and methods blocks for
functions that operate on instances. The method with the same name as
the class (BankAccount(...)) is the constructor — called
automatically by BankAccount(...) to create a new object.
acc = BankAccount("Priya", 100);
acc = acc.deposit(50); % acc.Balance is now 150
acc = acc.withdraw(30); % acc.Balance is now 120
disp(acc.Balance); % 120
Value classes vs. handle classes — the critical distinction¶
Notice deposit and withdraw both return obj and the caller
reassigns acc = acc.deposit(50). This is because a plain classdef ...
end (no superclass) defines a value class — like a double or a
struct, every object is copied on assignment and passed to functions by
value. A method that mutates obj.Balance internally only changes its
local copy; without obj = acc.deposit(50) reassigning the result, the
caller's acc would be unchanged.
acc.deposit(50); % WRONG for a value class: return value discarded, acc unchanged
acc = acc.deposit(50); % correct: acc now points to the updated copy
Handle classes (classdef BankAccount < handle) behave like
references/pointers instead — every variable referring to the same
object shares the same underlying data, and methods mutate it in place
without needing to return and reassign:
classdef BankAccountH < handle
properties
Balance = 0
end
methods
function deposit(obj, amount)
obj.Balance = obj.Balance + amount; % mutates the shared object directly
end
end
end
acc = BankAccountH;
acc.deposit(50); % no reassignment needed — acc.Balance is 50
acc2 = acc; % acc2 is the SAME object, not a copy
acc2.deposit(25);
disp(acc.Balance); % 75 — acc "sees" acc2's change, since they're the same object
Choosing wrong is a common source of confusing bugs: a value class where
you expected shared mutable state (methods silently "not working" because
you forgot to reassign), or a handle class where you expected an
independent copy (acc2 = acc unexpectedly aliasing, so mutating one
mutates both). Rule of thumb: model things with independent identity
that change over time and get passed around (a GUI control, a live
device connection, a growing log) as handle classes; model pure data
values (a coordinate, a measurement, an immutable configuration) as
value classes.
Property validation, same idiom as function arguments¶
classdef Rectangle
properties
Width (1,1) double {mustBePositive} = 1
Height (1,1) double {mustBePositive} = 1
end
methods
function a = area(obj)
a = obj.Width * obj.Height;
end
function p = perimeter(obj)
p = 2*(obj.Width + obj.Height);
end
end
end
Rectangle here reuses the same (size) type {validators} syntax from
Level 2's arguments blocks — properties blocks support it directly,
so r.Width = -5 throws a validation error immediately rather than
letting a negative width silently corrupt later area() calls.
Inheritance¶
classdef Square < Rectangle
methods
function obj = Square(side)
obj@Rectangle(); % call the superclass constructor
obj.Width = side;
obj.Height = side;
end
end
end
Square inherits area() and perimeter() from Rectangle without
redefining them — the whole point of inheritance is that behavior common
to both stays defined in one place. obj@Rectangle() explicitly invokes
the superclass constructor, required whenever the superclass constructor
takes arguments or needs to run before the subclass's own initialization.
Overriding a method¶
classdef Circle
properties
Radius (1,1) double {mustBePositive} = 1
end
methods
function a = area(obj)
a = pi * obj.Radius^2;
end
end
end
Circle isn't a subclass of Rectangle (a circle isn't a kind of
rectangle) but defines its own area() with the same name —
polymorphism: code that calls .area() on any shape object gets the
right computation without needing to know or check which concrete class
it's holding:
shapes = {Rectangle(3,4), Square(5), Circle(2)};
for i = 1:numel(shapes)
fprintf('Area: %.2f\n', shapes{i}.area());
end
% Area: 12.00
% Area: 25.00
% Area: 12.57
Static-like behavior: Constant properties and methods without obj¶
classdef MathConstants
properties (Constant)
Pi = 3.14159265358979
E = 2.71828182845905
end
methods (Static)
function r = degToRad(deg)
r = deg * MathConstants.Pi / 180;
end
end
end
Constant properties belong to the class itself, not any instance —
MathConstants.Pi works without ever constructing a MathConstants
object. Static methods likewise don't take obj as their first
argument and are called as MathConstants.degToRad(90) — the MATLAB
analog of a "utility function namespaced under a class" pattern common in
other object-oriented languages.
properties (Access = private) — encapsulation¶
classdef Counter < handle
properties (Access = private)
Count = 0
end
methods
function increment(obj)
obj.Count = obj.Count + 1;
end
function n = getCount(obj)
n = obj.Count;
end
end
end
Access = private restricts reading/writing Count to code inside the
class's own methods — external code must go through getCount(), so the
class controls exactly how its internal state can be modified.
c.Count from outside errors with an access-restriction message rather
than silently succeeding, which is the point: it forces callers through
the class's intended interface instead of reaching into internals that
might change in a future version of the class.
When to reach for classdef vs. a struct + functions¶
Use classdef when... |
Use a struct + plain functions when... |
|---|---|
| The data has behavior tightly coupled to it (validation, invariants to maintain) | Data is a simple, mostly-passive bag of fields |
| You need inheritance/polymorphism across several related types | There's only one "kind" of thing, no natural hierarchy |
| Encapsulation (hiding internal representation) actually matters | Any code can safely see/modify every field |
| Object identity and shared mutable state matter (→ handle class) | Everything is naturally copy-by-value data |
Overusing classdef for what's really just a data record adds ceremony
(constructor boilerplate, a whole file per type) without benefit — many
well-written MATLAB programs use structs and functions throughout and
only reach for classes when genuine object-oriented structure (shared
behavior across variants, encapsulated mutable state) is actually needed.
How It Actually Works¶
MATLAB has two fundamentally different object models, and which one a
class uses changes its runtime semantics completely. A classdef that
inherits from handle produces reference-semantics objects — exactly
like the graphics handles from Module 05: a variable holding a handle
object is a pointer to one shared instance, so b = a makes b and a
refer to the same object, and b.prop = 5 is visible through a too.
A classdef that does not inherit from handle produces
value-semantics objects, behaving like MATLAB's built-in numeric and
struct types: b = a triggers the same copy-on-write mechanism covered in
Module 02 — no copy happens at assignment time, but the first mutation to
either a or b forces a private copy of that object's data before the
change is applied, so afterward they are fully independent.
Method dispatch for classdef objects goes through MATLAB's class
metadata system (meta.class) at call time: obj.method(args) resolves
method by walking the object's class hierarchy (checking the object's
own class, then its superclasses in declaration order) to find the first
matching method definition — this lookup happens once per call, not once
per class definition, though MATLAB does cache resolved method handles
internally to avoid repeating the full hierarchy walk on every single
call in a hot loop.
Property validation blocks (properties (Access = private)
value (1,1) double {mustBePositive} end, R2021a+) are enforced by
generated setter code the class system inserts automatically — assigning
to that property runs the validation function before the new value is
stored, using the same validation-function mechanism as function
arguments blocks (Module 04).
Note: based on MathWorks' documented classdef value/handle semantics
and method-resolution order; not executed in MATLAB itself.
Summary¶
classdefbundlesproperties(data) andmethods(behavior) into a reusable type; the constructor method shares the class's name.- Value classes copy on assignment and require methods to return and
be reassigned (
obj = obj.method(...)); handle classes (< handle) behave as shared references and mutate in place. Picking the wrong one is the most commonclassdefbug. - Property validators reuse the same
{mustBePositive, ...}syntax as functionargumentsblocks. - Inheritance (
<), method overriding,Constantproperties, andStaticmethods give MATLAB the standard OOP toolkit, used when a problem genuinely has shared behavior across related types rather than as a default way to organize any data.