Introduction to Object-Oriented Programming in MATLAB
By Stuart McGarrity and Adam Sifounakis, MathWorks
When creating software applications, it is important to organize the various building blocks of your software into related groups. For example, a custom numerical solver may require several configuration parameters and routines to perform its full set of calculations. Object-oriented programming (OOP) allows you to group the solver’s configuration parameters (properties) with its functions (methods) into a single definition, or class. Everything a user will need to properly execute this solver is defined in this class.
An object is an instance of a class. When a program executes, the object is created based on its class definition and behaves in the way defined by the class. The properties of an object represent its state, and its methods represent all the actions a user may perform. In this way, a code author can easily group all the related data and functions for a software system and a user can easily find and use all the capabilities the code author has developed.
The following example uses object-oriented programming to build an application that will analyze sensor data from an array of sensors.
The code used in this article is available for download.
Application Example: Analyzing Sensor Array Data
A sensor array (Figure 1) is a collection of sensors, often arranged in a line, that is used to sample a medium such as air, water, or the ground for radar, sonar, or cellular communications. By collecting time samples from multiple points in space, you can extract additional information from the medium being sampled.
Our application uses a sensor array to determine the direction of arrival (DOA) of multiple distant electromagnetic sources, such as radio beacons and radar transmitters. In this scenario, we will attempt to estimate the angles θ1 and θ2 of the two sources relative to the direction the sensor array is pointing.
Reviewing Data Items and Operations
For this application, we will need to store and represent the following data:
- Numbers of sensors and samples
- Sampled sensor data
- Sensor sample rate
- Sensor spacing
- Wavelength of distant sources
- Speed of wave
- Sensor data set name or description
We will use a simple fast Fourier transform (FFT)-based technique to estimate the DOA of the sources. This technique can be broken down into parts and implemented as a collection of operations. A small number of utility operations will be implemented to help simplify the development work. For example, we must:
- Create the data set from synthetic data or acquired live data
- Inspect and modify data set values and parameters
- Plot the sample data to help with interpretation and validation
- Calculate and plot the power spectrum of the data set (by simple magnitude squared of the FFT method)
- Find the peaks of the power spectrum to estimate DOA of the sources
Having identified the data we need to represent and the activities we need to perform, we can represent the data with class properties and the activities with class methods.
Representing Data with Class Properties
We begin by defining a class to describe the sensor array. This initial representation contains only the data items and represents them as class properties.
You define a class in MATLAB with a class definition file, which begins with the classdef
keyword and is terminated by the end
keyword. Within the class definition block, additional keyword blocks will describe different aspects of the class, such as class properties and class methods. The definition file shown in Figure 2 describes a class sads
(sensor array data set) with all the data items that we need to represent listed in a properties block.
Creating an Object and Accessing Properties
To create an object or instance of the class that we defined, we use the statement
>> s = sads;
To set the value of a property, we specify its name just like fields of a structure with
>> s.NumSensors = 16;
We can display the object, seeing all the available properties and current values, by typing its name.
>> s s = sads with properties: Wavelength: [] c: 300000000 NumSensors: 16 NumSamples: [] Data: [] Spacing: [] SampleRate: [] Name: []
All the properties except NumSensors
and c
are still empty. The data set can now be identified as a sads
object using the class
function, isa
function, and the whos
command, something that is not possible with structures.
>> class(s) ans = 'sads'
The ability to identify the class of a variable is important to users who create code to operate on the data set, as it lets them determine the available data items to be accessed and operations that can be legally performed.
Error Checking
If you use structures to represent your data, you could add a new field name at any time simply by specifying a new field name and assigning it a value. This capability is particularly convenient when you are experimenting with and prototyping algorithms. However, if you misspell a field name, a new field will be added silently, which might cause an error later that is difficult to diagnose.
Unlike structures, you cannot dynamically add a new property to an object simply by specifying a new property name and assigning it a value. If you misspell an object property name, MATLAB immediately issues an error. This additional level of error checking is useful when the object is being accessed by users who are less familiar with it than the author, which is common during the development of a large application.
Controlling Access to Data
Classes give you great control over property access. For example, they let you prohibit modification of a property, hide a property, or cause it to be calculated dynamically. You control access to properties by specifying property attributes in the class definition file. We expand on the class definition file in Figure 2 by dividing the current list of properties into multiple property blocks, each with unique property attributes: GetAccess
, Constant
, and Dependent
(Figure 3).
You prohibit modification of a property by setting the Constant
attribute. In our example, we will set the speed of light property c
to be constant. Because constant properties do not change, they can be accessed simply by referencing the class name.
>> sads.c ans = 300000000
You make a property read-only by setting the SetAccess
attribute to private. You can make a property visible only to the methods operating on it by setting the GetAccess
attribute to private, as we will do with the Wavelength
property.
You can freely change the names or characteristics of a private property without affecting users of the object. This “black box” approach to defining a piece of software, known as encapsulation, prevents the user of the object from becoming dependent on an implementation detail or characteristic that could change and break their code.
You specify that a property is calculated only when asked for by setting its Dependent
attribute.
You then specify a get method that is automatically called when the property is accessed. See the "Accessing Properties with Get and Set Methods” section of this article for details on how to specify class methods. In our application, we set the NumSensors
and NumSamples
properties to be dependent.
Implementing Operations with Class Methods
Methods, or the operations that can be performed on the object, are specified as a list of functions in a methods block. A class can contain many types of methods, each fulfilling a different purpose, each specified differently. The following section describes a number of these types of methods.
We will add a methods block to the sads
definition file and add each new method inside this block (Figure 4).
Specifying a Constructor Method
In our example, we will specify a constructor method that lets the user provide parameters to be used in the creation of the object. The constructor method often performs data initialization and validation. The object is now created with
>> s = sads(Data, Wavelength, SampleRate, Spacing, Name);
Implementing Application-Specific Methods
We will add several methods to implement application-specific operations to be performed on the data set. Most methods take the object as an input argument (for example, obj
) and access the object properties by referencing this variable (for example, obj.NumSamples
), as in this method:
function mag = magfft(obj, zpt)
mag = zeros(obj.NumSamples, zpt);
...
end
Although it requires additional syntax, referencing properties via the object variable can help differentiate them from local function variables, such as mag
above.
Calling Methods
Methods are called just like functions, with the object passed in as one of the arguments. We can estimate the sources’ DOA angles by calling the doa
method of our class.
>> angles = doa(s) angles = -10.1642 18.9953
The DOA angles approximate the true locations of the sources shown in Figure 1, which are -10° and 20°.
Accessing Properties with Get and Set Methods
You can validate properties or implement dependent properties by specifying associated set and get methods. Here is the get method for the NumSensors
property.
function NumSensors = get.NumSensors(obj) NumSensors = size(obj.Data, 2); end
Get and set methods are called automatically when properties are accessed, for example with
>> N = s.NumSensors;
Specifying Methods for Existing MATLAB Functions with Overloading
Overloading lets you redefine existing MATLAB functions to work on your object by providing a function with that name in your list of methods. In our application, we will include an overloaded plot
method, providing a function to visualize the data set that is familiar to many MATLAB users (Figure 5).
>> plot(s)
This customized plot method represents the information in the most appropriate way for this data set, annotating it with all the available information. It is executed only on objects for which it has been defined—a much more robust approach than manipulating the order of directories in the path.
If you would like specialized behaviors for your class, you can also overload basic operators and even indexing by using methods with special names.
Developing the Application Further
The class that we created in this example represents our sensor array data set and allows us to easily perform a complex, specialized analysis on our data, including the main direction-finding operation. We can use this class to rapidly evaluate the performance of the FFT-based technique in different scenarios.
We could expand the application using additional OO techniques. For example, we could do the following:
- Define subclasses of existing classes (reusing a definition of a broader category to define a more specific subcategory) with inheritance
- Specify static methods, letting us define an operation for the class as a whole
- Use handle classes with reference behavior, enabling us to make data structures like linked lists or work with a large data set without copying it
- Define events and listeners, letting us monitor object properties or actions
These techniques enhance our ability to manage complexity by enabling us to further define relationships and behavior in the application.
Because it was built using OO techniques, the application is now robust enough for others to use and maintain and can be integrated with other applications throughout an organization.
Published 2020