Why Use Object-Oriented Design
Approaches to Writing MATLAB Programs
Creating software applications typically involves designing how to represent the application data and determining how to implement operations performed on that data. Procedural programs pass data to functions, which perform the necessary operations on the data. Object-oriented software encapsulates data and operations in objects that interact with each other via the object’s interface.
The MATLAB language enables you to create programs using both procedural and object-oriented techniques and to use objects and ordinary functions in your programs.
Procedural Program Design
In procedural programming, your design focuses on steps that must be executed to achieve a desired state. You typically represent data as individual variables or fields of a structure and implement operations as functions that take the variables as arguments. Programs usually call a sequence of functions, each one of which is passed data, and then returns modified data. Each function performs an operation or perhaps many operations on the data.
Object-Oriented Program Design
The object-oriented program design involves:
- Identifying the components of the system or application that you want to build
- Analyzing and identifying patterns to determine what components are used repeatedly or share characteristics
- Classifying components based on similarities and differences
After performing this analysis, you define classes that describe the objects your application uses.
Classes and Objects
A class describes a set of objects with common characteristics. Objects are specific instances of a class. The values contained in an object’s properties are what make an object different from other objects of the same class (an object of class double might have a value of 5). The functions defined by the class (called methods) are what implement object behaviors that are common to all objects of a class (you can add two doubles regardless of their values).
Using Objects in MATLAB Programs
The MATLAB language defines objects that are designed for use in any MATLAB code. For example, consider the try-catch programming construct.
If the code executed in the try block generates an error, program control passes to the code in the catch block. This behavior enables your program to provide special error handling that is more appropriate to your particular application. However, you must have enough information about the error to take the appropriate action.
MATLAB provides detailed information about the error by passing an MException object to functions executing the try-catch blocks.
The following try-catch blocks display the error message stored in an MException object when a function (surf in this case) is called without the necessary arguments:
try surf catch ME disp(ME.message) end Not enough input arguments.
In this code, ME is an object of the MException class, which is returned by the catch statement to the function’s workspace. Displaying the value of the object’s message property returns information about the error (the surf function requires input arguments). However, this is not all the information available in the MException object.
You can list the public properties of an object with the properties function:
properties(ME) Properties for class MException: identifier message cause stack
Objects Organize Data
The information returned in an MException object is stored in properties, which are much like structure fields. You reference a property using dot notation, as in ME.message. This reference returns the value of the property. For example,
class(ME.message) ans = char
shows that the value of the message property is an array of class char (a text string). The stack property contains a MATLAB struct:
ME.stack ans = file: 'U:\bat\A\perfect\matlab\toolbox\matlab\graph3d\surf.m' name: 'surf' line: 50
You can simply treat the property reference, ME.stack as a structure and reference its fields:
ME.stack.file ans = D:\myMATLAB\matlab\toolbox\matlab\graph3d\surf.m
The file field of the struct contained in the stack property is a character array:
class(ME.stack.file) ans = char
You could, for example, use a property reference in MATLAB functions:
strcmp(ME.stack.name,'surf') ans = 1
Object properties can contain any class of value and can even determine their value dynamically. This provides more flexibility than a structure and is easier to investigate than a cell array, which lacks fieldnames and requires indexing into various cells using array dimensions.
Objects Manage Their Own Data
You could write a function that generates a report from the data returned by MException object properties. This function could become quite complicated because it would have to be able to handle all possible errors. Perhaps you would use different functions for different try-catch blocks in your program. If the data returned by the error object needed to change, you would have to update the functions you have written to use the new data.
Objects provide an advantage in that objects define their own operations. A requirement of the MException object is that it can generate its own report. The methods that implement an object’s operations are part of the object definition (i.e., specified by the class that defines the object). The object definition might be modified many times, but the interface your program (and other programs) use does not change. Think of your program as a client of the object, which isolates your code from the object’s code.
To see what methods exist for MException objects, use the methods function:
methods(ME) Methods for class MException: addCause eq isequal rethrow throwAsCaller disp getReport ne throw Static methods: last
You can use these methods like any other MATLAB statement when there is an MException object in the workspace. For example:
ME.getReport ans = ??? Error using ==> surf at 50 Not enough input arguments.
Objects often have methods that overload (redefined for the particular object) MATLAB functions (e.g., isequal, fieldnames, etc.). This enables you to use objects just like other values. For example, MException objects have an isequal method. This method enables you to compare these objects in the same way you would compare variables containing doubles. If ME and ME2 are MException objects, you can compare them with this statement:
However, what really happens in this case is MATLAB calls the MException isequal method because you have passed MException objects to isequal.
Similarly, the eq method enables you to use the == operator with MException objects:
ME == ME2
Of course, objects should support only those methods that make sense. For example, it would probably not make sense to multiply MException objects so the MException class does not implement methods to do so.
When Should You Start Creating Object-Oriented Programs
Objects are well integrated into the MATLAB language, regardless of whether you are writing simple functions, working interactively in the command window, or creating large applications.
Simple programming tasks are easily implemented as simple functions, but as the magnitude and complexity of your tasks increase, functions become more complex and difficult to manage.
As functions become too large, you might break them into smaller functions and pass data from one to the other. However, as the number of functions becomes large, designing and managing the data passed to functions becomes difficult and error prone. At this point, you should consider moving your MATLAB programming tasks to object-oriented designs.
Thinking in terms of things or objects is simpler and more natural for some problems. You might think of the nouns in your problem statement as the objects you need to define and the verbs as the operations you must perform.
For example, consider performing an analysis of economic institutions. It would be difficult to represent the various institutions as procedures even though they are all actors in the overall economy. Consider banks, mortgage companies, credit unions. You can represent each institution as an object that performs certain actions and contains certain data. The process of designing the objects involves identifying the characteristics of these institutions that are important to your application.
Identify Commonalities. All of these institutions belong in the general class of lending institutions, so all objects might provide a loan operation and have a Rate property that stores the current interest rate.
Identify Differences. You must also consider how each institution differs. A mortgage company might provide only home mortgage loans. Therefore, the loan operation might need be specialized for mortgage companies to provide fixRateLoan and varRateLoan methods to accommodate two loan types.
Consider Interactions. Institutions can interact, as well. For example, a mortgage company might sell a mortgage to a bank. To support this activity, the mortgage company object would support a sellMortgage operation and the bank object would support a buyMortgage operation.
You might also define a loan object, which would represent a particular loan. It might need Amount, Rate, and Lender properties. When the loan is sold to another institution, the Lender property could be changed, but all other information is neatly packaged within the loan object.
Add Only What Is Necessary. It is likely that these institutions engage in many activities that are not of interest to your application. During the design phase, you need to determine what operations and data an object needs to contain based on your problem definition.
Managing Data. Objects encapsulate the model of what the object represents. If the object represents a kind of lending institution, all the behaviors of lending institutions that are necessary for your application are contained by this object. This approach simplifies the management of data that is necessary in a typical procedural program.
Objects Manage Internal State
In the simplest sense, objects are data structures that encapsulate some internal state, which you access via its methods. When you invoke a method, it is the object that determines exactly what code to execute. In fact, two objects of the same class might execute different code paths for the same method invocation because their internal state is different. The internal workings of the object need not be of concern to your program — you simply use the interface the object provides.
Hiding the internal state from general access leads to more robust code. If a loan object’s Lender property can be changed only by the object’s newLender method, then inadvertent access is less likely than if the loan data were stored in a cell array where an indexing assignment statement could damage the data.
Objects provide a number of useful features not available from structures and cell arrays. For example, objects provide the ability to:
- Constrain the data assigned to any given property by executing a function to test values whenever an assignment is made
- Calculate the value of a property only when it is queried and thereby avoid storing data that might be dependent on the state of other data
- Broadcast notices when any property value is queried or changed, to which any number of listeners can respond by executing functions
- Restrict access to properties and methods
As the complexity of your program increases, the benefits of an object-oriented design become more apparent. For example, suppose you need to implement the following procedure as part of your application:
- Check inputs
- Perform computation on the first input argument
- Transform the result of step 2 based on the second input argument
- Check validity of outputs and return values
This simple procedure is easily implemented as an ordinary function. But now suppose you need to use this procedure again somewhere in your application, except that step 2 must perform a different computation. You could simply copy and paste the first implementation, and then rewrite step 2. Or you could create a function that accepted an option indicating which computation to make, and so on. However, these options lead to more and more complicated code.
An object-oriented design could result in a simpler solution by factoring out the common code into what is called a base class. The base class would define the algorithm used and implement whatever is common to all cases that use this code. Step 2 could be defined syntactically, but not implemented, leaving the specialized implementation to the classes that you then derive from this base class.
Step 1 function checkInputs() % actual implementation end Step 2 function results = computeOnFirstArg() % specify syntax only end Step 3 function transformResults() % actual implementation end Step 4 function out = checkOutputs() % actual implementation end
The code in the base class is not copied or modified, it is inherited by the various classes you derive from the base class. This reduces the amount of code to be tested, and isolates your program from changes to the basic procedure.
Defining Consistent Interfaces
The use of a class as the basis for similar, but more specialized classes is a useful technique in object-oriented programming. This class is often called an interface class. Incorporating this kind of class into your program design enables you to:
- Identify the requirements of a particular objective
- Encode these requirements into your program as an interface class
For example, suppose you are creating an object to return information about errors that occur during the execution of specific blocks of code. There might be functions that return special types of information that you want to include in an error report only when the error is generated by these functions.
The interface class, from which all error objects are derived, could specify that all error objects must support a getReport method, but not specify how to implement that method. The class of error object created for the functions returning special information could implement its version of the getReport method to handle the different data.
The requirement defined by the interface class is that all error objects be able to display an error report. All programs that use this feature can rely on it being implement in a consistent way.
All of the classes derived from the interface class can create a method called getReport without any name conflicts because it is the class of the object that determines which getReport is called.
Objects reduce complexity by reducing what you need to know to use a component or part of a system. This happens in a couple of ways:
- Implementation details are hidden behind the interfaces defined by objects.
- Rules controlling how objects interact are enforced by object design and, therefore, not left to object users to enforce.
To illustrate these advantages, consider the implementation of a data structure called a doubly linked list.
To add a new node to the list, the following steps must occur. First disconnect the nodes:
- Unlink n2.Prev from n1
- Unlink n2.Next from n3
- Unlink n3.Prev from n2
- Unlink n1.Next from n2
Now connect the new node and renumber:
- Link new.Prev to n1
- Link new.Next to n3 (was n2)
- Link n1.Next to new (will be n2)
- Link n3.Prev to new (will be n2)
The act of inserting a new node in an existing doubly linked list requires a number of steps. Any application code using a data structure like this can perform these operations. However, defining the linked list nodes as objects enables all the operations like adding, deleting, and rearranging nodes to be encapsulated in methods. The code that implements these operations can be well tested, implemented in an optimal way, always up to date with the current version of the class, and can even automatically update old-versions of the objects when they are loaded from MAT-files.
The objects enforce the rules for how the nodes interact by implementing methods to carry out these operations. A single addNode method would perform all the steps listed above. This removes the responsibility for enforcing constraints from the applications that use the objects. It also means the application is less likely to generate errors in its own implementation of the process.
This approach can reduce the complexity of your application code, provide greater consistency across applications, and reduce testing and maintenance requirements.
As you decompose a system into objects (car –> engine –> fuel system –> oxygen sensor), you form modules around natural boundaries. These objects provide interfaces by which they interact with other modules (which might be other objects or functions). Often the data and operations behind the interface are hidden from other modules to segregate implementation from interface.
Classes provide three levels of control over code modularity:
- Public — Any code can access this particular property or call this method.
- Protected — Only the object’s own methods and those of the object’s whose class has been derived from this object’s class can access this property or call this method.
- Private — Only the object’s own methods can access this property or call this method.
When you define a class, you can overload existing MATLAB functions to work with your new object. For example, the MATLAB serial port class overloads the fread function to read data from the device connected to the port represented by this object. You can define various operations, such as equality (eq) or addition (plus), for a class you have defined to represent your data.
Suppose your application requires a number of dialog windows to interact with users. By defining a class containing all the common aspects of the dialog windows, and then deriving the specific dialog classes from this base class, you can:
- Reuse code that is common to all dialog window implementations
- Reduce code testing effort due to common code
- Provide a common interface to dialog developers
- Enforce a consistent look and feel
- Apply global changes to all dialog windows more easily
[important]See MATLAB Classes to learn more about writing object-oriented MATLAB programs.[/important]