Common Design Patterns
A number of “design patterns” are described in this section. I put the words “design patterns” in quotation marks, because the patterns are not described in a standard manner established by the GoF book.
The Framework Design Guidelines book calls to divide your API into two parts: the “factored types” and the “aggregated components”. They even recommend to put them in two separate assemblies (although Microsoft’s own development teams don’t seem to pay close attention to this one). The “factored types” are your regular classes that follow normal principles of software engineering: let one class be responsible for one thing, avoid having objects in invalid states, etc. “Aggregated components”, on the other hand, are classes for which these principles are not mandatory. These classes are designed for ease of (first) use.
Aggregated components should follow the Create-Set-Call pattern, along these lines:
' Create: must have a default constructor Dim queue As New MessageQueue() ' Set properties queue.Path = queuePath queue.EncryptionRequired = EncryptionRequired.Body queue.Formatter = New BinaryMessageFormatter() 'Call actions - minimum number of parameters queue.Send("Hello World") queue.Close()
To quote the book:
It is very important that aggregate components support this usage pattern. The pattern is something that users… expect and for which tools, such as Intellisense and designers, are optimized.
The authors proceed to explain that the same component object may be used to accomplish different tasks, or be in different “modes”. E.g. the same object may act as a client and send messages, and then become a server and receive messages. Switching between the “client” and the “server” modes should be, as far as possible, transparent to the user.
The “aggregated components” model has the following problems:
- It allows and even encourages to create objects in invalid state
- The code is difficult to maintain
- The code is difficult to unit-test
- Putting non-visual components on a form encourages mixing presentation and business logic.
If I forget to specify the
Path property, it will pop up only at runtime. If my queue happens to be used only in some kind of rare case (e.g. exception handling), the first people who will notice that error may be the users in the field. This is exacerbated by the difficulty of unit testing this kind of code.
If tomorrow I decide to use a new message queue implementation, or I decide that all my queues need another kind of encryption, I will have to hunt individual usages of the Queue component change them one by one. I may even have to (gasp!) use text search and modify designed-generated code by hand. This, however, is a problem of any code that directly calls
new on concrete types. Using dependency injection in conjunction with factory pattern solves this problem, but it does make the code look scary. Boo!
This code is also difficult to unit test. How do I make sure that the right properties are set, and the right actions are called? There is no way to mock the component if it is instantiated using direct
Putting non-visual components on a form encourages mixing of presentation and business logic. This is easy and fun when your project is small, but this becomes an issue for bigger projects. If we wanted toy projects, Visual Basic 6 would be enough for everybody.
The bottom line is: aggregated components make programming look easy, but they don’t make it easy for real. They are somewhat like alcohol. You drink it, you get all excited, but then you get a hangover. The authors make a good point that if the first use is difficult, there may be no second use. Yet, I am still not convinced that fooling the users by saying “look, it’s easy, you just drag this thingie to a form, make a couple of clicks and then you’re done” is a very good idea.
The Async Pattern
I have two issues with .NET async pattern:
- It is too complicated: both to use and to implement.
- The object that was called is not part of
IAsyncResult, which makes it not self-contained
The async pattern talks to the user in too many ways. There is the object on which we perform the operation, the user callback, the user state, the async result, the completion event (as in “wait handle”), the EndXXX method, completed synchronously, not completed synchronously… Too many moving parts. It is therefore difficult to get right, and difficult to use. It is especially difficult to decorate, i.e. build an async operation that is based on another async operation. Also, it is not type safe – if you implement this pattern, user may pass to you any old
IAsyncResult, not necessarily yours.
In addition to all that,
IAsyncResult does not keep track of the object it belongs to. If an async result was returned from
foo.BeginXXX, it can be fed only to
foo.EndXXX. It does not make sense to feed it to any other object. It would be nice if
IAsyncResult carried the object information with it.
The authors strongly recommend to implement finalizers on your disposable objects and declare
void Dispose(bool disposing).
First of all, I don’t like the parameter name. If I call
Dispose(false), does it mean I am not really disposing the object? I am just playing pretend? The parameter should be something like
It seems to be a prevailing opinion in the .NET and Java community that finalizers are error-prone, performance-problematic and generally bad. The authors do recognize that and warn against implementing finalizers. With this recommendation the whole pattern makes even less sense. If the finalizer is not there, why bother with the Boolean parameter and the
SuppressFinalize? If the finalizer is there, finalization code must adhere to very strict guidelines to be correct, so mixing “regular” code and restricted finalization code in the same method is quite illogical, let alone dangerous.
p 261: Do prefer constructors to factories, as they are generally more usable, consistent, and convenient than specialized constructing mechanisms.. Using explicit
new also ties user code to a particular implementation type, deprives that type of using non-static context information, and results in a code that is difficult to unit-test. Java approach where the API contains only interfaces, that may return other interfaces, and “new” is called only on the first “root” object seems to be more robust. From the other hand, this often leads to neglect of actual implementation details.
p 263: Consider naming factory types by concatenating the name of the type being created and
Factory. This assumes the factory creates objects of only one type, which may or may not be the case.
Optional Feature Pattern
p. 264-266: Optional feature pattern is a nice name for fat interface. They advocate using fat interfaces with
IsReadOnly type of discriminators that allow to query whether a feature is actually supported. This might be good in some cases, but not in others. The judgment is very difficult here.
p. 267-268: Avoid making public methods virtual. Well, how about
ToString() :-)? In general, I personally prefer to use aggregation instead of template methods. The duality of protected vs. public interface is difficult to keep clean. The protected interface is frequently an afterthought and often is quite fuzzy and fragile. E.g. some people see nothing wrong with having protected data members. Also, code that uses inheritance is difficult to test. Inheritance of graphic objects like forms is especially harmful. I have seen many cases when derived classes want the graphical representation, but they don’t want a big chunk of the parent’s behavior (the “refused bequest” smell), so developers go out of their way with protected methods to compensate for this problem.
p. 269 Consider naming protected virtual methods that provide extensibility points for nonvirtual members by suffixing the nonvirtual member name with “
SetBoundsCore). I find the
Core suffix quite confusing. I personally prefer
Impl. By the way, is “suffixing” even a word? 🙂
p. 270: Do prefer using
TimeSpan to represent timeout time. This is all cool, but
TimeSpan does not define a value for
Infinite. I had this exact problem just the other day. I ended up defining timeout as integer number of milliseconds, so the client could use
Timeout.Infinite when needed.