Skip to content

MATLAB - User-Defined Classes

Modern Object-Oriented Programming (OOP) in MATLAB

Section titled “Modern Object-Oriented Programming (OOP) in MATLAB”

While MATLAB excels at procedural and matrix-based programming, its object-oriented capabilities are powerful for building complex, modular, and reusable applications. User-defined classes allow you to create custom data types that bundle data (properties) and the functions that operate on that data (methods) into a single entity.

A class is defined in a .m file with the same name as the class, using the classdef keyword.

% Save as BasicCounter.m
classdef BasicCounter
properties
Count = 0; % A property to hold data
end
methods
% Constructor Method: Called when an object is created
function obj = BasicCounter(initialValue)
if nargin > 0
% Use validateattributes for robust input checking
validateattributes(initialValue, {'numeric'}, {'scalar', 'integer'});
obj.Count = initialValue;
end
end
% A regular method to modify the object's state
function obj = increment(obj)
obj.Count = obj.Count + 1;
end
% A method to display the object's state
function displayState(obj)
fprintf('Current count is: %d\n', obj.Count);
end
end
end

To use this class:

myCounter = BasicCounter(5); % Create an instance
myCounter = myCounter.increment(); % Call a method
myCounter.displayState();
Current count is: 6

This is the most important distinction in MATLAB OOP. By default, classes are value classes.

  • Value Classes (classdef MyClass): Behave like standard MATLAB data types (e.g., numbers, structs). When you pass a value object to a function or assign it to another variable, MATLAB creates a copy. Modifying the copy does not affect the original. In our BasicCounter example, increment had to return the modified object (function obj = increment(obj)).
  • Handle Classes (classdef MyClass < handle): Behave like pointers or references. When you pass a handle object, only a reference (the handle) is passed. Modifying the object through its handle affects the one and only instance of that object everywhere. This is more memory efficient for large objects.
% Save as HandleCounter.m
classdef HandleCounter < handle % Inherits from the 'handle' class
properties
Count = 0;
end
methods
function obj = HandleCounter(initialValue)
if nargin > 0
obj.Count = initialValue;
end
end
% Note: This method does NOT need to return 'obj'
function increment(obj)
obj.Count = obj.Count + 1;
end
end
end
% Usage:
hCounter = HandleCounter(10);
hCounter2 = hCounter; % Both variables point to the SAME object
hCounter.increment(); % Modify the object through the first handle
fprintf('hCounter count: %d\n', hCounter.Count); % Output: 11
fprintf('hCounter2 count: %d\n', hCounter2.Count); % Output: 11 (also changed!)

You can control access to properties and methods using attributes to enforce encapsulation.

classdef BankAccount < handle
properties (SetAccess = private) % Can only be set by methods of this class
Balance = 0;
end
properties (Constant)
ACCOUNT_TYPE = 'Checking';
end
methods
function obj = BankAccount(initialDeposit)
obj.deposit(initialDeposit);
end
function deposit(obj, amount)
validateattributes(amount, {'numeric'}, {'>=', 0});
obj.Balance = obj.Balance + amount;
fprintf('Deposited %.2f. New balance: %.2f\n', amount, obj.Balance);
end
function withdraw(obj, amount)
validateattributes(amount, {'numeric'}, {'>', 0});
if amount > obj.Balance
error('Insufficient funds.');
end
obj.Balance = obj.Balance - amount;
fprintf('Withdrew %.2f. New balance: %.2f\n', amount, obj.Balance);
end
end
end
% myAccount = BankAccount(100);
% myAccount.deposit(50);
% myAccount.Balance = 5000; % This will cause an ERROR! Good.

Inheritance allows a class (subclass) to inherit properties and methods from another class (superclass), promoting code reuse.

% Save as Shape.m
classdef (Abstract) Shape < handle % Abstract class cannot be instantiated
properties
Name
end
methods (Abstract) % Subclasses MUST implement this method
area = calculateArea(obj);
end
methods
function display(obj) % A concrete method all shapes will have
fprintf('This shape is a %s with area %.2f\n', obj.Name, obj.calculateArea());
end
end
end
% Save as Circle.m
classdef Circle < Shape % Circle inherits from Shape
properties
Radius
end
methods
function obj = Circle(radius)
obj.Name = 'Circle';
obj.Radius = radius;
end
% Implementing the abstract method from Shape
function area = calculateArea(obj)
area = pi * obj.Radius^2;
end
end
end
% myCircle = Circle(5);
% myCircle.display(); % Calls methods from both Circle and Shape
% This shape is a Circle with area 78.54
  • Value vs. Handle: Use Handle classes when you want reference-like behavior (e.g., for GUI elements, data containers that are modified in place). Use Value classes when you want copy-on-modify behavior (e.g., for immutable mathematical objects).
  • Validation: Use validateattributes and validatestring in constructors and property set methods for robust code.
  • Encapsulation: Use (Access = private) or (SetAccess = private) to protect internal state and expose a clean public API.
  • Composition over Inheritance: Before using inheritance, consider if you can solve the problem by having one object contain another (e.g., a Car class having a Motor object as a property).
  • Naming: Use PascalCase for class names (e.g., MyClassName) and camelCase for properties and methods (e.g., myProperty).