15.1.1.3 cLineStyle’s get The public variable section for cLineStyle’s get is shown in Code Listing 87.. Code Listing 87, Public Variable Implementation in cLineStyle’s get.m 1 % public-
Trang 115.1.1.2 cLineStyle’s fieldnames
Whereas ctor_ini defines the collection of private variables, fieldnames defines the collec-tion of public variables In this case, there are only three: Color, LineWeight, and LineHan-dle The public variables and the values they hold come directly from the requirements The code
to implement fieldnames for these public variables is shown in Code Listing 86
4 this(1).mColorHsv = [2/3; 1; 1]; % [H S V]’ of border, default is blue
5 this(1).mLineWidth = 1; % line weight: ‘normal’ == 1 ‘bold’
== 3
6 this(1).mLineHandle = []; % handle for shape’s line plot
7 superior = {};
8 inferior = {};
9 parents = {};
10 parent_list(parents{:});
Code Listing 86, Modular Code, cLineStyle’s fieldnames.m
1 function names = fieldnames(this, varargin)
2 names = {};
3
4 % first fill up names with parent public names
5 parent_name = parent_list; % get the parent name cellstr
6 for parent_name = parent_list'
7 names = [names; fieldnames([this.(parent_name{1})],
varargin{:})];
9
10 % returns the list of public member variable names
11 if nargin == 1
12 names = {'Color' 'LineWidth' 'LineHandle'}';
13 else
14 switch varargin{1}
15 case '-full'
16 names = {'Color % double array'
19 case '-possible'
20 names = {'Color' {{'double array (3x1)'}}
24 error('Unsupported call to fieldnames');
26 end
Trang 2The parent-forwarding code in lines 4–8 is not necessary because parent_list returns an empty cellstr It is included because it is part of the standard template When fieldnames
is called with one input argument, line 12 returns a cellstr populated with the three public variable names Lines 16–18 and 20–22 return additional information that depend respectively on
‘-full’ and ‘-possible’ flag values In line 21, note the possible values for LineWeight
are ‘normal’ or ‘bold’
15.1.1.3 cLineStyle’s get
The public variable section for cLineStyle’s get is shown in Code Listing 87 By now, the code in this listing should look familiar The value of found on line 2 is used to control entry into subsequent concealed variable, parent-forwarding, and error code blocks Inside the switch
beginning on line 3, there is a case for each public member variable Lines 4–10 came directly from the public member variable section of cShape’s previous implementation The remaining cases simply map one private variable into one public variable This public variable section is just about as easy as it gets The remaining sections of cLineStyle’s get function use code from the standard group-of-eight template
Code Listing 87, Public Variable Implementation in cLineStyle’s get.m
1 % public-member-variable section
2 found = true; % otherwise-case will flip to false
3 switch index(1).subs
9 varargout = mat2cell(rgb, 3, ones(1, size(rgb,2)));
11 case 'LineWidth'
17 case 'LineHandle'
24 found = false; % didn't find it in the public section
25 end
Trang 315.1.1.4 cLineStyle’s set
The public variable section for cLineStyle’s set is shown in Code Listing 88 Compared to the same cases in get, the code in this listing is a little more involved but that is primarily due to input-value checking The listing should still be familiar, particularly after you find all of the common landmarks The value of found on line 2 is used to control entry into subsequent concealed variable, parent-forwarding, and error code blocks Inside the switch beginning on line 3, there
is a case for each public member variable
Code Listing 88, Public Variable Implementation in cLineStyle’s set.m
1 % public-member-variable section
2 found = true; % otherwise-case will flip to false
3 switch index(1).subs
4
6 if length(index) > 1
8 rgb = subsasgn(rgb, index(2:end), varargin{:});
12 hsv = mat2cell(hsv, 3, ones(1, size(hsv,2)));
15 for k = 1:length(this(:))
'Color'));
20
22 if length(index) > 1
23 error([index(1).subs ' does not support indexing']);
25 if any([varargin{:}] < 1)
26 error([index(1).subs ' input values must be >= 1']);
28 if length(varargin) ~= 1 && length(varargin) ~=
length(this(:))
29 error([index(1).subs ' input length is not correct']);
31 [this.mLineWidth] = deal(varargin{:});
Trang 4Lines 5–14 came directly from the public member variable section of cShape’s previous implementation Lines 15–19 loop over the objects in this and set the handle graphic’s
‘Color’ attribute to the newly assigned value The new RGB value is accessed by calling get
on each cLineStyle object
Lines 21–37 deal with LineWidth Lines 22–30 check the inputs for several conditions First,
no additional indexing beyond the initial dot-reference name is allowed Second, all of the width values must be greater than or equal to one Third, the length of the input must be one or equal to the size of the object array If the input values pass these checks, line 31 deals the new line-width values into this.mLineWidth Lines 32–37 then loop over the objects in this and set the handle graphic’s LineWidth value The new value is accessed by calling get on each cLineStyle
object
Lines 39–46 deal with LineHandle Lines 40–45 check the inputs for several conditions First, no additional indexing beyond the initial dot-reference name is allowed Second, the length
of the input must be 1 or equal to the size of the object array If the input values pass these checks, line 46 deals the new line-width values into this.mLineHandle The remaining sections of
cLineStyle’s set function use code from the standard group-of-eight template
15.1.1.5 cLineStyle’s private/ctor_2
With cLineStyle, we have an opportunity to create a constructor helper function that takes two input arguments: color and width The standard constructor is designed to call the helper as long
as it has the correct name In this case, the name is /private/ctor_2.m The implementation
is shown in Code Listing 89
The function definition on line 1 defines three inputs because this is passed along with the two inputs originally passed into the constructor Lines 2 and 3 use set to assign the RGB color
38
39 case 'LineHandle'
40 if length(index) > 1
41 error([index(1).subs ' does not support indexing']);
43 if length(varargin) ~= 1 && length(varargin) ~=
length(this(:))
44 error([index(1).subs ' input length is not correct']);
46 [this.mLineHandle] = deal(varargin{:});
47
49 found = false; % didn't find it in the public section
50 end
Code Listing 89, Modular Code, cLineStyle Constructor, private/ctor_2.m
1 function this = ctor_2(this, color, width)
2 this = set(this, ‘Color’, color);
3 this = set(this, ‘LineWidth’, width);
Trang 5value and the line weight Using set works correctly here because the main constructor converted
this into an object before it called the helper By using this two-input constructor, cShape’s constructor can specify both the color and line width for default cShape objects
15.1.2 U SING A P RIMARY C S HAPE AND A S ECONDARY C L INE S TYLE
To create the composition we simply add a cLineStyle object to cShape’s collection of private member variables This addition occurs inside @cShape/private/ctor_ini.m Since the object’s structure has been modified, don’t forget to clear classes before using the new
cShape class With the addition of a cLineStyle object, we can also eliminate mColorHsv
and mPlotHandle Of course, we also have to change any member function that relies on
mColorHsv and mPlotHandle as private variables Group-of-eight functions subject to change include ctor_ini.m, get.m, and set.m Member functions outside the group of eight that require work include draw.m, mtimes.m, and reset.m because they currently use
mPlotHandle Changes to private variables affect cShape’s internal implementation They do not affect cShape’s public interface
Even though cLineStyle includes a public variable for LineWidth, LineWidth does not automatically become part of cShape’s public interface Due to composition, cShape’s
cLineStyle object is private and so is its interface As it stands, clients will not be able to change the shape’s line width If we want to permit clients to change the width, we need to include this ability by adding to cShape’s public interface There are several ways to implement the addition; however, they all boil down to a choice between two alternatives One alternative exposes the entire secondary object, while the other only exposes part of the secondary object No single choice is always right or always wrong Part of the design effort involves deciding between the two Exposing the secondary object is easy: treat the object like any other private variable by including cases in get and set to access and mutate the object as a whole This approach can be convenient because it automatically allows the primary class to evolve along with the secondary object Of course, this approach also introduces a high level of coupling between the primary and secondary implementations This approach can also be problematic because complete exposure typically introduces a read-modify-write approach to mutation Since multiple levels of dot-refer-ence indexing on arrays are not allowed,* clients have to copy the object to a local variable, modify the copy, and write the modified copy back into the primary object This process is convenient for the primary-object developer but tedious for primary-object clients Rather than exposing the whole secondary object, it is usually better to use parent–child inheritance
Exposing only part of the secondary object is also easy but it generally requires more work
In this case, the secondary object and its public members remain hidden behind the primary object’s interface If a client needs a value from the secondary object, a primary-object member function always operates as an intermediary The client asks the primary object for a value and in turn, the primary object’s function asks the secondary object for a value When the secondary object returns
a value, the primary object’s function forwards the value to the client Here, the primary object always maintains control over the interface The primary-object interface chooses which elements
to expose and which to leave hidden This interface also chooses how to expose secondary object elements The primary object’s interface can rename elements and modify their formats The example code in this chapter demonstrates this important capability
To add line-width capability to cShape we are not going to expose the entire secondary object, but rather we are going to define a public member variable named LineWeight Clients can set
LineWeight to one of only two values: ‘normal’ or ‘bold’ The LineWeight case
inside set will convert these strings into LineWidth integers, and the LineWeight case
* This statement applies to built-in functions In the tailored versions of subsref and subsasgn, a limit was implemented
to match built-in behavior It is possible to relax the limit at the risk of introducing nonstandard syntax.
Trang 6inside get will convert LineWidth integers back into strings Clients see the width as only
‘normal’ or ‘bold’, but inside cShape’s member functions, integer values are available from the secondary object Implementing this behavior will demonstrate how the primary object’s interface can easily buffer the interaction between client and secondary object
15.1.2.1 Composition Changes to cShape’s ctor_ini.m
The cLineStyle object needs a private variable, and two existing private variables need to be removed The cLineStyle object will be stored in the private variable mLineStyle With this change, mColorHsv and mPlotHandle are no longer needed because the secondary object manages their values The modified constructor helper is shown in Code Listing 90 In line 9, a call to cLineStyle’s constructor initializes the mLineStyle object The first argument is an RGB color, and the second is the default line width Since two arguments are passed, cLine-Style’s constructor will use /@cLineStyle/ctor_2 to complete the assignment
15.1.2.2 Adding LineWeight to cShape’s fieldnames.m
Adding a new public variable always adds a new name to the cellstr lists returned by field-names The modified code is shown in Code Listing 91 Additions to the previous version occur
in lines 12, 19, and 24 Note in line 24, the possible values for LineWeight are listed as
‘normal’ or ‘bold’ These possible values are displayed in response to set(cShape )
Code Listing 90, Modular Code, Modified Implementation of cShape’s ctor_ini.m
1 function [this, superior, inferior] = ctor_ini
2 this = struct([]); % initially empty structure
3 this(1).mDisplayFunc = []; % function handle for non-default display
4 this(1).mSize = ones(2,1); % scaled [width height]’ of bounding box
5 this(1).mScale = ones(2,1); % [width height]’ scale factor
6 this(1).mPoints =
7 [imag(exp(j*(0:4:20)*pi/5)); real(exp(j*(0:4:20)*pi/5))];
8 this(1).mFigureHandle = []; % handle to the figure's window
9 this(1).mLineStyle = cLineStyle([0;0;1], 1); % color blue, width 1
10 superior = {'double'};
11 inferior = {};
12 parents = {};
13 parent_list(parents{:});
Code Listing 91, Adding LineWeight to cShape’s fieldnames.m
1 function names = fieldnames(this, varargin)
2 names = {};
3
4 % first fill up names with parent public names
5 parent_name = parent_list; % get the parent name cellstr
6 for parent_name = parent_list'
Trang 715.1.2.3 Composition Changes to cShape’s get.m
A change to the way ColorRgb is stored and a new public member variable trigger changes to
cShape’s get.m These changes are isolated to two public member variable case statements The case blocks for ‘ColorRgb’ and ‘LineWeight’ are shown in Code Listing 92
7 names = [names; fieldnames([this.(parent_name{1})],
varargin{:})];
9
10 % returns the list of public member variable names
11 if nargin == 1
12 names = {'Size' 'ColorRgb' 'Points' 'LineWeight'}';
13 else
14 switch varargin{1}
15 case '-full'
16 names = {'Size % double array'
20 case '-possible'
21 names = {'Size' {{'double array (2x1)'}}
26 error('Unsupported call to fieldnames');
28 end
Code Listing 92, Adding ColorRgb and LineWeight Cases to cShape’s get.m
1 case 'ColorRgb'
8 case 'LineWeight'
12 line_style = [this.mLineStyle];
13 line_width = [line_style.LineWidth];
14 varargout = cell(1,length(this(:)));
15 varargout(line_width == 1) = {'normal'};
Trang 8The new case code for ‘ColorRgb’ is shorter because the conversion from HSV to RGB now occurs inside the secondary object Line 5 creates an array of cLineStyle objects, and line
6 uses dot-reference syntax to access Color values Line 5 is necessary because {this.mLin-eStyle.Color} throws an error if this is a nonscalar array Line 6 is composition in action The dot-reference syntax is converted into a function call that looks like
varargout = {subsref(line_style, substruct(‘.’, ‘Color’))};
and MATLAB uses path rules to find the appropriate version of subsref Since line_style
is a cLineStyle object, MATLAB finds and executes @cLineStyle/subsref.m The case code for ‘LineWeight’ was added in lines 8–17 Line 12 creates an array of
cLineStyle objects, and line 13 uses dot-reference syntax to access LineWidth values Line
14 preallocates varargout, and lines 15–16 fill varargout with strings Line 15–16 use a logical array to select the indices that receive ‘normal’ or ‘bold’ Elements where the == test
is true are assigned, and elements where the == test is false are not assigned Line 15 tests with 1 and assigns ‘normal’ Line 16 tests with 3 and assigns ‘bold’ Clients never see values
of 1 or 3 but rather only values of ‘normal’ or ‘bold’
15.1.2.4 Composition Changes to cShape’s set.m
A change to the way ColorRgb is stored and a new public member variable also trigger changes
to cShape’s set.m These changes are also isolated to the same two public member variable
case statements The case blocks for ‘ColorRgb’ and ‘LineWeight’ are shown in Code Listing 93 This listing appears more complicated than the previous version In reality, the increase
in code length is primarily due to rigorous input value testing
16 varargout(line_width == 3) = {'bold'};
17 end
Code Listing 93, Adding ColorRgb and LineWeight Cases to cShape’s set.m
1 case 'ColorRgb'
2 index(1).subs = 'Color';
3 line_style = set([this.mLineStyle], index, varargin{:});
4 line_style = num2cell(line_style);
5 [this.mLineStyle] = deal(line_style{:});
6
7 case 'LineWeight'
8 if length(index) > 1
9 error([index(1).subs ' does not support indexing']);
11 if length(varargin) ~= 1 && length(varargin) ~=
length(this(:))
12 error([index(1).subs ' incorrect input size']);
14 normal_sieve = strcmp(varargin, 'normal');
15 bold_sieve = strcmp(varargin, 'bold');
16 if ~all(normal_sieve | bold_sieve)
17 error([index(1).subs ' input values not ''normal'' or
''bold''']);
Trang 9The new case code for ‘ColorRgb’ is shorter because the conversion from RGB to HSV now occurs inside the secondary object Line 3 is about to forward the set arguments to the secondary object, and line 2 prepares for this forward by modifying index Line 2 changes the dot-reference name to ‘Color’ because that is the name used in the secondary object’s interface There is no reason to check the length of index because line 3 puts that ball in cLineStyle’s court Line 3 concatenates the cLineStyle objects and calls set MATLAB finds and executes
@cLineStyle/set.m and assigns the modified object into line_style This is again com-position in action Line 4 changes line_style into a cell array, and line 5 deals the modified objects back into their original locations
The case code for ‘LineWeight’ was added in lines 7–24 Lines 8–18 perform various input value checks First, lines 8–10 disallow indexing deeper than the first dot-reference level Next, lines 11–13 make sure the number of arguments in varargin is compatible with the length
of the object array One input argument is okay because it will be assigned to every object in the array With more than one argument, the number must equal the length of the object array Lines 14–18 check the string values in varargin Elements of normal_sieve will be true only
at indices where the input string is identically equal to ‘normal’ Similarly, elements of
bold_sieve will be true only at indices where the input string is identically equal to ‘bold’
If normal_sieve and bold_sieve are both false at the same index value, something is wrong with the input On line 16, normal_sieve or bold_sieve is used to determine when something is wrong
If the input values pass all the tests, line 19 overwrites ‘normal’ with 1 and line 20 overwrites
‘bold’ with 3 Line 22 is about to toss everything into cLineStyle’s court, and line 21 prepares for this by changing the dot-reference name from LineWeight to LineWidth Now the set
in line 22 will correctly return a modified version of the object This is another example of composition Line 23 converts the line_style array into a cell array, and line 24 deals the modified objects back into their original locations
15.1.2.5 Composition Changes to cShape’s draw.m
When a shape object is drawn, saving its plot handle makes it easy to change the shape’s line attributes Previously, the plot handle was saved in a private variable With composition, the plot
/@cStar/draw is shown in Code Listing 94 In line 1, the handle is stored in the secondary object; and in line 5, the LineWidth value stored in the secondary object is added as an argument
/@cLine-Style/subsref
15.1.2.6 Composition Changes to cShape’s Other Member Functions
In mtimes.m, the plot handle is used to set the shape’s new corner points In reset.m, the plot handle is assigned an empty value In both functions, this(k).mPlotHandle has been changed
to this(k).mLineStyle.LineHandle In the case of mtimes, MATLAB uses
19 varargin(normal_sieve) = {1};
20 varargin(bold_sieve) = {3};
21 index(1).subs = 'LineWidth';
22 line_style = set([this.mLineStyle], index, varargin{:});
23 line_style = num2cell(line_style);
24 [this.mLineStyle] = deal(line_style{:});
Trang 10eStyle/subsref to access the graphics handle In the case of reset, MATLAB mutates the graphics handle using /@cLineStyle/subsasgn Both represent composition
15.2 TEST DRIVE
Using a cLineStyle object in composition involved some significant changes to cShape’s implementation A private variable for the secondary object was added, and several private variables were deleted The first few commands in the test drive need to confirm that these structural changes did not change cShape’s public interface or alter its behavior Repeating the commands from Code Listing 84 and comparing the outputs will serve this purpose For easy reference, these commands are included as the first eighteen lines in Code Listing 95 Executing lines 1–18 results
in the same figures previously shown in Figures 14.1 through Figure 14.4 You can also experiment with other elements included in the public interface
Code Listing 94, Modified Implementation of cShape’s draw.m
1 this(k).mLineStyle.LineHandle = plot(
2 this(k).mSize(1) * this(k).mPoints(1,:),
3 this(k).mSize(2) * this(k).mPoints(2,:),
4 'Color', this(k).mLineStyle.Color,
5 'LineWidth', this(k).mLineStyle.LineWidth
6 );
Code Listing 95, Chapter 15 Test Drive Command Listing for Composition
1 >> cd '/oop_guide/chapter_15'
2 >> clear classes; fclose all; close all force; diary off;
3 >> star = [cStar cStar];
4 >> star(2).ColorRgb = [1; 0; 0];
5 >> star(1) = 1.5 * star(1);
6 >> star = draw(star);
7 >> diamond = [cDiamond; cDiamond];
8 >> diamond(1).ColorRgb = [0; 1; 0];
9 >> diamond(2).Size = [0.75; 1.25];
10 >> diamond = draw(diamond);
11 >>
12 >> shape = {star diamond};
13 >> fig_handle = figure;
14 >> for k = 1:length(shape)
15 shape{k} = draw(shape{k}, fig_handle);
16 end
17 >> star = draw(star);
18 >> star(1).Title = 'Shooting Star';
19 >>
20 >> shape{1}(1).LineWeight = 'bold';
21 >> shape{1}(1)
22 ans =