As we have already seen, the built-in version of struct returns the names and values of private member variables.. Unlike the help for fieldnames, help struct does not promise to return
Trang 194 A Guide to MATLAB Object-Oriented Programming
6.5 INDEPENDENT INVESTIGATIONS
1 In the ‘-full’ case, use class to inspect and assign the field’s type What are you going to do for object arrays?
2 Modify display.m to take advantage of fieldnames
Trang 2In the previous chapter, we patched a hole in MATLAB’s default encapsulation by tailoring
fieldnames In this chapter, we patch another hole by tailoring struct As we have already seen, the built-in version of struct returns the names and values of private member variables
In fact, struct’s default behavior represents a risky situation because clients can use it to gain access to private data We need to eliminate this possibility by developing a type-specific version
of struct.m
As a bonus, a standard function that returns an object’s public structure is broadly useful For example, look back at the scalar case of the tailored version of display The strategy of allowing MATLAB to perform most of the formatting requires a structure of public variables At that time, public structure generation was coded directly in line and we could not easily make use of it in developer view Further proliferation of in-line structure-generation code makes class extensions very tedious and error prone Consolidating structure generation into one function makes a lot of sense We can even take advantage of the tailored version of fieldnames so that even public names are not directly coded into struct
7.1 STRUCT
While we could easily write a specific get function for this task (e.g., getPublicStructure), MATLAB already defines a suitable function The built-in version of struct already returns a structure associated with the object The built-in version will also operate on objects Unlike the help for fieldnames, help struct does not promise to return a structure of “public data fields.” The help files for struct describe a function that converts an object into its equivalent structure Here our idea of “equivalent structure” and MATLAB’s idea are definitely not the same
In our world, the structure should contain public member variables; however, the built-in version
of struct returns a structure made up of the private variables The fieldnames function was bad enough, but the struct function is even more despicable It returns both the names and the values of the private variables!
While it is still true that MATLAB prevents changes to the private data, that does not prevent clients from using the values and even passing around the structures created from the object Here is yet another place where MATLAB seems very lax about preventing client access to private data We need to reinforce this area because the potential result of such permissiveness can be devastating If left unchecked, clients will use this back door to destroy most of the benefits of encapsulation Once a client ties code to the private structure returned by the default version of struct, later attempts to reorganize or improve the class will result in broken code and angry clients While it might indeed be their fault, it becomes our problem I have personally witnessed such chaos and can tell you it is no easy chore to clean it up Preventing it from happening in the first place is a much better plan Like always, if we don’t like what the built-in version gives us, we simply tailor the function
to suit our purposes In this particular case, tailoring is not perfect because we can’t prevent clever
or devious clients from using builtin to tie their code to an object’s private data A client can bypass all our carefully crafted member functions by using, for example,
shape_struct = builtin(‘struct’, shape)
When a client uses builtin in this way, shape_struct is not an object, but rather a structure With a structure, the client loses the ability to call member functions, but in return gains the ability C911X_C007.fm Page 95 Thursday, March 1, 2007 2:15 PM
Trang 396 A Guide to MATLAB Object-Oriented Programming
to read and write any field in the structure Mutation does not carry into the object, but once a client has a structure, the object is usually forgotten Unfortunately, there is no way to prevent a client from using builtin
For this very reason, clients should be told about builtin and strongly cautioned against its use There is no conceivable reason for a client to circumvent the behavior of any type-specific member function by using a call to builtin This decision belongs to a class developer and not
a client Class developers can and often do use builtin to coerce MATLAB into doing work for them; however, if class developers are doing their job properly, it is extremely rare for a client to have the same need This is particularly true with struct
7.2 CODE DEVELOPMENT
The first time we noticed a need for a function like struct was during the implementation of
display We didn’t yet have such a function, so we resorted to building the structure in line The code used in display built a structure for a scalar object, and it will serve as a decent starting point for the tailored, nonscalar version of struct All we have to do is adapt and generalize The important lines of code from display are repeated below
This code has three problems:
1 the use of public names
2 in-line conversions from private variables to public
3 only works on scalar objects
The first problem is easily solved by calling our newly tailored version of fieldnames We never need to hard-code the public names because we can easily get them in the form of a cellstr The second problem is solved by realizing that subsref already includes the necessary conver-sions In fact, lines 2–3 above already use subsref Code in the generalized version of display
must always use subsref to obtain public member values Using both fieldnames and
subsref allows for a non-class-specific implementation The third problem isn’t hard to solve, but it does require a for loop The initial implementation is shown in Code Listing 41
1 public_struct.Size = this.mSize;
2 public_struct.ColorRgb =
Code Listing 41, Initial Implementation for struct.m
1 function public_struct = struct(this)
2 names = fieldnames(this); % tailored version returns public names
3 values = cell(length(names), length(this(:))); % preallocate
4 for k = 1:length(names)
5 [values{k, :}] = subsref(this(:), substruct('.', names
{k}));
7 public_struct = reshape(cell2struct(values, names, 1), size(this));
C911X_C007.fm Page 96 Thursday, March 1, 2007 2:15 PM
Trang 4struct.m 97
As the solution to problem 1, line 3 calls fieldnames to get a cellstr of public member
variable names Now that we have the names, we need values Line 3 preallocates an empty cell array
where the values will be stored The size of the cell array is length(names)×length(this)
or, equivalently, the number of public variables × the number of objects in the array Line 4 loops over
the names, and line 5 assigns public values We avoid a double loop because the whole object array is
passed into subsref and subsref returns length(this) answers The magic of list expansion
allows the assignment of multiple cell array elements Finally, line 7 uses cell2struct to generate
the structure as a row vector, and reshape converts the row into the same shape as this
The function cell2struct is very powerful but somewhat obscure The structure values
and names are passed into cell2struct as cell arrays The third argument tells cell2struct
how to assign values in the values cell array to fieldnames in the names cell array In the case
of line 7, a 1 breaks up values into columns Each value in the column is associated index for
index with a name in names After cell2struct, the resulting structure array will be 1 ×
size(values,2)
7.3 THE TEST DRIVE
The test drive for struct is easy because there aren’t many options The results are shown in
Code Listing 42 Line 3 builds an object, and lines 4–5 set some values Line 6 returns the public
structure Because the returned value is a structure, we can use the full_display utility function
if we want more detail Lines 10–12 show the details, and the values match those assigned in lines
4–5 Line 13 uses a horzcat operator to create an object array Line 14 obtains the structure
array, and lines 19–23 display the details Again, the detailed values match the values expected
Code Listing 42, Chapter 7 Test Drive Command Listing for struct.m
1 >> cd /oop_guide/chapter_7
2 >> clear classes; clc
3 >> shape = cShape;
4 >> shape.Size = 2;
5 >> shape.ColorRgb = [1;1;1];
6 >> struct(shape)
10 >> full_display(ans)
11 ans.Size = [2 2]';
12 ans.ColorRgb = [1 1 1]';
13 >> shape = [cShape shape];
14 >> struct(shape)
16 1x2 struct array with fields:
19 >> full_display(ans)
20 ans(1, 1).Size = [1 1]';
21 ans(1, 1).ColorRgb = [0 0 1]';
22 ans(1, 2).Size = [2 2]';
23 ans(1, 2).ColorRgb = [1 1 1]';
C911X_C007.fm Page 97 Thursday, March 1, 2007 2:15 PM
Trang 598 A Guide to MATLAB Object-Oriented Programming
7.4 SUMMARY
This function, while simple, is important because it closes a huge hole in the object’s encapsulation Now clients are presented with a uniform public front because display, fieldnames, and struct all advertise the same variables The tailored version of struct takes advantage of code
modularity relying on fieldnames and subsref to return public information Notice that no class-dependent data are contained inside the implementation of struct The tailored version also supports modularity by allowing other functions to obtain the public structure without having
to resort to in-line code Member functions can now take advantage of this support, and eventually
we will modify some of the code in display to use both struct and fieldnames
7.5 INDEPENDENT INVESTIGATIONS
1 Try to overload builtin and see what happens
2 Modify display.m to take advantage of struct
3 Modify developer_view (a subfunction inside display.m) to take advantage of
fieldnames.
Trang 6One of the exercises back in Chapter 3 asked you to examine the possible benefits of developing one get and one set instead of a get and set pair for every public variable That was before the implementations of subsref and subsasgn Since subsref and subsasgn provide such
an elegant, natural interface, perhaps there is no longer a need for get or set There are still benefits, but it seems the most compelling reasons were stolen away by subsref and subsasgn
In this chapter, we will examine some of the benefits and define implementations for get and set
that add quite a lot of flexibility The functions get and set complete the membership roles for the group of eight
8.1 ARGUMENTS FOR THE MEMBER FUNCTIONS GET AND SET
A general get and set pair is still useful to both developers and clients A get and set
pair helps simplify syntax and increases code modularity In special circumstances, get and set
can be used to “conceal” object data So-called concealed data are variables that shouldn’t be part
of the typical interface but still might be needed from time to time A get and set pair can provide abbreviated information about the class in the same way that get(0) and set(0) provide information about the command environment Finally, defining a get and set pair allows tab completion to work with objects Without a get and set pair, tab completion displays a list of private variables By closing another gap in MATLAB’s default encapsulation, the deal for devel-oping get.m and set.m is sealed
8.1.1 F OR D EVELOPERS
Switch statements inside subsref and subsasgn convert between private data and the public interface Often, the conversion code is simple and can be included directly in each case In these cases, private-public conversion can only be accomplished using subsref or subsasgn For clients, operator-syntax conversion automatically formats the arguments From inside the class,
subsref or subsasgn syntax is cumbersome Using substruct to pack the index helps, but dot-reference access to public variables should be easier For example, the current implementation
of display uses the command
[values{k, :}] = subsref(this(:), substruct(‘.’, names{k}));
Using get makes the command easier to read In that case, the command uses the modified syntax
[values{k, :}] = get(this(:), names{k});
While this change might seem insignificant, it has a big impact on maintainability because the get
syntax is easier to read at a glance
Instead of “inventing” new function names like get and set, we could have tailored get-field and setfield There are several reasons why get and set are better First, in version 7.0, getfield and setfield are deprecated functions, i.e., they may not exist in future versions
We certainly don’t want to develop new classes based on obsolete functions Second, the argument syntax for getfield and setfield is too complicated By using get and set, we don’t need
to support a complicated predefined syntax Third, if we don’t overload getfield and set-field, MATLAB automatically converts them into subsref and subsasgn calls Finally, we C911X_C008.fm Page 99 Thursday, March 1, 2007 2:24 PM
Trang 7100 A Guide to MATLAB Object-Oriented Programming
are not really inventing the names get and set These functions are already used to manipulate
the command environment and for graphics handles Indeed, the environment and graphics handles
look a lot like objects Even more important, tab completion uses get and set to obtain the
completion list
Clients primarily use access-operator syntax This opens the possibility of concealing special
functionality inside get or set For example, even when
shape.mDisplayFunc = ‘developer_view’;
throws a “field not found” error, mutation can be accomplished by calling set directly The
equivalent direct-call syntax looks like the following:
shape = set(shape, ‘mDisplayFunc’, ‘developer_view’);
To make this happen, both subsasgn and set must be properly implemented This kind of
concealed functionality might be used to support development, test, or quality assurance
Devel-opers, testers, and inspectors usually demand more access to internal states compared to normal
clients It is also an easy way to include developer-level capability without advertising it as public
Concealed functionality still has public visibility because there are no formal checks that can be
used to limit access to these so-called special-access variables This makes it difficult to decide
when to promote the visibility of a private variable to concealed
Consider mDisplayFunc as an example How do we decide whether to keep
mDisplay-Func private or promote its visibility to concealed or even public? If we leave it as private,
developers must modify the constructor or add a member function to assign a value If we promote
it to public visibility, then we need to document its behavior along with that of all the other public
variables An interface description that includes many obscure or special-use functions can make
a simple class appear overwhelming True public visibility also requires more diligence by
devel-opers to trap error conditions and protect class integrity Concealed visibility is a gray area There
is more latitude in the documentation as well as with safety The answer usually comes down to
purpose and complexity In general, it is unwise to add too much concealed functionality You want
clients to respect the boundaries imposed by the interface, and adding a lot of concealed functionality
promotes bad habits
8.1.2 F OR C LIENTS
We generally discourage clients from using get and set in their source code; however, during
development, get and set can provide a quick summary of the public variables Using get and
set with graphic handles provides a good example The command window is another example.
Open MATLAB and enter the commands get(0) and set(0) in the command window For
reference, the first few lines of each command are shown in Code Listing 43 With get(0), you
get a display that includes all the “get-able” variables along with their current values The output
is very similar to a structure display With set(0), you get a display that includes all the
“set-able” variables along with a list of possible assignment values Many times this is the only reminder
you need The displays from graphics handles are similar Adding this capability to every class
makes the use of objects more agreeable With the added benefits, clients might actually prefer
objects to structures
The outputs in Code Listing 43 are also interesting for another reason The list of “get-able”
variables is different from the list of “set-able” variables Just like the public variables in our objects,
certain variables are read-write while others are read-only Now that we are comfortable with
encapsulation, we recognize this as the rule rather than an exception Both the command window
settings and graphics handles seem much more like objects than like structures
C911X_C008.fm Page 100 Thursday, March 1, 2007 2:24 PM
Trang 8get.m, set.m 101
8.1.3 T AB C OMPLETION
A very convenient command-line option is “tab completion.” With tab completion, you type the first few characters of a command or variable name and hit the Tab key At that point, MATLAB will either fill in the rest of the command or display a list of items that begin with the characters typed This helps speed development and reduces the burden of remembering complete command
or variable names
Tab completion calls on get and set to populate the name list If we don’t implement get
and set, the name list isn’t empty Instead, it contains the names of the class’ private variables
An empty list would be better because the list of private names represents another breach of encapsulation In this case, the list of private names is also worthless If we accept a name from the list, we will be presented with an error message telling us that it is illegal to access an object’s private variables There is really no choice: we must tailor get and set for every class
8.2 CODE DEVELOPMENT
Inside get and set, a switch statement will be used to steer the execution into the correct public case In fact, the switch statements needed by get and set already exist inside the dot-reference sections of subsref and subsasgn, respectively The function calls are a little different, but if
we grab the dot-reference switch code and wrap it in a new interface the implementation is quick and easy During the implementation for get and set, you will notice a lot of code duplication between get and subsref and between set and subsasgn In fact, the dot-reference switches can be replaced by calls to get or set In this chapter, the focus centers on the implementations
of get and set In the next chapter, we will clean up code in several group-of-eight functions by replacing in-line code with calls to fieldnames, struct, get, and set
Code Listing 43, Output Example for Built-In get and set
1 >> get(0)
10
11 >> set(0)
Trang 9102 A Guide to MATLAB Object-Oriented Programming
8.2.1 I MPLEMENTING GET AND SET
The implementations for get and set need to do three things: access and mutate public variables, access and mutate concealed variables, and display summary information To access and mutate public variables, we will copy the switch cases from subsref and subsasgn into get and
set To implement concealed variables, get and set need code to allow them to figure out from
where they were called The easiest way to figure this out is to examine the format of the input arguments The input arguments also steer the execution to display summary information Too few input arguments will trigger get and set to display a summary
Because the arguments are used to steer the execution, we need to examine them more closely The function definitions listing all arguments are given by the following:
The functions are member functions, and consequently the object occupies the first argument position In addition, in both functions the index variable occupies the second position The format
of index will be used to grant or deny access to the concealed variables When index is a simple string, get and set will allow access to concealed variables In this case, switch statements can use the value without modification because it already names the desired variable When index
is not a string, get and set will deny access to concealed variables In this case, get and set assume a substruct format for index The element at index (1) will specify dot-reference
so that substruct, index (1).type will be equal to ‘.’ and index (1).subs is the string value used for the switch
At first, allowing two formats for index might seem odd, but it turns out to be very convenient
A typical call to get might look something like the following:
shape_size = get(shape, ‘Size’);
Here, passing the public name as the index simplifies the syntax The simple arguments also match the typical syntax for getfield We don’t specify a dot-reference operator because get
is specifically tailored to return public variables The dot-reference operator is inherent in its operation
On the other hand, when a public variable is accessed using operator syntax, for example,
shape_size = shape.Size;
MATLAB automatically packages the indices into a substruct The substruct index is passed into subsref or subsasgn, and the dot-referenced public variable name is contained in
index(1).subs Under these conditions, subsref and get should behave the same way The
same is true for subsasgn and set Because they share the same behavior, it is smart to let them share the code that implements the behavior One way to do this is to allow get and set to perform the actual dot-reference indexing and let subsref call get and subsasgn call set
We can even use the substruct index passed into subsref or subsasgn as a cue to disallow access to concealed variables In this chapter, we won’t make changes to subsref or subsasgn, but we will implement get and set so that the changes are easier to make when we get to
§10.1.1.3 and §10.1.1.4
The number of input arguments is used to select between access/mutate or summary display Calling get or set with only one argument, for example,
get(shape) % shape is an object of type cShape
set(cShape) % constructor creates a temporary object
1 function varargout = get(this, index)
2 function varargout = set(this, index, varargin)
Trang 10get.m, set.m 103
results in a summary display of all “get-able” or “set-able” public variables Calling set with two arguments, for example,
set(cShape, ‘Size’)
results in a summary display of the indexed variable only
When this is a scalar object, get returns one value and set assigns one value When this
is nonscalar, get returns a cell array composed of one value from each object in the object array
In addition, when this is nonscalar, set potentially needs more than one assignment value In the calling syntax, the values appear as a separated list MATLAB packages the comma-separated arguments into individual cells of varargin
Execution based on both the number and type of the input arguments leads to an implementation with several logical paths Supporting public and concealed variables adds a few more The implementations will be easier to follow if we first construct a high-level block diagram
We are now in a position to draw a high-level block diagram for these functions The block diagrams are shown in Figure 8.1 and Figure 8.2 The logical flow is similar for both because in many ways they both do the same sort of things They have to check the number of input arguments, search the public names, determine whether concealed access is allowed, throw an error for unknown names, and convert between public and private data The diagram for set is a little more compli-cated because of its support for both full and subset summary displays Similarities allow the implementations to share the same general structure The initial implementations of get and set are shown in Code Listing 44 and Code Listing 45
FIGURE 8.1 get’s functional block diagram.
Allow Concealed Access?
no
yes
yes
return varargout Error
no
Variable Name is a public variable?
get
nargin == 1
?
no yes
Display summary get info
Get values from the object and put them
in varargout
Variable Name is a concealed variable?