ghytred.com


NewsLook for Outlook

Patterns and OO
DesignPrinciples1
The Web Site you seek
Cannot be located, so
Waste your time here

Contents

Design Principles, Techniques and Guidelines

This is a loosely-coupled set of ideas I have gleaned over the years, mostly from Web Sites but also from usenet, mailing lists, books, and almost any other format which supports information exchange. There are many relevant ideas floating around; these are some of the ideas I agree with which have given me value in my work.

These ideas overlap.

CRC Cards

See D Butler's page at California Poly for an overview.

CRC stands for 'Class, Responsibilities, Collaborators'. The idea is that each class is assigned an index card (a small one) on which is written its name and its responsibilities. If there isn't room on the card to write them all, the class has too many responsibilities and should be split into two or more classes.

A particular use case is played out: each class has someone 'operating' it, saying things like 'I've got this message from you, so I do that to it and pass it on to you and you'. The collaborators are written on the card and the use case continues. At the end a clear idea of the responsibilities and collaborators of each class in the use case should emerge � as should indications that some classes are over- or under-used and should be split or merged into another one.

Personally I have never actually done a CRC session, but I do play a very similar game mentally when designing. The aim is to get a set of focused classes with minimal collaborators � high cohesion, low coupling.

The Open-Closed Principle

This comes from Bertrand Meyer, author of 'Object Oriented Software Construction' and the Eiffel language (see Design By Contract below) among other things.

Paraphrased, the principle is:

Classes should be Open for Extension, but Closed to Modification

In other words, you can add functionality, but should not change existing, used, functions. (Note that 'used' word: of course code that is un-used should be cut out. With that understood I'll drop the word for brevity's sake.)

Changing existing code risks breaking clients of a class. If it doesn't break the clients, then it complicates the code needlessly; so don't do it. Use abstractions instead:

  • Add an abstract super-class and descend the existing class from it. Add new functionality to a new class descending from the abstract.
  • Abstract the existing class's signature to an Interface and make the existing class implement it. Change the clients to use the Interface not the class. Add functionality in a new class which also implements the interface. Further changes just use the Interface and the clients are unaffected.

When to use inheritance and when an Interface? Use inheritance when it's useful: when there is common code in the two or more concrete classes which can be concentrated in the abstract super class.

You may need to change the clients when applying the first change, which can cause problems, but subsequent changes will be trouble free.

You will probably need to add a Factory class to create the appropriate class, returning that class as the super class or the interface. It may be appropriate to change the constructor of the concrete classes to internal so that they can only be created by the factory class and not directly by the clients (this is great help when converting to an abstract interface from a single concrete class: the compiler will show you where it is used!).

Following on from this principle there are a couple of corollaries:

  • member fields must not be public
  • no global variables
  • Late-binding or reflection should be used cautiously.

The Dependency Inversion Principle

This comes from Robert C Martin (aka Uncle Bob). He originally came up with it in the context of large C++ projects which have their own specific problems due to the structure of the language; these problems do not apply so much to C# but the principle is still valid.

Simply stated:

  • High level modules should not depend on Low level modules; they should both depend on abstractions
  • Abstractions should not depend on details; details should depend on abstractions.

If you want to re-use a high level module (in the same system or another) you are quite often not able to because although it does the right things it uses the wrong low-level classes to do it. (For instance, you have a high-level class which gathers disparate sets of data from many sources and writes the formatted data to a file. You now want to write it to a Queue or email it, or something.) In this situation you need to decouple the main functionality (gathering and formatting the data) from the low-level functionality (writing it to a file). Do this by interposing an Interface or an abstract class. The high-level class uses the interface; the low-level class implements it and other appropriate implementers of the interface can be used at run-time. The two classes are now decoupled and a possibly harmful or problematic dependency is eliminated.

You may need to create adaptors for the low-level classes which implement the interface and translate the interface's methods appropriately for the actual low-level classes which do the work, hiding the details of the work from the abstraction. You will probably need Factory classes to instantiate the adaptors.

Uncle Bob also has the 'Interface Segregation principle' which is worth a mention here. It basically says to fit the Interface to the Client, not the other way around. Don't make one all-purpose adaptor interface which has different bits used by different clients; if there are different uses of the base functionality make a separate adaptor for each use. This can be done either by having different adaptors for each case, or one adaptor implementing Interfaces designed for each case.

The Liskov Substitution Principle

Barbara Liskov presented this idea in 1988 with a Computer Science definition which doesn't mean much to me. Translated it reads:

Anything which uses a base class must be able to use objects of a derived class without knowing it

This seems obvious but does have some subtleties. These arise out of a different mistake which I'll explain with an example: the good old Square/Rectangle problem.

We'll descend Square from Rectangle in this example (the problem arises whichever way we do it and mathematically a square is a rectangle, not the other way around). Rectangle looks like this (pseudo-code):

	public class Rectangle {
		public int Height { get; set; }
		public int Width { get; set; }
		public int Area { get; }
	}
			

To implement Square, the Height and Width set properties must call each other � otherwise it stops being a square. Clients don't know this, though. A client which sets the Height to 4 and the width to 5 will end up with a Rectangle of unexpected area � which may break the client. Clearly, then, despite presenting the same interface to clients we are breaking this Principle - and more importantly this client. In software terms, a Square is not a Rectangle when areas have to be considered. (If the clients did not use the area we might be OK.)

