This note provides a brief review of the notion of encapsulation and data hiding in Object-Oriented Programming (OOP) paradigm via MATLAB.

Encapsulation and data hiding

Classes can be used for many purposes in scientific programming and computation. One of the most frequently encountered tasks is to represent mathematical functions that have a set of parameters in addition to one or more independent variables. These functions, together with all of their essential and auxiliary variables have to be frequently passed to other functions to perform some tasks. Implementing such problems using purely procedural programming tools that we have learned so far can lead to the development of an unsafe, unclean, and undesired codebase, which is not easy to work with either. To understand why and how this happens, consider the following scientific problem.

A common programming challenge in numerical computing

Consider a function that takes in some parameters as input, for example, the equation of motion a stone when it is thrown upward in the air. The physics equation describing this motion is $y(t) = v_0t-\frac{1}{2}gt^2$. Physically speaking, this equation gives the position $y$ from the ground of the physical object stone, as a function of time. Therefore, in physics, $y$ is viewed as a function of $t$.

However, mathematically speaking, $y$ also depends on two other parameters, $v_0$ and $g$, although it is unnatural to view $y$ as a function of these parameters. One can therefore write $f(t;v_0g)$ to emphasize that $t$ is the independent variable, while $v_0$ and $g$ are parameters. Strictly speaking, $g$ is a fixed parameter (as long as the experiment is run on the surface of the earth), so only $v_0$ and $t$ can be arbitrarily chosen in the formula.

It would then be better to write $y(t;v_0)$. Here is an implementation of this function,

function height = getHeight(time,iniVel)
    g = 9.81;
    height = iniVel*time - 0.5*g*time^2;
end

This function gives the height of the projectile as a function of time. Now suppose you wanted to differentiate $y$ with respect to $t$ to obtain the velocity. You could write the following code to do so,

function derivative = getDerivative(func, x)
    h = 1E-5;
    derivative = (func(x+h) - func(x))/h;
end

But, here is the catch with this problem of differentiation. The getDerivative function works with any function func that takes only one argument. In other words, if we want to input getHeight to getDerivative, then we will have to redefine getHeight to take only one argument. You may wonder why not change getDerivative. For this simple problem, this could be a solution. But, with larger problems, you are more likely to use sophisticated routines and modules that have been already developed and many of these routines take a function as input that only has one input variable. This is quite often the case with high-performance integration routines.

One perhaps-bad solution to the above problem is to use global variables. The requirement is thus to define MATLAB implementations of mathematical functions of one variable with one argument, the independent variable,

function height = getHeightGlobal(time)
    global iniVel;
    g = 9.81;
    height = iniVel*time - 0.5*g*time^2;
end

This function will work only if iniVel is a global variable, initialized before one attempts to call the function. Here is an example call where getDerivative differentiates y,

iniVel = 3;
derivative = getDerivative(@getHeightGlobal, 1)

The use of global variables is in general considered bad programming. Why global variables are problematic in the present case can be illustrated when there is a need to work with several versions of a function. Suppose we want to work with two versions of $y(t;v_0)$, one with $v_0=1$ and one with $v_0=5$. Every time we call y, we must remember which version of the function we work with, and set iniVel accordingly before the call,

global iniVel; iniVel = 1; r1 = getHeightGlobal(t)
global iniVel; iniVel = 5; r2 = getHeightGlobal(t)

Another problem is that variables with simple names like iniVel, may easily be used as global variables in other parts of the program. These parts may change our iniVel in a context different from the getHeightGlobal function, but the change affects the correctness of the getHeightGlobal function. In such a case, we say that changing iniVel has side effects, i.e., the change affects other parts of the program in an unintentional way. This is one reason why a golden rule of programming tells us to limit the use of global variables as much as possible.

An alternative solution to the problem of needing two iniVel parameters could be to introduce two getHeight functions, each with a distinct iniVel parameter,

function height = getHeightGlobal_1(time)
    global iniVel_1; % initial velocity
    g = 9.81;
    height = iniVel_1*time - 0.5*g*time^2;
end

function height = getHeightGlobal_2(time)
    global iniVel_2; % initial velocity
    g = 9.81;
    height = iniVel_2*time - 0.5*g*time^2;
end

Now we need to initialize iniVel_1 and iniVel_2 once, and then we can work with getHeightGlobal_1() and getHeightGlobal_2(). However, if we need $100$ iniVel parameters, we need $100$ functions. This is tedious to code, error-prone, difficult to administer, and simply a really bad solution to a programming problem.

