I don't mind admitting that I am a huge fan of Robert (Uncle Bob) C. Martin. I've read so much of his published material and I think he has
a great deal of important stuff to say. His videos are fantastic too, you must have seen them? Well if not then I strongly suggest you
search for him on youtube; there's oodles of great stuff on there all ready for your viewing pleasure.
I have always taken everything I've read or heard from Uncle Bob very seriously. I've been reading a lot of stuff on his blog (I hope he doesn't mind the link, but I'm sure it will be fine)
Anyway it is with great reluctance that I posit the notion with this question ...
I lapped it up, every word that had been carefully selected so as not to leave room for ambiguity. I thought it was a great post and that it helped me to realize just what it was that was bugging me about .Net. There was this little itch when I consider C#. Don't misunderstand me at all, C# is my language of choice these days, but there was something niggling away at the back of my mind and reading that blog entry brought it right to the fore.
Part of the dialogue in this particular article goes as follows:
Anyway I was so fired up about this that I started to put together a blog entry of my own, and it was to be called
I hadn't thought it through (not the title, that I was certain of), I had not properly thought through what I was saying. Not because I didn't want to, not because I didn't think it needed thinking about because clearly it was being thought about!, but rather I did not think it through entirely simply because I believed it to be true and besides, Uncle Bob said it, and I don't believe he would publish something like this before having thought through all of the possibilities.
I had even written this because I believed it so strongly.
Entire articles, and books can, have been, and will continue to be written about each of these principles in isolation; I've even written a bit about some of these within my blogs -and a really great book is Dependency Injection in .Net . For this reason I am not going to go into the detail of these principles here, but instead within this article I am going to concentrate on the Single Responsibility Principle. So, let's expand a little upon what this means to us as software engineers.
With this definition of the SRP in mind, we will take a look at an example of how the lack of support for multiple inheritance in .Net makes this principle impossible to be adhered to. By way of example we will consider the Observer pattern.
But first ... Some might argue that Multiple inheritance is available in .Net, it's just that only one base class can be a concrete/abstract, and the others need to be interfaces (now, in C++ terms, for example, that is actually multiple inheritance, because an interface is a class whose members are all virtual). But this does not help us in my example though
And then I turned my attention to ...
You should understand that the discussion was based around the Observer Pattern because that was the example which Uncle Bob had used in his post. I wasn't going to take his example code because I felt it needed doing to prove anything, and so I started to write some code in order to illustrate the point ...
The first thing I needed was something to observe, so I figured "what could be worse than a robot?". It is a very simple robot, and it has no functionality beyond knowing both its X- and Y- coordinates. We can implement this as a struct because it is little more than a wrapper for a couple of coordinates.
Because we want to be able to place the robot at various coordinates perhaps we should expose the "location" related functionality via an interface (remember the I of SOLID), and because we might want to use this "location" functionality interface for other POCOs the interface makes use of Generics.
It is important to use a Generic interface in this case because we want to be able to return an object of the type we are locating.
Next on the agenda for me is enabling the robot to move.
There are options here: We could either, add some movement methods to the Robot (and consequently polluting our Robot class with functionality which it does not need in order to fulfill its purpose), or we could create another object responsible for moving the Robot.
The Robot itself already has one responsibility (i.e. knowing where it is), so clearly adding movement functionality to Robot is adding a further responsibility. Currently the Robot is a small and concise unit and is able to function perfectly in accordance with our requirements, so instead we will create another object whose single responsibility is moving a Robot. We will define an interface to describe this functionality, adorne an existing type with extended functionality we will once again create a Generic interface.
And then of course comes the implementation of said interface, which is responsible for nothing beyond moving an object of type <T>. Below then is an example of how we can adhere to the SOLID Principles whilst adding new functionality to our logical model; at the same time we have also adhered to the Interface Segregation Principle, and the Open/Closed Principle
Now that we have a POCO and the ability to move it, we come to the heart of this discussion. This is the part that Uncle Bob tells us is no good in .Net, based on the lack of support for multiple inheritance. I must admit that I was pretty much sold on the idea myself, until I set about getting on and writing it myself. However, I wasn't sure that there was nothing amiss with Uncle Bob's code, because, hang on a minute we've just done exactly what he said we can't do; though maybe that statement is a bit misleading, what he actually said is that we could not do it cleanly, i.e. without repetitious code.
It was relatively straight forward getting the movement functionality into play without modifying our POCO, and so the IObservable<T> interface shoud be no more difficult. - Can you see me changing my mind?
The "stuff" to help us implement this pattern which is part of the package (read .Net Framework) from Microsoft comes in the form of a couple of interfaces.
Obviously we don't have to use these interfaces, but they have been included in the Framework from 2.0 for a reason -because they are used extensively within the Windows Presentation Foundation (WPF) Framework and help us to implement the Model-View-ViewModel (MVVM) pattern quite nicely (Did you know that? Yes, of course you did!)- so we will do the "done thing" and use these interfaces instead of creating our own, which of course means that the code we come up with could be plugged straight into a WPF application should we so choose. So without further ado, let's look at how we we might go about implementing the Observer pattern and how we might use these interfaces as part of that implementation.
First of all we'll enable observation of the Robot quite generically, because the Observable functionality is common to all observable things, i.e. the functionality will stay the same, no matter what type of object is being observed.
We should note here that for the sake of this article we will assume that we know how to take care of any threading issues we might have, and we can omit to include any threading code for fear of detracting from the point under discussion.
Just look at this implementation! It is so clean that we can even hand off the responsibility of ending a subscription to a discrete object. What on earth was Uncle Bob talking about?
This is brilliant, we've now encapsulated three discrete areas of functionality in three objects which each have a single responsibility, and none of the added functionality has interfered with the original POCO, the POCO is still a POCO. All that remains is for us to compose an object which will give all of this functionality to a Robot, and we can achieve that end with the following class.
But now comes the "messy bit" ... Unlcoe Bob was right after all. Here I am trying to compose an object and yet in order to do so I have to provide delegation methods for all of the functionality I want to make available, because: I cannot extend two classes. If extend the interfaces (which is my only option) I need to provide members which will delegate to objects within the composite
The sole responsibility of the ObservableRobot class is to make a composite object consisting of a Robot, and both an IMoveable<T>, and an IObservable<T> interface instance.
We can only achieve clean and well structured code, which adheres to our SOLID Principles whilst using the .Net Framework to a degree. The issue is cleanliness, and the need to (superfluously) re-expose discrete functionality from the items of which the new structure is composed. However, I am unaware of anything which would cause one to note that we have violated any of the SOLID Principles, I also believe that the code is clean and concise. It is uncluttered and highly maintainable in my opinion.
But let's not get ahead of ourselves here, we still have to create the clients, or the Observers ...
Because we have such a clean code-base, creating the client(s) for our Observable objects is a bit of a breeze. We'll define an abstract class first of all. Why is this?. Well we might want some common functionality in our observers and at some point we will most likely want to just treat these objects as observers, not worrying about their specializations. In this case we want all of our observers to have a name, so that they can identify themselves in our sample program.
Here then is our abstract observer ...
This is our first concrete observer. It's sole purpose is to let us know of the Robot's current location. So, when the OnNext() member is invoked from the Observable object the LocationReporter emits a message to the screen indicating the Robot's X- and Y-coordinates.
And this is our second concrete observer. This object's sole purpose is to emit a beep under a set of given conditions, in this example the condition is right for a sound to be output when modulo x of y is equal to zero, i.e. when the X-coordinate is equally divisible by the Y-coordinate.
And that is it, now we just need to fire up a Robot, wire up a couple of observers and set the Robot off on it's merry way. In the example code we un-subscribe and re-subscribe to observers throughout the lifetime of the program in order to notice the effect this has upon the output.
Running the sample program will yield the following result; each time that "PING" is output to the screen the computer will emit a beep.
Essentially the stance is that it is impossible to create 100% clean code when using the .Net Framework, and that in order to implement some common designs one must "ignore" at least one of our SOLID principles. His conclusion is that "Interfaces are Bad" and they are the cause of the deficit within .Net. He also says that we only really have them because the creators felt that the "triangle of death" was not a problem to be solved.
How have we addressed this within this blog post?
We've looked at how we can maintain a separation of concerns, specifically by employing the Single Responsibility Principle, and we've looked at extend types, adding discrete functionality without changing our original object. We have noted however that the drawback to having to implement interfaces without the ability to extend multiple base classes is the necessity for writing some "plumbing" code.
If we didn't need this plumbing creating our final ObservableRobot would have been as simple as the below code fragment
It is important to note however that there will almost always be a little wiring up, or "plumbing" required in order to tie the interfaces/implementations together. For example, how would our ObesrvableRobot in the above example know when to call Update() in the absence of said plumbing. So then it is safe to say that even without the restriction placed upon multiple inheritance of concrete classes there would still be the likelyhood of some plumbing code being required.
So do we agree or disagree with Uncle Bob? We agree that there is some code required in order to expose all functionallity, be that whether we implement interfaces and create aggregate classes, or even if we could extend multiple concrete classes. We have the feeling that the latter approach would involve less plumbing code however.
But are interfaces bad? Do we care?
The belief of this author is that interfaces are not by their very nature bad things. We have them in other languages too, for example in C++ they are called pure virtual classes and they are fine. They do not bring anything bad to the table, again, in this author's view.
As to whether or not we care, I would say that we do care about interfaces, and we care about them a lot. As to whether or not we need a new keyword as we had for .Net, well, that is up for grabs really. I guess it does help compiler writers, and IDE users.
I think however that what is most important is not even whether or not you think that Uncle Bob got it wrong, but rather that you have actually thought about what it is that he is saying, and that you have some understanding of why he draws the conclusion that he has.
I have always taken everything I've read or heard from Uncle Bob very seriously. I've been reading a lot of stuff on his blog (I hope he doesn't mind the link, but I'm sure it will be fine)
Anyway it is with great reluctance that I posit the notion with this question ...
Did Uncle Bob get it wrong?
One blog in particular from Uncle Bob caught my eye recently, it is entitled 'Interface' Considered Harmful and can be found hereI lapped it up, every word that had been carefully selected so as not to leave room for ambiguity. I thought it was a great post and that it helped me to realize just what it was that was bugging me about .Net. There was this little itch when I consider C#. Don't misunderstand me at all, C# is my language of choice these days, but there was something niggling away at the back of my mind and reading that blog entry brought it right to the fore.
Part of the dialogue in this particular article goes as follows:
Well then just implement the register and notify functions in
MyObservableWidget
What? And duplicate that code for every observed class? I don't think so!
Well then have MyObservableWidget hold a reference to Subject
and delegate to it?
What? And duplicate the delegation code in every one of my observers?
How crass. How degenerate. Ugh.
Well, you're going to have to do one or the other of those things.
I know. And I hate it.
Yeah, it seems that there's no escape. Either you'll have to
violate the separation of concerns, or you'll have to duplicate code.
Yes. And it's the language forcing me into that situation.
Yes, that's unfortunate.
And what feature of the language is forcing me into this bad situation?
The interface keyword.
And so...?
The interface keyword is harmful.
The conversation is with regards to the lack of support for multiple inheritance in .Net which is a restrictive factor when it comes to adhering
to our SOLID principles. I don't agree, and after reading through this article I think that you will not agree either. I can't believe Uncle Bob
got it wrong; I'd like to say that it is an old entry from him, but it is not; it is dated 08 January 2015, and none of the features of .Net which I had used
were unavailable at that time.
Anyway I was so fired up about this that I started to put together a blog entry of my own, and it was to be called
There is a real problem with .Net ... The lack of support for Multiple Inheritance is a restrictive factor when it comes to adhering to our SOLID principles.
Catchy title huh?I hadn't thought it through (not the title, that I was certain of), I had not properly thought through what I was saying. Not because I didn't want to, not because I didn't think it needed thinking about because clearly it was being thought about!, but rather I did not think it through entirely simply because I believed it to be true and besides, Uncle Bob said it, and I don't believe he would publish something like this before having thought through all of the possibilities.
I had even written this because I believed it so strongly.
I welcome your disputes, but it has to be supported with firm evidence to the contrary in order to convince me otherwise.... and I went on to briefly talk about:
The SOLID principles
What are they? A nice little article about them can be found here , but in short| Single Responsibility | A class should have one and only one reason to change, meaning that a class should have only one job |
| Open/Closed | Objects or entities should be open for extension, but closed for modification |
| Liskov Substitution | Anywhere you can use a class, you should equally be able to use a sub-class |
| Interface Segregation | Many client-specific interfaces are better than one general-purpose interface |
| Dependency Inversion | A client should “Depend upon Abstractions, do not depend upon concretions" |
Entire articles, and books can, have been, and will continue to be written about each of these principles in isolation; I've even written a bit about some of these within my blogs -and a really great book is Dependency Injection in .Net . For this reason I am not going to go into the detail of these principles here, but instead within this article I am going to concentrate on the Single Responsibility Principle. So, let's expand a little upon what this means to us as software engineers.
Single Responsibility Principle (SRP)
The SRP states that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. The term was introduced by our good friend Robert (Uncle Bob) Martin in an article by the same name as part of his Principles of Object Oriented Design, made popular by his book Agile Software Development, Principles, Patterns, and Practices . A responsibility is defined as a reason to change, and a class or module should have one, and only one, reason to change.With this definition of the SRP in mind, we will take a look at an example of how the lack of support for multiple inheritance in .Net makes this principle impossible to be adhered to. By way of example we will consider the Observer pattern.
But first ... Some might argue that Multiple inheritance is available in .Net, it's just that only one base class can be a concrete/abstract, and the others need to be interfaces (now, in C++ terms, for example, that is actually multiple inheritance, because an interface is a class whose members are all virtual). But this does not help us in my example though
And then I turned my attention to ...
The Observer
The Observer is classified, in Design Patterns: Elements of Reusable Object Oriented Software (GoF) pp 293, as a behavioral (sic.) pattern whose intent is to"Define a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically"
You should understand that the discussion was based around the Observer Pattern because that was the example which Uncle Bob had used in his post. I wasn't going to take his example code because I felt it needed doing to prove anything, and so I started to write some code in order to illustrate the point ...
The first thing I needed was something to observe, so I figured "what could be worse than a robot?". It is a very simple robot, and it has no functionality beyond knowing both its X- and Y- coordinates. We can implement this as a struct because it is little more than a wrapper for a couple of coordinates.
public struct Robot : ILocatable<Robot> { public ILocatable<Robot> SetLocation(int xCoordinate, int yCoordinate) { return new Robot(xCoordinate, yCoordinate); } private Robot(int xCoordinate, int yCoordinate) { XCoordinate = xCoordinate; YCoordinate = yCoordinate; } public int XCoordinate { get; } public int YCoordinate { get; } }
Because we want to be able to place the robot at various coordinates perhaps we should expose the "location" related functionality via an interface (remember the I of SOLID), and because we might want to use this "location" functionality interface for other POCOs the interface makes use of Generics.
It is important to use a Generic interface in this case because we want to be able to return an object of the type we are locating.
public interface ILocatable<out T> { ILocatable<T> SetLocation(int xCoordinate, int yCoordinate); int XCoordinate { get; } int YCoordinate { get; } }
Next on the agenda for me is enabling the robot to move.
There are options here: We could either, add some movement methods to the Robot (and consequently polluting our Robot class with functionality which it does not need in order to fulfill its purpose), or we could create another object responsible for moving the Robot.
The Robot itself already has one responsibility (i.e. knowing where it is), so clearly adding movement functionality to Robot is adding a further responsibility. Currently the Robot is a small and concise unit and is able to function perfectly in accordance with our requirements, so instead we will create another object whose single responsibility is moving a Robot. We will define an interface to describe this functionality, adorne an existing type with extended functionality we will once again create a Generic interface.
public interface IMovable<out T> : ILocatable<T> where T : ILocatable<T> { ILocatable<T> LocatableObject { get; } IMovable<T> MoveHorizontally(HorizontalMovements direction); IMovable<T> MoveVertically(VerticalMovements direction); } public enum VerticalMovements { Up, Down} public enum HorizontalMovements { Left, Right }
And then of course comes the implementation of said interface, which is responsible for nothing beyond moving an object of type <T>. Below then is an example of how we can adhere to the SOLID Principles whilst adding new functionality to our logical model; at the same time we have also adhered to the Interface Segregation Principle, and the Open/Closed Principle
public class Movable<T> : IMovable<T> where T : struct, ILocatable<T> { public static Movable<T> SetStartingLocation(int xCoordinate, int yCoordinate) { return new Movable<T>(xCoordinate, yCoordinate); } private Movable(int xCoordinate, int yCoordinate) { LocatableObject = default(T).SetLocation(xCoordinate, yCoordinate); } // IMovable<T> public ILocatable<T> LocatableObject { get; } public IMovable<T> MoveHorizontally(HorizontalMovements direction = HorizontalMovements.Right) { var xCoordinate = direction == HorizontalMovements.Right ? XCoordinate + 1 : XCoordinate - 1; return new Movable<T>(xCoordinate, YCoordinate); } public IMovable<T> MoveVertically(VerticalMovements direction = VerticalMovements.Up) { var yCoordinate = direction == VerticalMovements.Up ? YCoordinate + 1 : YCoordinate - 1; return new Movable<T>(XCoordinate, yCoordinate); } public int XCoordinate => LocatableObject.XCoordinate; public int YCoordinate => LocatableObject.YCoordinate; // ILocatable<T> public ILocatable<T> SetLocation(int xCoordinate, int yCoordinate) { return SetStartingLocation(xCoordinate, yCoordinate).LocatableObject; } }
SOLID check-list
| Single Responsibility |
|
Each class has one and only one reason to change. |
| Open/Closed |
|
The functionality can be extended without modifying the existing structures |
| Liskov Substitution |
|
We've not really sub-classed anything yet, except for those interfaces |
| Interface Segregation |
|
We have interfaces for describing descrete functionality; the first (ILocatable<T>) is to do with location, and the second deals with the modification of that location, via the IMovable<T> interface |
| Dependency Inversion |
|
We can describe our system in abstract terms (i.e. interfaces); all of our objects are loosely coupled, there are no concrete dependencies |
Now that we have a POCO and the ability to move it, we come to the heart of this discussion. This is the part that Uncle Bob tells us is no good in .Net, based on the lack of support for multiple inheritance. I must admit that I was pretty much sold on the idea myself, until I set about getting on and writing it myself. However, I wasn't sure that there was nothing amiss with Uncle Bob's code, because, hang on a minute we've just done exactly what he said we can't do; though maybe that statement is a bit misleading, what he actually said is that we could not do it cleanly, i.e. without repetitious code.
It was relatively straight forward getting the movement functionality into play without modifying our POCO, and so the IObservable<T> interface shoud be no more difficult. - Can you see me changing my mind?
The "stuff" to help us implement this pattern which is part of the package (read .Net Framework) from Microsoft comes in the form of a couple of interfaces.
IObservable<T>
IObserver<T>
IObserver<T>
Obviously we don't have to use these interfaces, but they have been included in the Framework from 2.0 for a reason -because they are used extensively within the Windows Presentation Foundation (WPF) Framework and help us to implement the Model-View-ViewModel (MVVM) pattern quite nicely (Did you know that? Yes, of course you did!)- so we will do the "done thing" and use these interfaces instead of creating our own, which of course means that the code we come up with could be plugged straight into a WPF application should we so choose. So without further ado, let's look at how we we might go about implementing the Observer pattern and how we might use these interfaces as part of that implementation.
First of all we'll enable observation of the Robot quite generically, because the Observable functionality is common to all observable things, i.e. the functionality will stay the same, no matter what type of object is being observed.
We should note here that for the sake of this article we will assume that we know how to take care of any threading issues we might have, and we can omit to include any threading code for fear of detracting from the point under discussion.
public abstract class Observable<TTypeToObserve> : IObservable<TTypeToObserve> { private readonly List<IObserver<TTypeToObserve>> _observers; protected Observable() { _observers = new List<IObserver<TTypeToObserve>>(); } protected TTypeToObserve ObservedObject { get; protected set; } public IDisposable Subscribe(IObserver<TTypeToObserve> observer) { if (!_observers.Contains(observer)) _observers.Add(observer); return new Unsubscriber<TTypeToObserve>(_observers, observer); } public void Unsubscribe(IObserver<TTypeToObserve> observer) { if(!_observers.Contains(observer)) return; observer.OnCompleted(); _observers.Remove(observer); } public void Update() { foreach (var observer in _observers) { if (ObservedObject == null) observer.OnError(new NullObservableException<TTypeToObserve>()); else observer.OnNext(ObservedObject); } } public void UnsubscribeAll() { foreach (var observer in _observers.ToArray() .Where(observer => _observers.Contains(observer))) { observer.OnCompleted(); } _observers.Clear(); } }
Just look at this implementation! It is so clean that we can even hand off the responsibility of ending a subscription to a discrete object. What on earth was Uncle Bob talking about?
public sealed class Unsubscriber<TTypeToObserve> : IDisposable { private readonly List<IObserver<TTypeToObserve>> _observers; private readonly IObserver<TTypeToObserve> _observer; public Unsubscriber(List<IObserver<TTypeToObserve>> observers , IObserver<TTypeToObserve> observer) { _observers = observers; _observer = observer; } public void Dispose() { if (_observer == null || !_observers.Contains(_observer)) return; _observer.OnCompleted(); _observers.Remove(_observer); } }
This is brilliant, we've now encapsulated three discrete areas of functionality in three objects which each have a single responsibility, and none of the added functionality has interfered with the original POCO, the POCO is still a POCO. All that remains is for us to compose an object which will give all of this functionality to a Robot, and we can achieve that end with the following class.
But now comes the "messy bit" ... Unlcoe Bob was right after all. Here I am trying to compose an object and yet in order to do so I have to provide delegation methods for all of the functionality I want to make available, because: I cannot extend two classes. If extend the interfaces (which is my only option) I need to provide members which will delegate to objects within the composite
public class ObservableRobot : Observable<IMovable<Robot>>, IMovable<Robot> { public static ObservableRobot SetStartingLocation(int xCoord, int yCoord) { return new ObservableRobot(xCoord, yCoord); } private ObservableRobot(int xCoord, int yCoord) { ObservedObject = Movable<Robot>.SetStartingLocation(xCoord, yCoord); } // // Below is the duplicate code of which Uncle Bob was speaking. // // IMovable<T> private ILocatable<Robot> LocatableObject => ObservedObject.MovableObject; public IMovable<Robot> MoveHorizontally( HorizontalMovements direction = HorizontalMovements.Right) { ObservedObject = (Movable<Robot>)ObservedObject.MoveHorizontally(direction); Update(); return ObservedObject; } public IMovable<Robot> MoveVertically(VerticalMovements direction = VerticalMovements.Up) { ObservedObject = (Movable<Robot>)ObservedObject.MoveVertically(direction); Update(); return ObservedObject; } // ILocatable<T> public ILocatable<Robot> SetLocation(int xCoordinate, int yCoordinate) { var movableRobot = Movable.SetStartingLocation(xCoordinate, yCoordinate); ObservedObject = movableRobot; return movableRobot.LocatableObject; } public int XCoordinate => ObservedObject.XCoordinate; public int YCoordinate => ObservedObject.YCoordinate; }
The sole responsibility of the ObservableRobot class is to make a composite object consisting of a Robot, and both an IMoveable<T>, and an IObservable<T> interface instance.
We can only achieve clean and well structured code, which adheres to our SOLID Principles whilst using the .Net Framework to a degree. The issue is cleanliness, and the need to (superfluously) re-expose discrete functionality from the items of which the new structure is composed. However, I am unaware of anything which would cause one to note that we have violated any of the SOLID Principles, I also believe that the code is clean and concise. It is uncluttered and highly maintainable in my opinion.
But let's not get ahead of ourselves here, we still have to create the clients, or the Observers ...
Because we have such a clean code-base, creating the client(s) for our Observable objects is a bit of a breeze. We'll define an abstract class first of all. Why is this?. Well we might want some common functionality in our observers and at some point we will most likely want to just treat these objects as observers, not worrying about their specializations. In this case we want all of our observers to have a name, so that they can identify themselves in our sample program.
Here then is our abstract observer ...
public abstract class Observer<TTypeObserved> : IObserver<TTypeObserved> { protected Observer(string observerName) { ObserverName = observerName; } public string ObserverName { get; private set; } public abstract void OnCompleted(); public abstract void OnError(Exception e); public abstract void OnNext(TTypeObserved observed); }
This is our first concrete observer. It's sole purpose is to let us know of the Robot's current location. So, when the OnNext() member is invoked from the Observable object the LocationReporter emits a message to the screen indicating the Robot's X- and Y-coordinates.
public class LocationReporter : Observer<IMovable<Robot>> { private const string DefaultInstanceName = @"Location Reporter"; private const string ErrorFormatString = @"{0}: The location cannot be determined."; private const string LocationDataFormatString = @"The current location is {0}, {1}"; private const string CompletedMessageFormatString = @"The Robot has completed transmitting data to {0}."; public LocationReporter() : base(DefaultInstanceName) { } public LocationReporter(string name) : base(name) { } public override void OnError(Exception e) { WriteToConsole(string.Format(ErrorFormatString, ObserverName)); WriteToConsole(e.Message); } public override void OnNext(IMovable<Robot> observed) { var robot = observed.MovableObject; WriteToConsole(string.Format(LocationDataFormatString , robot.XCoordinate, robot.YCoordinate)); } public override void OnCompleted() { WriteToConsole(string.Format(CompletedMessageFormatString , ObserverName)); } private void WriteToConsole(string message) { ConsoleWriter.Write(Consoleclient.LocationReporter, message); } }
And this is our second concrete observer. This object's sole purpose is to emit a beep under a set of given conditions, in this example the condition is right for a sound to be output when modulo x of y is equal to zero, i.e. when the X-coordinate is equally divisible by the Y-coordinate.
public class Beeper : Observer<IMovable<Robot>> { private const string DefaultInstanceName = @"Beeper"; private const string CompletedMessageFormatString = @"The Robot has completed transmitting data to {0}."; public Beeper() : base(DefaultInstanceName) { } public override void OnError(Exception e) { } public override void OnNext(IMovable<Robot> observed) { if (WeDoNotNeedToAct(observed)) return; Console.Beep(); WriteToConsole(@"*** PING ***"); } private bool WeDoNotNeedToAct(ILocatable<Robot> observed) { var robot = observed.MovableObject; var x = robot.XCoordinate; var y = robot.YCoordinate; try { if (x % y != 0) return true; } catch (DivideByZeroException) { } return false; } public override void OnCompleted() { WriteToConsole(string.Format(CompletedMessageFormatString , ObserverName); } private static void WriteToConsole(string message) { ConsoleWriter.Write(Consoleclient.Beeper, message); } }
And that is it, now we just need to fire up a Robot, wire up a couple of observers and set the Robot off on it's merry way. In the example code we un-subscribe and re-subscribe to observers throughout the lifetime of the program in order to notice the effect this has upon the output.
// This is just a way of providing a shorthand name for a type // It would normally be use to avoid conflicts caused by // importing several namespaces using RobotObserver = IObserver<IMovable<Robot>>; class Program { enum ObserverType { Beeper, Movement } static void Main() { var movementSubscribed = true; var observers = new Dictionary<ObserverType, RobotObserver>(); WriteToConsole("Creating a Robot at position [0,0]"); var robot = ObservableRobot.SetStartingLocation(0, 0); WriteToConsole("Subscribing to Beeper"); RobotObserver beeper = new Beeper(); robot.Subscribe(beeper); observers.Add(ObserverType.Beeper, beeper); WriteToConsole("Subscribing to Location Reporter"); RobotObserver movementObserver = new LocationReporter((); var movementUnsbscriber = robot.Subscribe(movementObserver); observers.Add(ObserverType.Movement, movementObserver); WriteToConsole("Moving the Robot\n"); for (var index = 0; index < 20; ++index) { robot.MoveHorizontally(); if(index % 2 == 0) robot.MoveVertically(); if (index == 8 || index == 18) { WriteToConsole("\nRe-subscribing to Location Reporter"); movementUnsbscriber = robot.Subscribe(observers[ObserverType.Movement]); movementSubscribed = true; } if (index%5 != 0 || !movementSubscribed) continue; WriteToConsole("\nUnsubscribing from Location Reporter"); movementUnsbscriber.Dispose(); movementSubscribed = false; } WriteToConsole("\nUnsubscribing from Beeper"); robot.Unsubscribe(observers[ObserverType.Beeper]); WriteToConsole("Unsubscribing from Location Reporter"); robot.Unsubscribe(observers[ObserverType.Movement]); WriteToConsole("\nHit ENTER to finish"); Console.ReadLine(); } private static void WriteToConsole(string message) { ConsoleWriter.Write(Consoleclient.Program, message); } }
public enum ConsoleClient { Program, Beeper, LocationReporter } public static class ConsoleWriter { // Each client to the Console writes in a different // colour to differentiate it's output private static readonly Dictionary<ConsoleClient, ConsoleColor> ClientColours = new Dictionary<ConsoleClient, ConsoleColor> { { ConsoleClient.Program, ConsoleColor.Magenta } , { ConsoleClient.Beeper, ConsoleColor.Yellow } , { ConsoleClient.LocationReporter, ConsoleColor.Green } , }; private static ConsoleClient _currentClient = ConsoleClient.Program; private static ConsoleColor _currentColor = ClientColours[_currentClient]; public static void Write(ConsoleClient client, string message) { SetClientColor(client); Console.ForegroundColor = _currentColor; Console.WriteLine(message); Console.ForegroundColor = ConsoleColor.White; } private static void SetClientColor(ConsoleClient client) { if(client == _currentClient) return; _currentClient = client; _currentColor = ClientColours[_currentClient]; } }
Running the sample program will yield the following result; each time that "PING" is output to the screen the computer will emit a beep.
SOLID check-list
| Single Responsibility |
|
Each of our classes has one and only one reason to change. |
| Open/Closed |
|
We have clearly demonstrated (through use) how it is possible to extend the functionality of our POCO without making any changes to the original definition. In the example we have added movement and observational functionality, and yet the Robot definition has not been altered. Augmenting our Robot has been achieved by encapsulating behaviours within new objects and then using composition to create the extended object. |
| Liskov Substitution |
|
In the example code we create instances of types Beeper and LocationReporter, and yet we store them in a collection of IObserver<IMovable<Robot>> In the example we don't go on to use that collection for anything, but it should be clear how that would be possible. |
| Interface Segregation |
|
We use interfaces for describing discrete functionality throughout our code, including making use of the built in types IObservable<T> and IObserver<T> |
| Dependency Inversion |
|
We can describe our system in abstract terms (via both interface and abstract classes); all of our objects are loosely coupled, and there are no concrete dependencies. It would be a relatively painless exercise to use these objects in conjunction with IoC containers such as Unity, NInject, Castle Windsor, Spring, etc. |
Conclusions
What have we taken from Uncle Bob's blog post?Essentially the stance is that it is impossible to create 100% clean code when using the .Net Framework, and that in order to implement some common designs one must "ignore" at least one of our SOLID principles. His conclusion is that "Interfaces are Bad" and they are the cause of the deficit within .Net. He also says that we only really have them because the creators felt that the "triangle of death" was not a problem to be solved.
How have we addressed this within this blog post?
We've looked at how we can maintain a separation of concerns, specifically by employing the Single Responsibility Principle, and we've looked at extend types, adding discrete functionality without changing our original object. We have noted however that the drawback to having to implement interfaces without the ability to extend multiple base classes is the necessity for writing some "plumbing" code.
If we didn't need this plumbing creating our final ObservableRobot would have been as simple as the below code fragment
public struct Robot { } public class Movable<T> { } public class Observable<T> { } public class ObservableRobot : Observable<Robot>, Movable<Robot> { }
It is important to note however that there will almost always be a little wiring up, or "plumbing" required in order to tie the interfaces/implementations together. For example, how would our ObesrvableRobot in the above example know when to call Update() in the absence of said plumbing. So then it is safe to say that even without the restriction placed upon multiple inheritance of concrete classes there would still be the likelyhood of some plumbing code being required.
So do we agree or disagree with Uncle Bob? We agree that there is some code required in order to expose all functionallity, be that whether we implement interfaces and create aggregate classes, or even if we could extend multiple concrete classes. We have the feeling that the latter approach would involve less plumbing code however.
But are interfaces bad? Do we care?
The belief of this author is that interfaces are not by their very nature bad things. We have them in other languages too, for example in C++ they are called pure virtual classes and they are fine. They do not bring anything bad to the table, again, in this author's view.
As to whether or not we care, I would say that we do care about interfaces, and we care about them a lot. As to whether or not we need a new keyword as we had for .Net, well, that is up for grabs really. I guess it does help compiler writers, and IDE users.
I think however that what is most important is not even whether or not you think that Uncle Bob got it wrong, but rather that you have actually thought about what it is that he is saying, and that you have some understanding of why he draws the conclusion that he has.
No comments:
Post a Comment