The lesson is that not all classes which share an interface or signature can be related.

The mistake made is basing the model on 'real world' things. In the real world (whatever 'real' means to mathematical constructs) a Square is a Rectangle. In software, a Square object may or may not be a Rectangle object depending on how you use it: context dictates this relationship.

The Law of Demeter

This is more a style guide, for me, than a hard and fast rule. I should note though that it is reported that systems which stick to it rigidly are less complex and error-prone than systems which ignore it; this is a good reason to take it seriously.

The simple statement of the Law (aka LoD) is: Only talk to your friends. Brad Appleton has a very informative page about it. A quote:

Its essence is the 'principle of least knowledge' regarding the object instances used within a method. The idea is to assume as little as possible about the structure and properties of instances and their subparts. Thus, it is okay for me to request a service of an objects instance, but if I reach into that object to access another sub-object and request a service of that sub-object, I am assuming knowledge of the deeper structure of the original object that was made available to me.

The rule may be expressed thus (again from Brad's page):

A method 'M' of an object 'O' should invoke only the methods of the following kinds of objects:

  • itself
  • parameters
  • any objects it creates itself
  • its own components (i.e. members)

This means that expressions of the form

	myComponent.Yours.Theirs.WhoseIsThis.DoSomething()

should not be used. Unfortunately, this form has to be used in some circumstances (e.g. DataSet.Tables[0].Rows[i][j]). Also, a strict interpretation would say that something created for you by a Factory object should not be used either.

For these reasons I call it a style guide rather than a rule not to be broken. If I find myself using expressions like the DataSet on my own classes I stop and wonder whether my design is as simple and clear as it should be.

There are some implications of the LoD; again from Brad's page:

  1. Whenever an object needs to request a service of some other external object, this external service request should be encapsulated in an internal non-public method of the object. This allows derived classes to override the service request with a more specialized one (it is also a use of the GoF Template pattern).
  2. Whenever an object needs to instantiate some other externally associated object, it should do so using a non-public method to perform the instantiation. This allows derived classes to instantiate a more specialized object if needed.

This is good advice.

Design by Contract

This also comes from Bertrand Meyer (the Open-Closed Principle above is interleaved with DbC: DbC is a way to enforce, in a sense, the OCP).

DbC assumes that for each method in a class the are pre-conditions and post-conditions: things about the class that must be true before and after the method runs. (The main point of the Eiffel language is that it offers native support for this concept, throwing exceptions if a pre- or post-condition is broken.)

This is possible to code into C# classes (and other languages) by using various Debug methods called at the start and end of each method which check the conditions. These methods and the calls to them would not be included in the release versions of the system. In C#, these methods would be marked with the [Conditional('Debug')] attribute.

There is an additional rule in DbC to do with inheritance which enforces the OCP:

When overriding a method in a sub-class, you may replace its pre-condition by a weaker one (not a stronger one), and replace its post-condition with a stronger one (not a weaker one).

When interacting with a derived class, the client cannot be expected to obey stronger pre-conditions than the base class (which it what it uses it as). The derived class must accept anything that the base class would, but it can accept less. Also, the derived class must conform to all the post-conditions of the base, otherwise the client could be embarrassed.

This interacts with the LSP. The post-conditions for Square.Width are weaker than those for Rectangle.Width: the rectangle post-condition includes 'old.Height == current.Height' � i.e.the Height is not affected by the width. This post-condition is not implemented by Square and so fails the DbC test and also the LSP.

Another advantage of DbC - its main one in fact - is that by thinking about, defining, and enforcing the pre- and post-conditions the object is guaranteed to be in a 'useable' state at all times: if the pre-conditions for an operation are not met, the operation is not carried out; if the post-conditions are not met, the operation is rolled back. (In fact, most implementations would throw an exception if post-conditions were not met; after that it's up to the client to behave accordingly.)

What makes this all easy! (Well, easier�)

The more focused a class is, the easier these guidelines are to implement. This gets back to the first section in this document: CRC cards. The great advantage of CRC Cards is that they produce classes with one responsibility: classes that do one thing, and do it very well. Such classes make pre- and post-conditions much easier to define; consequently the Open-Closed principle is easier to stick to and the LSP more likely to be obeyed. The LoD is not necessarily going to be kept, but a large number of small focused classes do make it easier to avoid breaking it than a smaller number of sprawling classes that encompass too much.

How do you recognise a class which is doing too much? If the pre- or post-conditions of each method are difficult to figure out. The sheer size of the class also helps here: I once heard of a Smalltalk application which was just about as good as it gets; classes had an average of 8 methods each of which had an average of 10 lines of code. That's my personal aim. I won't reach it (Smalltalk is capable of producing much smaller classes than C#), but you have to have a target. The point when I know that a class is not focused enough is when I can't see a complete method on the screen and when I have to search (using find, or drop-down boxes) for the location of a method I want to look at, or I really can't remember what a method is called even when I haven't got a hangover.

Arthur J Riel's book, OO design Heuristics has a lot of very good stuff. See here for my review of it.

Finally, another technique which helps � so much that it should be mandatory � is refactoring. But that's a subject for another week.

Return to top