Time Class Case Study: Overloaded Constructors
Time Class Case Study Overloaded Constructors
As you know, you can declare your own constructor to specify how objects of a class should be initialized. Next, we demonstrate a class with several overloaded constructors that enable objects of that class to be initialized in different ways. To overload constructors, simply provide multiple constructor declarations with different signatures. Recall from Section 7.12 that the compiler differentiates signatures by the number, types and order of the parameters in each signature.
Class Time2 with Overloaded Constructors
By default, instance variables hour, minute and second of class Time1 (Fig. 9.1) are initialized to their default values of 0 (which is midnight in universal time). Class Time1 does not enable the class's clients to initialize the time with specific non-zero values. Class Time2 (Fig. 9.7) contains five overloaded constructors for conveniently initializing its objects in a variety of ways. The constructors ensure that each Time2 object begins in a consistent state. In this application, four of the constructors invoke a fifth constructor, which in turn calls method SetTime. Method SetTime invokes the set accessors of properties Hour, Minute and Second, which ensure that the value supplied for hour is in the range 0 to 23 and that the values for minute and second are each in the range 0 to 59. If a value is out of range, it is set to 0 by the corresponding property (once again ensuring that each instance variable remains in a consistent state). The compiler invokes the appropriate constructor by matching the number and types of the arguments specified in the constructor call with the number and types of the parameters specified in each constructor declaration. Note that class Time2 also provides properties for each instance variable.
Figure 9.7. Time2 class declaration with overloaded constructors.
(This item is displayed on pages 421 - 422 in the print version)
1 // Fig. 9.7: Time2.cs 2 // Time2 class declaration with overloaded constructors. 3 public class Time2 4 { 5 private int hour; // 0 - 23 6 private int minute; // 0 - 59 7 private int second; // 0 - 59 8 9 // Time2 parameterless constructor: initializes each instance variable 10 // to zero; ensures that Time2 objects start in a consistent state 11 public Time2() : this( 0, 0, 0 ) { } 12 13 // Time2 constructor: hour supplied, minute and second defaulted to 0 14 public Time2( int h ) : this( h, 0, 0 ) { } 15 16 // Time2 constructor: hour and minute supplied, second defaulted to 0 17 public Time2( int h, int m ) : this( h, m, 0 ) { } 18 19 // Time2 constructor: hour, minute and second supplied 20 public Time2( int h, int m, int s ) 21 { 22 SetTime( h, m, s ); // invoke SetTime to validate time 23 } // end Time2 three-parameter constructor 24 25 // Time2 constructor: another Time2 object supplied 26 public Time2( Time2 time ) 27 : this( time.Hour, time.Minute, time.Second ) { } 28 29 // set a new time value using universal time; ensure that 30 // the data remains consistent by setting invalid values to zero 31 public void SetTime( int h, int m, int s ) 32 { 33 Hour = h; // set the Hour property 34 Minute = m; // set the Minute property 35 Second = s; // set the Second property 36 } // end method SetTime 37 38 // Properties for getting and setting 39 // property that gets and sets the hour 40 public int Hour 41 { 42 get 43 { 44 return hour; 45 } // end get 46 // make writing inaccessible outside the class 47 private set 48 { 49 hour = ( ( value >= 0 && value < 24 ) ? value : 0 ); 50 } // end set 51 } // end property Hour 52 53 // property that gets and sets the minute 54 public int Minute 55 { 56 get 57 { 58 return minute; 59 } // end get 60 // make writing inaccessible outside the class 61 private set 62 { 63 minute = ( ( value >= 0 && value < 60 ) ? value : 0 ); 64 } // end set 65 } // end property Minute 66 67 // property that gets and sets the second 68 public int Second 69 { 70 get 71 { 72 return second; 73 } // end get 74 // make writing inaccessible outside the class 75 private set 76 { 77 second = ( ( value >= 0 && value < 60 ) ? value : 0 ); 78 } // end set 79 } // end property Second 80 81 // convert to string in universal-time format (HH:MM:SS) 82 public string ToUniversalString() 83 { 84 return string.Format( 85 "{0:D2}:{1:D2}:{2:D2}", Hour, Minute, Second ); 86 } // end method ToUniversalString 87 88 // convert to string in standard-time format (H:MM:SS AM or PM) 89 public override string ToString() 90 { 91 return string.Format( "{0}:{1:D2}:{2:D2} {3}", 92 ( ( Hour == 0 || Hour == 12 ) ? 12 : Hour % 12 ), 93 Minute, Second, ( Hour < 12 ? "AM" : "PM" ) ); 94 } // end method ToString 95 } // end class Time2 |
Class Time2's Constructors
Line 11 declares a parameterless constructora constructor invoked without arguments. Note that this constructor has an empty body, as indicated by the empty set of curly braces after the constructor header. Instead, we introduce a use of the this reference that is allowed only in the constructor's header. In line 11, the usual constructor header is followed by a colon (:), then the keyword this. The this reference is used in method-call syntax (along with the three int arguments) to invoke the Time2 constructor that takes three int arguments (lines 2023). The parameterless constructor passes values of 0 for the hour, minute and second to the constructor with three int parameters. The use of the this reference as shown here is called a constructor initializer. Constructor initializers are a popular way to reuse initialization code provided by one of the class's constructors rather than defining similar code in another constructor's body. We use this syntax in four of the five Time2 constructors to make the class easier to maintain. If we needed to change how objects of class Time2 are initialized, only the constructor that the class's other constructors call would need to be modified. Even that constructor might not need modificationit simply calls the SetTime method to perform the actual initialization, so it is possible that the changes the class might require would be localized to this method.
Line 14 declares a Time2 constructor with a single int parameter representing the hour, which is passed with 0 for the minute and second to the constructor at lines 2023. Line 17 declares a Time2 constructor that receives two int parameters representing the hour and minute, which are passed with 0 for the second to the constructor at lines 2023. Like the parameterless constructor, each of these constructors invokes the constructor at lines 2023 to minimize code duplication. Lines 2023 declare the Time2 constructor that receives three int parameters representing the hour, minute and second. This constructor calls SetTime to initialize the instance variables to consistent values. SetTime, in turn, invokes the set accessors of properties Hour, Minute and Second.
Lines 2627 declare a Time2 constructor that receives a Time2 reference to another Time2 object. In this case, the values from the Time2 argument are passed to the threeparameter constructor at lines 2023 to initialize the hour, minute and second. Note that line 27 could have directly accessed the hour, minute and second instance variables of the constructor's time argument with the expressions time.hour, time.minute and time.secondeven though hour, minute and second are declared as private variables of class Time2.
|
Notes Regarding Class Time2's Methods, Properties and Constructors
Note that Time2's properties are accessed throughout the body of the class. In particular, method SetTime assigns values to properties Hour, Minute and Second in lines 3335, and methods ToUniversalString and ToString use properties Hour, Minute and Second in line 85 and lines 9293, respectively. In each case, these methods could have accessed the class's private data directly without using the properties. However, consider changing the representation of the time from three int values (requiring 12 bytes of memory) to a single int value representing the total number of seconds that have elapsed since midnight (requiring only 4 bytes of memory). If we make such a change, only the bodies of the methods that access the private data directly would need to changein particular, the individual properties Hour, Minute and Second. There would be no need to modify the bodies of methods SetTime, ToUniversalString or ToString, because they do not access the private data directly. Designing the class in this manner reduces the likelihood of programming errors when altering the class's implementation.
Similarly, each Time2 constructor could be written to include a copy of the appropriate statements from method SetTime. Doing so may be slightly more efficient, because the extra constructor call and the call to SetTime are eliminated. However, duplicating statements in multiple methods or constructors makes changing the class's internal data representation more difficult and error-prone. Having the Time2 constructors call the three-parameter constructor (or even call SetTime directly) requires any changes to the implementation of SetTime to be made only once.
|
Also notice that class Time2 takes advantage of access modifiers to ensure that clients of the class must use the appropriate methods and properties to access private data. In particular, the properties Hour, Minute and Second declare private set accessors (lines 47, 61 and 75, respectively) to restrict the use of the set accessors to members of the class. We declare these private for the same reasons that we declare the instance variables privateto simplify code maintenance and ensure that the data remains in a consistent state. Although the methods in class Time2 still have all the advantages of using the set accessors to perform validation, clients of the class must use the SetTime method to modify this data. The get accessors of properties Hour, Minute and Second are implicitly declared public because their properties are declared publicwhen there is no access modifier before a get or set accessor, the accessor inherits the access modifier preceding the property name.
Using Class Time2's Overloaded Constructors
Class Time2Test (Fig. 9.8) creates six Time2 objects (lines 914) to invoke the overloaded Time2 constructors. Line 9 shows that the parameterless constructor (line 11 of Fig. 9.7) is invoked by placing an empty set of parentheses after the class name when allocating a Time2 object with new. Lines 1014 of the application demonstrate passing arguments to the other Time2 constructors. C# invokes the appropriate overloaded constructor by matching the number and types of the arguments specified in the constructor call with the number and types of the parameters specified in each constructor declaration. Line 10 invokes the constructor at line 14 of Fig. 9.7. Line 11 invokes the constructor at line 17 of Fig. 9.7. Lines 1213 invoke the constructor at lines 2023 of Fig. 9.7. Line 14 invokes the constructor at lines 2627 of Fig. 9.7. The application displays the string representation of each initialized Time2 object to confirm that each was initialized properly.
Figure 9.8. Overloaded constructors used to initialize Time2 objects.
(This item is displayed on pages 425 - 426 in the print version)
1 // Fig. 9.8: Time2Test.cs 2 // Overloaded constructors used to initialize Time2 objects. 3 using System; 4 5 public class Time2Test 6 { 7 public static void Main( string[] args ) 8 { 9 Time2 t1 = new Time2(); // 00:00:00 10 Time2 t2 = new Time2( 2 ); // 02:00:00 11 Time2 t3 = new Time2( 21, 34 ); // 21:34:00 12 Time2 t4 = new Time2( 12, 25, 42 ); // 12:25:42 13 Time2 t5 = new Time2( 27, 74, 99 ); // 00:00:00 14 Time2 t6 = new Time2( t4 ); // 12:25:42 15 16 Console.WriteLine( "Constructed with: " ); 17 Console.WriteLine( "t1: all arguments defaulted" ); 18 Console.WriteLine( " {0}", t1.ToUniversalString() ); // 00:00:00 19 Console.WriteLine( " {0} ", t1.ToString() ); // 12:00:00 AM 20 21 Console.WriteLine( 22 "t2: hour specified; minute and second defaulted" ); 23 Console.WriteLine( " {0}", t2.ToUniversalString() ); // 02:00:00 24 Console.WriteLine( " {0} ", t2.ToString() ); // 2:00:00 AM 25 26 Console.WriteLine( 27 "t3: hour and minute specified; second defaulted" ); 28 Console.WriteLine( " {0}", t3.ToUniversalString() ); // 21:34:00 29 Console.WriteLine( " {0} ", t3.ToString() ); // 9:34:00 PM 30 31 Console.WriteLine( "t4: hour, minute and second specified" ); 32 Console.WriteLine( " {0}", t4.ToUniversalString() ); // 12:25:42 33 Console.WriteLine( " {0} ", t4.ToString() ); // 12:25:42 PM 34 35 Console.WriteLine( "t5: all invalid values specified" ); 36 Console.WriteLine( " {0}", t5.ToUniversalString() ); // 00:00:00 37 Console.WriteLine( " {0} ", t5.ToString() ); // 12:00:00 AM 38 39 Console.WriteLine( "t6: Time2 object t4 specified" ); 40 Console.WriteLine( " {0}", t6.ToUniversalString() ); // 12:25:42 41 Console.WriteLine( " {0}", t6.ToString() ); // 12:25:42 PM 42 } // end Main 43 } // end class Time2Test
|
Default and Parameterless Constructors
|