So, is there a good remedy? The answer is yes: the class concept solves all the problems described above.

Class representation of a function

A class contains a set of variables (data) and a set of functions, held together as one unit. The variables are visible in all the functions in the class. That is, we can view the variables as “global” in these functions. You can also make many copies of a class.

Consider the function $y(t;v_0) = v_0t - \frac{1}{2}gt^2$. We may say that $v_0$ and $g$, represented by the variables iniVel and g, constitute the data. A MATLAB function, say getValue(time), is then needed to compute the value of $y(t;v_0)$ and this function must have access to the data iniVel and g, while time is an argument. A programmer experienced with classes will then suggest collecting the data iniVel and g, and the function getValue(time), together as a class. Also, a class usually has another function, called constructor for initializing the data. The constructor is always named similar to the class-name. Every class must have a name, often starting with a capital, so we choose ProjectileHeight as the name since the class represents a projectile motion. The next step is to implement this class in MATLAB. A complete class code ProjectileHeight for our problem here would look as follows in MATLAB:

classdef ProjectileHeight

    properties
        g = 9.81;
        iniVel; % initial velocity
    end

    methods (Access = public)

        % This is the class constructor
        function ProjectileHeightObject = ProjectileHeight(iniVel)
            ProjectileHeightObject.iniVel = iniVel;
        end

        % Computes the height of the Projectile motion for a given time and initial velocity: iniVel
        function height = getValue(ProjectileHeightObject,time)
            height = ProjectileHeightObject.iniVel * time - 0.5 * ProjectileHeightObject.g * time^2;
        end

    end

end

A class creates a new data type, here of name ProjectileHeight, so when we use the class to make objects, those objects are of type ProjectileHeight. All the standard MATLAB objects, such as lists, tuples, strings, floating-point numbers, integers, …, are built-in MATLAB classes, and each time the user creates on these variable types, one instance of these classes are created by the MATLAB interpreter. A user-defined object class (like ProjectileHeight) is usually called an instance. We need such an instance to use the data in the class and call the value function. The following statement constructs an instance bound to the variable name MyProjectileHeight,

>> MyProjectileHeight = ProjectileHeight(100)
MyProjectileHeight = 
  ProjectileHeight with properties:

         g: 9.8100
    iniVel: 100

Seemingly, we call the class ProjectileHeight as if it were a function. Indeed, ProjectileHeight(3) is automatically translated by MATLAB to a call to the constructor method ProjectileHeight() in class ProjectileHeight. With the instance MyProjectileHeight, we can compute the value of y(t=0.1;v_0=3) by the statement,

>> height = MyProjectileHeight.getValue(1)
height =
   95.0950

Note that the ProjectileHeightObject input argument is dropped in the call to getValue() method. To access functions and variables in a class, one must prefix the function and variable names by the name of the instance and a dot: the getValue() function can be reached by MyProjectileHeight.value, and the variables are reached as MyProjectileHeight.iniVel and MyProjectileHeight.g. One could, for example, print the value of iniVel in the instance getValue by writing,

MyProjectileHeight.iniVel
ans =
   100

We have already introduced the term instance for the object of a class. Functions in classes are commonly called methods, and variables (data) in classes are called data attributes. Methods are also known as method attributes. For example, in our sample class ProjectileHeight we have two methods or method attributes, ProjectileHeight() and getValue(), two data attributes, iniVel and g. Note that the names of attributes can be chosen freely, just as names of ordinary MATLAB functions and variables. However, the constructor must have the same name as the name of the class, otherwise, it is not automatically called when new instances are created. You can do whatever you want in whatever method, but it is a common convention to use the constructor for initializing the variables of an instance of a class.

Calling object methods from other functions

So far, we have an object-oriented implementation of our ProjectileHeight motion. Now, going back to the original differentiation problem, if we want to be able to differentiate the height with respect to time using the function getDerivative(), we will have to pass MyProjectileHeight object in its entirety to getDerivative(),

function derivative = getDerivativeOOP(FuncObj, x)
    h = 1E-5;
    derivative = (FuncObj.getValue(x+h) - FuncObj.getValue(x))/h;
end
>> MyProjectileHeight = ProjectileHeight(100)   % create object with initial velocity 100 (m/s)
MyProjectileHeight = 
  ProjectileHeight with properties:

         g: 9.8100
    iniVel: 100
>> derivative = getDerivativeOOP(MyProjectileHeight,1) % compute derivative at time = 1 (s)
derivative =
   90.1900