Reference types, Value types, and Patterns |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
Throughout this material we there will be chapters titled "Patterns and Techniques". A number of such chapters are oriented towards object-oriented design patterns. In Section 16.1 we therefore introduce the general idea of design patterns, and in Section 16.2 we specialize this to a discussion of object-oriented design patterns. In Section 16.3 we encounter the first object-oriented design pattern, the one called Singleton. In Section 16.5 we discuss how to avoid leaking private information from a class.
|
16.1. Design Patterns
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Design patterns originate from the area of architecture, and they were pioneered by the architect Christopher Alexander.
The following is an attempt to give a very dense and concentrated definition of design patterns.
|
Each of the important, underlined words - and a few more - are addressed below:
|
A set of design patterns serve as a catalogue of well-proven solutions to (more or less) frequently occurring problems. A design pattern has a name that eases the communication among programmers. A design pattern typically reflects a solution to a problem, which is non-trivial and distanced from naive and obvious solutions.
16.2. Object-oriented Design Patterns
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Object-oriented design patterns were introduced in the book "Design Patterns - Elements of Reusable Object-Oriented Software" by Gamma, Helm, Johnson and Vlissides. |
Numerous books have been written about design patterns (and other kinds of patterns as well). The book mentioned above, [Gamma96], was the first and original one, and it still has a particular status in the area. It is often referred to as the GOF (Gang of Four) book. The patterns and pattern categories mentioned below stem from the original book.
|
There are patterns in a variety of different areas, and at various levels of abstractions |
16.3. The Singleton pattern
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The concrete contribution of this chapter is the Singleton design pattern. As stated below, use of Singleton is intended to ensure that a given class can be instantiated at most once.
Problem: For some classes we wish to guarantee that at most one instance of the class can be made. Solution: The singleton design pattern |
The idea of Singleton is to remove the constructor from the client interface. This is done by making it private. Instead of the constructor the class provides a public static method, called Instance in Program 16.1, which controls the instantiation of the class. Inside the Instance method the private constructor is available, of course. The private, static variable uniqueInstance keeps track of an existing instance (if it exists). If there already exists an instance, the Instance method returns it. If not, Instance creates an instance and assigns it to the variable uniqueInstance for future use. All this appears in Program 16.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Singleton{ // Instance variables private static Singleton uniqueInstance = null; private Singleton(){ // Initialization of instance variables } public static Singleton Instance(){ if (uniqueInstance == null) uniqueInstance = new Singleton(); return uniqueInstance; } // Methods } | |||
|
Let us program a singleton Die class. It is shown below in Program 16.2. We have already seen the Die class in Section 10.1 (Program 10.1) and Section 12.4 (Program 12.5).
It should be easy to recognize the pattern from Program 16.1 in Program 16.2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using System; public class Die { private int numberOfEyes; private Random randomNumberSupplier; private int maxNumberOfEyes; private static Die uniqueInstance = null; private Die (){ randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks)); this.maxNumberOfEyes = 6; Toss(); } public static Die Instance(){ if (uniqueInstance == null) uniqueInstance = new Die(); return uniqueInstance; } // Die methods: Toss and others } | |||
|
Let us know bring the singleton Die class into action. It is done in Program 16.3. First notice that we cannot just instantiate the singleton Die class. The compiler will complain. In Program 16.3 we attempt to make two dice with use of the Instance method. In reality, the second call of Instance returns the same die as returned by the first call of Instance. Thus, d2 and d3 refer to the same object. The program first tosses the die referred by d2 four times, and next it tosses the die referred by d3 five times. In reality the same die is tossed nine times. The output of the die tossing program is shown in Listing 16.4 (only on web).
Recall our very first class example in Section 10.1. In Program 10.2 the three different Die objects tossed in identical ways. The reason is that they use three separate - but identically seeded - Random objects. The solution is to use a singleton Random class, which ensures that at most a single Random object can exist. The three Die objects will share the Random object. With this organization we solve the "parallel tossing problem". Please consult Exercise 3.7 and its solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using System; class diceApp { public static void Main(){ // Die d1 = new Die(); // Compile-time error: // The type 'Die' has no constructors defined Die d2 = Die.Instance(), d3 = Die.Instance(); for(int i = 1; i < 5; i++){ Console.WriteLine(d2); d2.Toss(); } for(int i = 5; i < 10; i++){ Console.WriteLine(d2); d3.Toss(); } // Test for singleton: if (d2 == d3) Console.WriteLine("d2 and d3 refer to same die instance"); else Console.WriteLine("d2 and d3 do NOT refer to same die instance"); } } | |||
|
1 2 3 4 5 6 7 8 9 10 | Die[6]: 2 Die[6]: 3 Die[6]: 5 Die[6]: 4 Die[6]: 5 Die[6]: 6 Die[6]: 1 Die[6]: 4 Die[6]: 4 d2 and d3 refer to same die instance | |||
|
You may ask if Singleton is important in everyday programming. How often do we have a class that only can give rise to one object? The singleton Die shown above is not a very realistic use of Singleton.
Singleton is probably not the most frequently used pattern. But every now and then we encounter classes, which it does not make sense to instantiate multiple times. In these situations is it nice to know how to proceed. Use Singleton instead of a homegrown ad hoc solution! There are additional details which can be brought up in the context of Singleton, see [singleton-msdn].
16.4. Factory methods
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
As we have seen in Chapter 12, instantiation of classes by use of programmed constructors is the primary means for creation of new objects. In some situations, however, direct use of constructors is not flexible enough. In this section we will see how we can make good use of static methods as a supplementary means for object creation. Such methods are called factory methods.
We have already studied class Point several times, see Section 11.6 and Section 14.6. In the version of class Point shown in Program 16.5 below we need constructors for both polar and rectangular initialization of points. Recall that rectangular represented points have ordinary (x,y) coordinates and that polar represented points have (r,a) - radius and angle - coordinates. If we use two constructors for the initialization, both will take two double parameters. In Program 16.5 we supply an extra enumeration parameter to the last constructor, shown in line 16. This is an ugly solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | using System; public class Point { public enum PointRepresentation {Polar, Rectangular} private double r, a; // polar data repr: radius, angle // Construct a point with polar coordinates public Point(double r, double a){ this.r = r; this.a = a; } // Construct a point, the representation of which depends // on the third parameter. public Point(double par1, double par2, PointRepresentation pr){ if (pr == PointRepresentation.Polar){ r = par1; a = par2; } else { r = RadiusGivenXy(par1,par2); a = AngleGivenXy(par1,par2); } } private static double RadiusGivenXy(double x, double y){ return Math.Sqrt(x * x + y * y); } private static double AngleGivenXy(double x, double y){ return Math.Atan2(y,x); } // Remaining Point operations not shown } | |||
|
In Program 16.6 we show another version, in which the constructor is private. From the outside, the two static factory methods MakePolarPoint and MakeRectangularPoint are used for construction of points. Internally, these methods delegate their work to the private constructor. This is a much more symmetric solution than Program 16.5, and it allows us to have good names for the "constructors" - or more correctly, the factory methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using System; public class Point { public enum PointRepresentation {Polar, Rectangular} private double r, a; // polar data repr: radius, angle // Construct a point with polar coordinates private Point(double r, double a){ this.r = r; this.a = a; } public static Point MakePolarPoint(double r, double a){ return new Point(r,a); } public static Point MakeRectangularPoint(double x, double y){ return new Point(RadiusGivenXy(x,y),AngleGivenXy(x,y)); } private static double RadiusGivenXy(double x, double y){ return Math.Sqrt(x * x + y * y); } private static double AngleGivenXy(double x, double y){ return Math.Atan2(y,x); } // Remaining Point operations not shown } | |||
|
In the web-edition of this material we present another example of factory methods. This example is given in the context of the Interval struct, which we will encounter in Section 21.3. The constructor problem of this type is that structs do not allow parameterless constructors. It is, however, natural for us to have a parameterless constructor for an empty interval. Program 16.7 (only on web) shows a clumsy solution, and Program 16.8 (only on web) shows a more satisfactory solution that uses a factory method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using System; using System.Collections; public struct Interval{ private readonly int from, to; private readonly bool empty; // Construct a non-empty interval [from - to]. public Interval(int from, int to){ this.empty = false; this.from = from; this.to = to; } // Construct an empty interval. The parameter is a dummy. public Interval(int notUsed){ this.empty = true; this.from = 0; // not really used this.to = 0; // not really used } // Illegal constructor. Compile-time error. // Structs cannot contain explicit parameterless constructors public Interval(){ this.empty = true; this.from = 0; this.to = 0; } // Other Interval operations not shown } | |||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System; using System.Collections; public struct Interval{ private readonly int from, to; private readonly bool empty; // Construct a non-empty interval [from - to] if empty is false. // Else construct an empty interval (from and to are not used). private Interval(int from, int to, bool empty){ this.empty = empty; this.from = from; this.to = to; } public static Interval MakeInterval(int from, int to){ return new Interval(from,to,false); } public static Interval MakeEmptyInterval(){ return new Interval(0,0,true); } // Other Interval operations not shown } | |||
|
In Section 32.10 we come back to factory methods, and in particular to an object-oriented design pattern called Factory Method, which relies on inheritance.
Chose a coding style in which factory methods are consistently named: Make...(...) |
16.5. Privacy Leaks
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The discussion in this section is inspired by the book Absolute Java by Walter Savitch. Privacy leaks is normally not thought of as a design pattern.
Problem: A method can return part of its private state, which can be mutated outside the object |
To be concrete, let us look at the problem in context of Program 16.9 and Program 16.10. We use properties in this example. Properties will be introduced in Chapter 18. On the slide belonging to this example we show a version with methods instead. The class Person represents the birth date as a Date object. In order to make our points clear we provide a simple implementation of the Date class in Program 16.9. In real-life programming we would, of course, use C#'s existing DateTime struct. You should notice that the property DateOfBirth in line 17-19 of Program 16.10 returns a reference to a Date object, which represents the person's birthday. As it appears in line 4 in Program 16.10, the date of birth is intended to be private in class Person.
The client of class Person, shown in Program 16.11 mutates the Date object referred by d. The mutation of the Date objects takes place in line 10. This object came from the birthday of person p. Is this at all reasonable to do so, you may ask. I would answer "yes". If you have access to a mutable Date object chances are that you will forget were it came from, and eventually you may be tempted to mutate it.
As shown in the output of the client program, in Listing 16.12, Hanne is now 180 years old. We have managed to modify her age despite the fact the birthday is private in class Person.
As of now we leave it as an exercise to find good solutions to this problem, see Exercise 4.3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class Date{ private ushort year; private byte month, day; public Date(ushort year, byte month, byte day){ this.year = year; this.month = month; this.day = day; } public ushort Year{ get{return year;} set{year = value;} } public byte Month{ get{return month;} set{month = value;} } public byte Day{ get{return day;} set{day = value;} } public override string ToString(){ return string.Format("{0}.{1}.{2}",day, month, year); } } | |||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class Person{ private string name; private Date dateOfBirth, dateOfDeath; public Person (string name, Date dateOfBirth){ this.name = name; this.dateOfBirth = dateOfBirth; this.dateOfDeath = null; } public string Name{ get {return name;} set {name = value;} } public Date DateOfBirth{ get {return dateOfBirth;} } public ushort AgeAsOf(Date d){ return (ushort)(d.Year - dateOfBirth.Year); } public bool Alive(){ return dateOfDeath == null; } public override string ToString(){ return "Person: " + name + " " + dateOfBirth; } } | |||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using System; class Client{ public static void Main(){ Person p = new Person("Hanne", new Date(1926, 12, 24)); Date d = p.DateOfBirth; d.Year -= 100; Console.WriteLine("{0}", p); Date today = new Date(2006,8,31); Console.WriteLine("Age of Hanne as of {0}: {1}.", today, p.AgeAsOf(today)); } } | |||
|
1 2 | Person: Hanne 24.12.1826 Age of Hanne as of 31.8.2006: 180. | |||
|
Exercise 4.3. Privacy Leaks The starting point of this exercise is the observations about privacy leaks on the accompanying slide. Make sure that you understand the problem. Test-drive the program (together with its dependent Person class and Date class) on your own computer. If you have not already done so, read the section about privacy leaks in the textbook! Find a good solution to the problem, program it, and test your solution. Discuss to which degree you will expect that this problem to occur in everyday programming situations. There is no solution to this exercise |
We return to the Date and Person classes in Section 20.4 and Section 20.5. In these sections we also comment on the privacy leak problem.
16.6. References
[Gamma96] | E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns: Elements of Reusable Object-oriented Software. Addison Wesley, 1996. |
[Singleton-msdn] | MSDN: Implementing Singleton in C# http://msdn.microsoft.com/en-us/library/ms998558.aspx |