A general container is different from an array because it can hold different types.. A general container is different from a cell array because all objects must descend from the same par
Trang 1274 A Guide to MATLAB Object-Oriented Programming
18 >> star.Size = [2;3];
19 >> disp(star.Size')
21 >> star
22 star =
26 LineWeight: 'normal'
28 >> fieldnames(star)
29 ans =
31 'ColorRgb'
33 'LineWeight'
35 >> fieldnames(star, '-full')
36 ans =
37 ans =
38 'Size % double array (2x1)'
39 'ColorRgb % double array (3x1)'
40 'Points % double array (2xN)'
41 'LineWeight % normal, bold'
42 'Title % string'
43 >> fieldnames(star, '-possible')
44 ans =
46 {1x1 cell}
47 'ColorRgb'
48 {1x1 cell}
50 {1x1 cell}
51 'LineWeight'
52 {1x1 cell}
54 {1x1 cell}
55 >> struct(star)
59 LineWeight: 'normal'
61 >> star = [cStar cStar; cStar cStar];
62 >> size(star)
63 ans =
Trang 2Class Wizard Versions of the Shape Hierarchy 275
Attending to the myriad details is something that a CASE tool can do very well Even this is difficult unless there is a good organizational structure The organizational structure advocated by the preceding chapters results in good class implementation, and Class Wizard is very helpful in maintaining that structure This is particularly true when the class definition evolves With Class Wizard, evolution is a simple matter of adding elements to the definition and rebuilding the files Files managed by the tool are overwritten with the new definition, while handcrafted files are untouched The best balance is always maintained between standard idioms and a developer’s creativity
18.7 INDEPENDENT INVESTIGATIONS
1 Repeat the test-drive commands using cDiamond objects instead of cShape objects
2 Modify the class interfaces to allow shapes to be rotated by an arbitrary angle Use Class Wizard to generate the initial versions of helper functions and public member functions
3 Add other line-style features to cLineStyle, and expose these features so that clients can use them with cStar and cDiamond objects (for example, dotted vs solid lines)
4 Add a cCircle class to the hierarchy Does cCircle inherit cShape or is there a better relationship? Should /@cCircle/draw use polar instead of plot? How would the use of polar change the organization?
65 >> [star.Size]
66 ans =
67 1 1 1 1
68 1 1 1 1
69 >> {star.Size}
70 ans =
71 [2x1 double] [2x1 double] [2x1 double] [2x1 double]
72 >>
73 >> disp(class(star))
74 cStar
75 >> disp(isa(star, 'cShape'))
77 >> disp(isa(star, 'cDiamond'))
Trang 4Part 3
Advanced Strategies
In this section, we redeploy standard object-oriented techniques in a way that allows MATLAB to create a few special-purpose classes commonly found in other object-oriented languages These include containers, singleton objects, functors, and iterators These classes require strong encap-sulation and a flexible object-oriented language The fact that these sophisticated classes can be created from elements that already exist pays tribute to the MATLAB design team If these example classes make you wonder what else is possible, they have done their job With a little imagination and creativity, anything is possible
Some of the topics in this section are controversial because they upset the status quo Redefining member-function syntax, adding a pass-by-reference function model, and obtaining protected vis-ibility for variables and functions are probably the most disruptive topics The discussions don’t try to judge whether you should adopt a particular technique, but instead try to demonstrate the flexibility inherent in MATLAB objects and expand the way you think about them Some of the techniques are worthy of adoption, while others would benefit from more support from the language and the user community Like many disruptive technologies, it is hard to know in advance what will be embraced Unlike many languages, MATLAB’s evolution isn’t restricted by an ISO standard
If enough of us adopt a few of these techniques, market forces will ultimately prevail
C911X_S003.fm Page 277 Friday, March 2, 2007 9:09 AM
Trang 5C911X_S003.fm Page 278 Friday, March 2, 2007 9:09 AM
Trang 6Container Class
As a further demonstration of composition, we make an initial foray into designing and imple-menting a general container class A general container is different from an array because it can hold different types A general container is different from a cell array because all objects must descend from the same parent For example, a general cShape container can hold both cStar
and cDiamond objects because they both use cShape as a parent A container is also different from a cell array because a container has a structure-like interface The interface makes a container behave a lot like an object array Rather than looping over each element in the container, clients can use vector syntax Often the loop still exists; however, it is now hidden behind the container’s interface
Developing a set of standard containers compatible with the general computer-engineering literature* or with National Institute of Standards (NIST) definitions** would be an enormous undertaking The goals for this chapter’s container are much less ambitious The primary goal is
to demonstrate one potential use of composition A secondary goal is to produce a container that might be useful as is, or at least produce a container that can be easily improved The container developed for this chapter isn’t perfect, but with what you already know, you can fix all of its deficiencies
19.1 BUILDING CONTAINERS
To implement a container, several details are important First, we need to specify the object type held by the container Any object that passes an isa test for the specified type will be allowed in Thus, objects of the specified type and objects using the specified type as a parent are okay to add
to the container For the example, we will specify cShape as the object type That will allow the container to hold cShape, cStar, and cDiamond objects If we want to create new shape classes, the container will hold them too Of course, these new classes must have cShape somewhere in their hierarchy so that isa(object, ‘cShape’) returns true
The next thing we need to decide is how the container implementation will store the objects MATLAB will not let us use a built-in type, like cell, as a parent, so we must look for an alternative There are two options but both represent compromises The first and probably the most obvious approach stores objects in a private cell array Cell array storage is probably the best approach because it aligns public and private indices One potential problem with this approach is the mismatch among built-in functions like length and size and the number of objects held in the container Of course, we will code around this problem by overloading length and size
We might also want to consider overloading reshape, ndims, numel, num2cell, and
mat2cell, among others
The next potential problem with a private cell array is the index value end Using end to add
a new element to the container should work the same as adding an element to an array For example, the command syntax might look like the following:
* Cardelli, L., and Wegner, P “On Understanding Types, Data Abstraction and Polymorphism,” ACM Computer Survey,
17, 4, December 1985, 471–522.
** http://www.nist.gov/dads/.
C911X_C019.fm Page 279 Friday, March 2, 2007 9:42 AM
Trang 7280 A Guide to MATLAB Object-Oriented Programming
shape_array(end+1) = cStar;
The built-in behavior of end returns the dimension of shape_array, not the dimension of the
private cell array inside shape_array Redefining size and length doesn’t help, but thanks
to the foresight of the MATLAB developers, we can code around this problem too In this situation,
end acts like an operator Like any operator, MATLAB converts end into a function call This
behavior allows us to overload the function end.m to return an appropriate value
An alternate container solution avoids length, size, reshape, and end issues by taking
advantage of the way MATLAB implements structures For example, the container class might
include a private member variable named m O b j e c t After several additions,
class(this(1).mObject) might equal ‘cStar’ and class(this(2).mObject)
might equal ‘cDiamond’ MATLAB allows different object types stored in the mObject element
to coexist As long as we never try to concatenate mObject elements (i.e., [this.mObject]),
everything will work fine With this solution, adding a new object simply increases the size of the
private structure The primary problem with this approach involves repeating the container’s private
structure and the fact that arrays of structures are memory hogs Using repmat can also produce
inconsistent results
Regardless of the approach, we also need to consider concatenation with cat, horzcat, and
vertcat Achieving the best compatibility means supporting the concatenation of a container
and an object and the concatenation of two or more containers We usually don’t want to restrict
the concatenation order, and that means the container must be superiorto the classes it holds
19.2 CONTAINER IMPLEMENTATION
For implementation purposes, this chapter uses the cell-array approach With the cell-array
approach, the container object itself is never empty even when the private cell array contains no
objects This eliminates the potential for empty-object memory errors that sometimes arise.* The
cell-array approach requires a little more work up front, but the result seems to be more robust
compared to the object-array approach After the first container implementation, the added workload
isn’t a problem because most of the tailored functions can be copied, as is, to other container
implementations
The implementation example is organized into three sections The first section focuses on our
standard group-of-eight framework The second section focuses on a set of tailored functions that
overload the behavior of standard MATLAB built-in functions The third section focuses on
cShape-specific functions The implementation of any container can be organized along these
divisions
19.2.1 T HE S TANDARD F RAMEWORK AND THE G ROUP OF E IGHT
Even though a container class is quite different from the other class examples, we don’t have to
code everything from scratch Instead, use Class Wizard to generate the initial set of files and
modify them to suit the needs of the container The constructor, ctor_ini, ctor_1, display,
parent_list, and struct won’t need modifications The remaining group-of-eight functions
— fieldnames, get, set, subsref, and subsasgn — will need container-specific changes
The changes are modest and are relatively easy since the generated code serves as a guide The
data entered into Class Wizard are provided in Table 19.1 through Table 19.4 The list of function
names in Table 19.3 provides a preview of the tailoring to come Fields not listed in the tables
should remain set to their default values The complete Class Wizard mat file and the unmodified
* Versions 7.1 and earlier are not stable when repmat is used to create an object array with a dimension size equal to
zero Returning a so-called empty object from a constructor is particularly bad The fact that a default-constructed container
should be empty makes the repeated-structure approach unreliable in these versions.
C911X_C019.fm Page 280 Friday, March 2, 2007 9:42 AM
Trang 8Composition and a Simple Container Class 281
results are included in /chapter_0/as_generated cShapeArray After entering the data, generate the class
The container itself contains no public member variables, and, as generated by Class Wizard, the public variable sections inside fieldnames, get, and set are empty These sections will not remain empty Instead, these functions will forward public variable requests to the secondary objects stored in the container The Class Wizard cases inside subsref and subsasgn also need some changes The initial code assumes the container itself is an array In reality, the container class is always scalar Changes to subsref and subsasgn use the private cell array to make the container look like an array The dot-reference case is okay because changes to get and set
determine dot-reference behavior The array-reference case needs to access and mutate objects held in the container’s cell array Only the highlights are included in the following sections The fully modified files are included in /chapter_0/@cShapeArray
19.2.1.1 Container Modifications to fieldnames
Since the container itself has no public variables, fieldnames.m doesn’t initially contain a list
of public names This is correct because only the objects held by the container have public variables The container needs to return a list of public names, but it doesn’t need an explicit list Rather than coding an explicit name list inside the container’s version of fieldnames, we simply forward the fieldnames request and collect the result There are two potential targets for the forward: the class type held in this.mType (see Table 19.2) and the objects held in this.mArray (see Table 19.2) Choosing the first returns the public members allocated to the parent These names are guaranteed to exist for every object in the container Choosing the latter also includes parent-class public names, but it might also include public names defined for the children Choosing the latter also means that any particular object may or may not contain every public variable listed This is not necessarily a problem, but it is something that must be considered when container functions are implemented
For cShape and its children, most of the public variables are defined by the parent In this situation, using the container class type held in this.mType is a good choice This choice also
TABLE 19.1 cShapeArray Class Wizard Main Dialog Fields
Class Name cShapeArray Superior To cShape, cStar, cDiamond, double
TABLE 19.2
cShapeArray Private Variable Dialog Fields
passes isa(object, this.mType).
mFigHandle [] Figure handle where all contained shapes
are drawn.
Trang 9282 A Guide to MATLAB Object-Oriented Programming
TABLE 19.3
cShapeArray Public Function Field Values
Function
Name
Input Argument List
Output
FigureHandle
in the container If FigureHandle is not passed
in, draw will manage the use
or creation of a figure window.
Returns an index value consistent with “end.” If n
is not equal to length(size(this.mObject)), some reshaping is done to find the correct value.
Returns the correct length based on the number of objects in the container.
Function is not supported; throws an error if called.
for * Allows multiplication between the container and arrays of doubles.
for * Allows multiplication between the container and arrays of doubles.
Returns the correct ndims value based on the shape of the container’s mObject cell array.
Trang 10Composition and a Simple Container Class 283
allows the container to return a list of names even when the container is empty Add the following command to the end of the initial version of @cShapeArray/fieldnames.m
names = [names; fieldnames(feval(this.mType), varargin{:})];
In this command, feval(this.mType) creates a temporary object and varargin{:}
expands input arguments The complete list of names is created by concatenating the return value with any names that already exist To improve run time, the result could be assigned to a persistent variable Use the profiler to determine when a persistent is warranted
19.2.1.2 Container Modifications to subsref
For dot-reference operations, the container needs to forward the operation to the objects held in the container The best location for the forward isn’t in subsref but rather in get Locating the forward inside get means no changes to subsref’s dot-reference case
For array-reference operations, the input index value is used to return elements in the container’s cell array In the normal situation, the built-in version of subsref and MATLAB’s assignment operator cooperate to return a subset array with the same type as this The container’s array-reference code can’t rely on the same built-in behavior Instead, the code first constructs an empty container and then assigns the indexed subset into the new container’s mArray variable Modifications to subsref’s array-reference case are shown in Code Listing 109
Line 2 instantiates a new container object by calling the constructor Class(this) returns the name of the constructor and feval executes it No arguments are passed with the function call so the result is an empty container Line 3 uses index(1) to get the correct subset out of
TABLE 19.3 (CONTINUED)
cShapeArray Public Function Field Values
Function
Name
Input Argument List
Output
num2cell this, varargin container_
cells
Redefines built-in behavior Use this function to access the container’s entire cell array Function only supports one input argument If you try to pass in a direction, the function will throw an error.
in the container.
size this, varargin varargout Redefines built-in behavior
Returns the correct size array based on the number of objects in the container Reshape this, varargin this Redefines built-in behavior
Reshapes the object cell array.