These changes eliminate the possibility of a single object array being populated with different types, but they do not eliminate the promise of inheritance, virtual functions, polymorphi
Trang 1174 A Guide to MATLAB Object-Oriented Programming
12.4 INDEPENDENT INVESTIGATIONS
1 Try your hand at adding a couple of other shape-specific classes You might try adding
a square or a triangle For some real fun, try creating the corner points using rand Think about how you might add a shape with no corners, like a circle
2 Define a child of cStar called cGreenStar, and construct it so that when drawn the star is green rather than blue
Trang 2With the introduction of cStar and cDiamond, the same class no longer represents both stars and diamonds Even though both are derived from cShape and even though neither adds new features, cStar and cDiamond objects are different These differences cast a big shadow on design because they force difficult choices between inheritance and vectorization Here we discuss the differences and add some implementation details to our classes
13.1 WHEN IS A CSHAPE NOT A CSHAPE?
One of the nice things about inheritance, virtual functions, polymorphism, and arrays of objects is the promise that MATLAB will always find and execute the right function based on the object’s type Following this to its conclusion, you might get the idea that a cShape array should be able
to hold objects in any combination of cShape, cStar, and cDiamond In reality, the vectorized implementation inside cShape’s group of eight cannot deal with a mixture of types Vectorized operations rely on every object having exactly the same private structure, and exactly the same type Therefore, even though cStar objects can masquerade as cShape objects, with respect to building object arrays, there is definitely a difference
Unfortunately, MATLAB currently permits some questionable syntax For example, using the code from Chapter 12, the commands in Code Listing 74 execute without causing an immediate error The command in line 1 concatenates objects of different types Line 2 is a variation on line
1 To make matters worse, the class reported for my_shapes(2) from both forms of concatenation
is cStar Concatenation does not cause an immediate error because the underlying structures are the same This loophole must be closed To close it, we need to add some code to subsasgn and
we need to tailor cat, horzcat, and vertcat The commands on line 7 are particularly bad because they eventually result in an internal memory error
These changes eliminate the possibility of a single object array being populated with different types, but they do not eliminate the promise of inheritance, virtual functions, polymorphism, and arrays of objects A cell array populated with different object types can still be used The syntax
is not quite as convenient as a regular array, but short of developing specialized container classes,
a cell array is a good compromise These issues will be discussed in the test drive
Code Listing 74, Questionable Inheritance Syntax
1 >> my_shapes = [cStar cDiamond];
2 >> my_shapes = cStar; my_shapes(2) = cDiamond;
3 >> class(my_shapes(2));
4 ans =
5 cStar
6 >>
7 >> shape = cShape; shape(2) = cStar;
C911X_C013.fm Page 175 Friday, March 30, 2007 11:35 AM
Trang 3176 A Guide to MATLAB Object-Oriented Programming
13.1.1 C HANGES TO SUBSASGN
Rather than repeating the entire listing for subsasgn, only the modified case ‘()’ section is shown in Code Listing 75 In this listing, the modifications occur as additions in lines 11–17 These additions check the values in varargin to make sure they all match the type of the class If they don’t match, an error is thrown
Lines 11–14 generate an error when varargin contains more than one assignment value Since the array-reference operator is assigning array elements, the input must be an array and there should be only one If there is more than one, lines 12–13 throw an error Lines 15–22 check the input type The input can be empty or can be an array of objects of the current class Recall from Chapter 9 that mfilename(‘class’) is a general way to obtain the name of the current class
If the checks on lines 15–16 are okay, line 17 uses the built-in version of subsasgn to index and assign elements If the checks are not okay, lines 19–21 throw an error
Code Listing 75, Changes to subsasgn That Trap Mismatched Array Types
3 % due to superiorto, need to look at this and varargin
4 if isa(this, mfilename('class'))
7 this = eval(class(varargin{1}));
10 if length(index) == 1
11 if length(varargin) > 1
12 error('OOP:UnexpectedInputSize',
13 ['Only one input is allowed for () assignment.']);
15 if isempty(varargin{1}) ||
16 strcmp(class(varargin{1}), mfilename('class'))
17 this = builtin('subsasgn', this, index,
varargin{end:-1:1});
19 error('OOP:UnexpectedType',
20 ['Conversion to ' mfilename('class') ' from '
21 class(mismatched{1}) ' is not possible.']);
22 end
24 this_subset = this(index(1).subs{:}); % get the subset
25 this_subset = subsasgn(this_subset, index(2:end),
varargin{:});
26 this(index(1).subs{:}) = this_subset; % put subset back
27 end
C911X_C013.fm Page 176 Friday, March 30, 2007 11:35 AM
Trang 4Object Arrays with Inheritance 177 13.1.2 VERTCAT AND HORZCAT
When objects are concatenated, the current built-in versions of cat, vertcat, and horzcat do not carefully inspect the types If the underlying structures are the same, the built-in versions will concatenate objects of different types, usually resulting in an internal memory error when the combined variable is used To avoid this situation, our classes will perform some additional checking Performing the additional checks means adding vertcat.m, horzcat.m, and cat.m
as member functions to every class These standard member functions are not in the same league
as the group-of-eight functions because they don’t do anything to the object Ideally, the built-in functions would already include this kind of check For example, cell2mat does not require tailoring because the built-in version correctly checks element types prior to concatenation The vertcat function is shown in Code Listing 76 For horzcat, even though the function name is different, the body is identical to Code Listing 76 The cat function is very similar and
is shown in Code Listing 77
In both functions, lines 2–3 create a cell array of mismatched inputs The cellfun command applies an isclass check to the cells of varargin For vertcat and horzcat, every cell
is checked For cat, the cell at {1} is not checked because it specifies the concatenation direction The output of cellfun is a logical array The element values are true where the input class matches mfilename(‘class’) If all elements match, the inputs are okay and mismatched
will be empty If mismatched is not empty, lines 5–7 throw an error The error identifies the first
Code Listing 76, Implementing Input Type Checking for vertcat.m
1 function this = vertcat(varargin)
2 mismatched = varargin(
3 ~cellfun('isclass', varargin, mfilename('class')));
4 if ~isempty(mismatched)
5 error('MATLAB:UnableToConvert',
6 ['Conversion to ' mfilename('class') ' from '
7 class(mismatched{1}) ' is not possible.']);
9
10 this = builtin(mfilename, varargin{:});
Code Listing 77, Implementing Input Type Checking for cat.m
1 function this = cat(varargin)
2 mismatched = varargin(
3 [false ~cellfun('isclass', varargin(2:end), mfilename
('class'))]);
4 if ~isempty(mismatched)
5 error('MATLAB:UnableToConvert',
6 ['Conversion to ' mfilename('class') ' from '
7 class(mismatched{1}) ' is not possible.']);
9
10 this = builtin(mfilename, varargin{:});
C911X_C013.fm Page 177 Friday, March 30, 2007 11:35 AM
Trang 5178 A Guide to MATLAB Object-Oriented Programming
mismatched type using class(mismatched{1}) and the type from mfilename(‘class’)
If mismatched is empty, line 10 uses builtin to forward the arguments to the built-in version
of the function
13.1.3 T EST D RIVE
Using a cell array to hold objects of different types is a good compromise because the variable
types in a cell array’s elements do not control the cell array’s type This allows a cell array to hold
any combination of types When each cell is indexed, the contents are used to select the appropriate
functions This is where the trade-off between performance and inheritance creeps in Holding
different object types in cell arrays leads to code that is difficult or inconvenient to vectorize The
cellfun command in MATLAB version 7.1 helps to some degree, but there are still important
differences What follows is a discussion of some examples
Class code is designed so that an object can actually be an array of objects All objects in the
array must be of the same type For example,
>> star = [cStar; cStar];
>> star(2).ColorRgb = [1; 0; 0];
>> star(1) = 1.5 * star(1);
>> star = draw(star);
>> diamond = [cDiamond; cDiamond];
>> diamond(1).ColorRgb = [0; 1; 0];
>> diamond(2).Size = [0.75; 1.25];
>> diamond = draw(diamond);
results in the figures shown in Figure 13.1 and Figure 13.2
This chapter’s additions to subsasgn and to the set of member functions prohibit assignment
or concatenation of mismatched types, for example,
FIGURE 13.1 cStar graphic (simple inheritance plus an array of objects) after scaling via multiplication,
1.5* star(1).
1.5
1
0.5
0
–0.5
–1
–1.5
1 0
–1
C911X_C013.fm Page 178 Friday, March 30, 2007 11:35 AM
Trang 6Object Arrays with Inheritance 179
>> shape = [star diamond];
??? Error using ==> cStar.horzcat
Conversion to cStar from cDiamond is not possible.
Trying to concatenate a cStar object and a cDiamond object throws conversion error from
horzcat If we want to comingle different object types in a single array, we have to use a cell
array, for example,
>> shape = {star diamond}
shape =
[1x2 cStar] [2x1 cDiamond]
The problem with a cell array is that we have to index each cell before calling member functions
Trying to call a member function on the entire cell array results in an error, for example,
>> shape_size = shape.Size;
??? Attempt to reference field of non-structure array.
Naturally, we can’t use a dot-reference operator on shape because shape is a cell array We have
to use a loop instead For example,
>> shap_size = [];
>> for k = 1:length(shape)
shape_size = [shape_size shape{k}.Size];
end
>> disp(shape_size)
1.5000e+000 1.0000e+000 1.0000e+000 7.5000e-001
1.5000e+000 1.0000e+000 1.0000e+000 1.2500e+000
FIGURE 13.2 cDiamond graphic (simple inheritance plus an array of objects) after setting the size of (2) to
[0.75; 1.25].
1.5
1
0.5
0
–0.5
–1
–1.5
0.5 0
–0.5
C911X_C013.fm Page 179 Friday, March 30, 2007 11:35 AM
Trang 7180 A Guide to MATLAB Object-Oriented Programming
builds the shape_size array using individual calls to subsref Drawing the shapes works the same way A loop is used, and the resulting figures are identical to Figure 13.1 and Figure 13.2.* The process of looping over object arrays is common in other object-oriented languages, and
it isn’t too objectionable in MATLAB It would be convenient if the loops could be vectorized, but for objects of different types, vectorization is not possible One consequence is that we can’t currently draw cStar objects and cDiamond objects in the same figure window
Drawing multiple objects in the same figure is now a design issue If we want to be able to draw all the shapes held in the same cell array in the same figure window, we need to alter the design of the interface In this particular case, we simply need to modify draw to accept a figure handle as an optional argument The modified /@cShape/draw is shown in Code Listing 78 This function was first developed in Chapter 10 Only the changes are discussed below
* In version 7.1, it is also possible to use cellfun instead of a loop The syntax is certainly a lot less familiar: shape = cellfun(@draw, shape, ‘UniformOutput’, false);.
Code Listing 78, Modified Implementation of draw That Will Accept an Input Figure Handle
1 function this = draw(this, figure_handle)
2 if nargin < 2
5
6 if nargout ~= 1
7 warning('draw must be called using: obj = draw(obj)');
10 handle_array = unique([figure_handle
this(:).mFigureHandle]);
11 if length(handle_array) ~= 1 % no handle or mismatched
12 for k = fliplr(find([handle_array ~= figure_handle]))
19 if isempty(handle_array)
20 figure_handle = figure; % create new figure
22 figure_handle = handle_array(1); % use existing
24 [this.mFigureHandle] = deal(figure_handle); % save the
handle
25 figure(handle_array); % use the handle
26
27 if nargin < 2
Trang 8Object Arrays with Inheritance 181
When a figure_handle isn’t passed in, lines 2–4 initialize figure_handle with empty Line 10 concatenates figure_handle with the object’s figure handles before calling unique When no figure_handle was passed in, concatenation with empty has no effect The loop in lines 12–17 now loops over indices of handle_array that are not equal to the value of the passed-in handle value The command fliplr is used to reverse the loop order because elements are removed during the loop Line 14 closes each figure, and line 16 removes the handle from
handle_array On line 19, if handle_array is empty, line 20 creates a new figure; otherwise, the first element of handle_array will be reused Line 24 assigns the handle into the objects, and line 25 activates the figure Lines 27–29 clear the figure only when a handle was not passed into the function When a handle is passed in, the caller is responsible for clearing the figure Now that we can pass in a figure handle to draw, we can create a loop that will draw all the shapes on one figure window The following commands will draw the figure shown in Figure 13.3
It is a little crowded, but all have been drawn together
>> fig_handle = figure;
>> for k = 1:length(shape)
shape{k} = draw(shape{k}, fig_handle);
end
30
31 hold on; % all shapes drawn in the same figure
32 for k = 1:length(this(:))
33 this(k).mPlotHandle = plot(
34 this(k).mSize(1) * this(k).mPoints(1,:),
35 this(k).mSize(2) * this(k).mPoints(2,:),
36 'Color', get(this(k), 'ColorRgb'));
40 end
FIGURE 13.3 Combined graphics for cStar and cDiamond.
1.5
1
0.5
0
–0.5
–1
–1.5
0
Trang 9182 A Guide to MATLAB Object-Oriented Programming
13.2 SUMMARY
In this chapter, we tied up some loose ends related to inheritance and arrays of objects MATLAB’s built-in functions allow the assignment of child objects into array elements of the parent, and they allow the assignment of different types as long as their private structures match A simple modifi-cation to subsasgn and the addition of cat, horzcat, and vertcat member functions effectively eliminate assignment errors These functions do not rise to the level of the group-of-eight functions because they don’t really interact with the object They simply error-check the input and pass that input along to the built-in version
We also investigated how to manage arrays of objects with different types but the same set of member functions A cell array must be used and cell arrays force us to abandon vectorized code,
at least for some operations The use of cell arrays also influences the interface design The interface must always consider the possibility of holding objects in a cell array rather than in a regular array
In our shape example, the impact was small
13.3 INDEPENDENT INVESTIGATIONS
1 With the shape cell array populated with cStar and cDiamond, experiment with changing the color or size of individual shapes
2 Can you change all shapes to the same color with one assignment? Do you have to use
a loop or cellfun?
Trang 10An important facet of parent–child inheritance is the child’s ability to tailor any function that would otherwise be conveyed from the parent In Chapters 12 and 13, the child-class functions didn’t do anything other than slice and forward They couldn’t do much more than that because the child classes inherited all of their data from the parent Closely related to member function tailoring is the child’s ability to go beyond inheritance by adding private member variables, public member variables, and member functions
Adding new m-files is straightforward Adding new public member variables is a little more difficult because additional variable names need to be incorporated in the group-of-eight functions Supporting these additions is exactly the reason behind the organization of get.m and set.m
In Chapters 12 and 13, these functions contained slice-and-forward sections only There was no reason to include sections for public or concealed variables because cStar and cDiamond had none In this chapter, we will add a public variable to cStar and examine the effects on both the implementation and inheritance
14.1 FUNCTION REDEFINITION
A class can tailor the behavior of almost any built-in function The group-of-eight functions are a good example of tailoring We also used the fact that a tailored function can call the built-in version
to coerce MATLAB into doing most of the heavy lifting In Chapters 12 and 13, we examined parent–child inheritance and noted that a child class can tailor the behavior of many parent-class functions Using a slice-and-forward strategy, a tailored child’s function can coerce the parent into doing most of the heavy lifting The limiting factors in slice and forward are the parent’s public interface and the visibility of variables A child-class function is not limited to include only slice-and-forward code A child-class function doesn’t have to call the parent function at all, but when
it does, the child can add behavior either before or after the call to the parent Such redefinition allows the hierarchy to extend beyond its original intent without changing the original code We get the maximum amount of reuse with the smallest number of side effects
Adding a title string to figures that contain a star is a nice addition that fits nicely into the shape hierarchy We could hold the title string at the parent level and give every shape the same capability Since we already know how to add things at the parent level, we will instead give only
cStar objects a title We will create the public variable Title and store the string in the private variable mTitle The constructor will set a default value of ‘A Star is born’, and a client can change the string at any time
Since we are adding a new member variable to cStar, the following four group-of-eight functions will need to be modified:
/@cStar/private/ctor_ini.m /@cStar/fieldnames.m
/@cStar/get.m /@cStar/set.m
The constructor will add and initialize the new private variable, fieldnames will add the new public variable to its name list, and get and set will add accessor and mutator code for the
C911X_C014.fm Page 183 Friday, March 2, 2007 7:53 AM