| Matthew's profileMatthew WrenPhotosBlogLists | Help |
|
|
March 23 Separation of Concerns IIAt the end of the last blog we had taken a simple calculator class and demonstrated how we can start finding lines of separation within our code. In this article I want to talk about what actually defines a line of separation. Whilst our focus in the last article was on the pieces that we were carving off of the calculator, little regard was made to what we were leaving behind, and whether or not what we are leaving behind is worthy of still being called a calculator. So, to start with we need to pose a more fundamental philosophical/ ontic question: What is a calculator? Answering this in the context of separating out concerns, it would be tempting to answer this in terms of the objects that a calculator comprises - a calculator is a set of buttons, a display panel, etc. But, if you consider a calculator through the more existential maxim "you are what you do" we would conclude that a calculator is defined by its function - to calculate. This is a very Heideggerian way of viewing objects, and his work in ontology (Ontology comes from the Greek word ὄν, which means "existence", and is the etymological root of the word "entity") is very interesting to consider in the context of object-oriented design, so I would like to introduce some of his ideas and see how we may use them in our understanding of SoC. I have to add a disclaimer however that I would not confess to being a savant on Heideggerian philosophy, and would advise those interested in his ideas, and most certainly anyone studying Heidegger, to look towards much better read authors as I am really just skirting over the huge intricacies of Heidegger's theories. For Heidegger entities are represented in the human mind in their primordial form in terms of their "in-order-to". that is to say, a calculator is firstly revealed to us as an "in-order-to" calculate, a seat as an "in-order-to" sit on, etc. What Heidegger is saying is that we do not perceive entities primarily in terms of its properties, such as having a bunch of buttons and a display, which is how an entity reveals itself when we decide to inspect it , but as a means to achieve some task. Heidegger distinguishes these two representations of entities as "ready-to-hand" or zuhand (seeing an object as an "in-order-to") and "present-at-hand" (an object as its properties). Heidegger also sees us humans as a third and distinct category of entity, what he refers to as Dasein, and states that the other two entities exist only because of Dasein. That is to say that the whole root of our calculator class ever being written in the first place is an abstract phenomena particular to a Dasein. Heidegger also stresses that a Dasein is in a cultural context that allows him to understand the appropriate application of a calculator as "equipment". If a calculator was dropped from the sky instead of a glass Coke bottle in the Gods Must be Crazy, the African tribe that discovered it would experience the entity with just as much bewilderment. That is to say they would experience it as a present-at-hand entity rather than ready-to-hand equipment. So, as I stated earlier, the lines of separation are not merely representations of the physical world around us, but reflections of the framework through which we experience life, a framework borne out of the evolutionary imperative to perceive boundaries in the entities in our environment, recognise their application, and use them transparently to aid us in our every-day tasks. What is more, the phenomena of a calculator is a result of working in a particular mode of that framework in which we are focused on achieving a task (a towards-which in Heideggerian terms), and so we are very much treating the entity as ready-to-hand, which in turn would make our class representation of our entity very much behaviour oriented. To use a famous Heideggerian example a hammer is an "in-order-to" hammer in nails, and so we would expect a hammer class to expose a hammer method (that is hammer in its verb sense). Heidegger states though in his theory of Umwelt that if an equipment does not function as expected it becomes conspicuous, and shifts to the present-at-hand. For example, if we go to open a door and the door doesn't open, the door handle becomes conspicuous and we try to figure out what is wrong with it. In the OO World I think this shift maps quite well to exceptions - the entity may then become conspicuous, and expose its state, through an exception without having to expose its state via public getters and setters on the entity. I guess what I am getting at here is: can I really orient my design in such a way that the state of an object becomes as hidden as possible, if not entirely hidden, and that really our ready-to-hand entities' purpose is to expose behaviour. I tried to think of this in terms of the hammer and nail example. Traditionally we would hammer the nail in and check its state to see if it was suitably hammered in. But if we focus on the "towards which" we might test using: Given a hammer, a wall, a picture and a nail, when I hit the nail into the wall with the hammer, then I expect to be able to hang a picture on it. This, in my opinion, is far more valid than the state analysis: Given a hammer, a wall and a nail, when I hit the nail into the wall with a hammer, then I expect the nail to be in a particular state. The reason is that the tested state could be wrong. The true focus of our test (the towards-which) is to be able to hang a picture on the wall, and the validity of the state of the nail is borne out by whether or not you can hang the target picture on it. This may seem less granular, but, I'll stick my neck out here and say that it is in effect testing exactly the same functionality as the second state test and in a much more accurate way. I might also argue that the often claimed benefits of easier identification of a point of failure afforded by more "granular" tests is just as easy in such behaviour driven tests, as the exceptions should reveal the source and nature of failures. Another interesting common problem comes when we consider how to represent the relationship between the wall and the nail. This is where Heidegger can help again with his notion that we are only really interested in the way that entities are presented to us (the phenomena). I have read a few articles toiling with the concept that entities are "disconnected in the real World" and therefore a separate entity is needed for each and a totally new entity to describe the relationship between the wall and the nail when they interact. I see this as symptomatic of Dasein acting as an inspector of the present at hand, rather than interacting seamlessly with the ready to hand. So, to describe the relationship between the nail and the wall we should look to the phenomena. When we see a nail by itself or a wall by itself they reveal themselves in a totally different way than a nail in a wall, because a "nail in a wall" in its totality presents us with a new single entity and an "in order to" hang a picture on, much in the same way that we see five pieces of wood nailed together in a particular configuration as a wooden box, and an "in order to" store things in, we don't see it as five pieces of wood and nails. Putting nails in a wall, brings us somewhat back to the original problem of putting buttons on a calculator and the question of what is a calculator. In a way the calculator is the totality of the buttons on the calculator. Primarily Dasein initiates its interaction with the calculator by using the calculator button entities as an in order to enter its calculation. So it seems that what is left behind really is not the calculator. So what is left then? This I will have to leave until next blog along with other remaining themes I was intending to deal with in this blog. February 25 Separation of ConcernsSince I started writing this blog it has been through the mill a bit. I started looking into explaining some standard principles to use when writing extensible code, in particular I was trying to formulate a process whereby at each step you could apply a particular principle. It seemed logical to put separation first, but as I progressed through new principles the more I had to return to the same topic of separation. At times established principles just fell apart through not properly separating out concerns before hand, at others principles revealed new lines of separation in the modules. In short, separation proved to be a fundamental principle underlying good OO design. So, I have chosen to walk though a very simple calculator application to illustrate this point. The objective is to write a calculator that you can allow the developer to extend with new function buttons, and in order to keep the samples as simple as possible I have focused on this particular area of the problem. The first place any modern application will start is with the god-like class. Because it is how most dabblers cut their first code, and is an example of how not to do it. public class Calculator : Form { public void AddButton_Click() { //implementation here } }
A lot of developers do try to separate out their code, but unfortunately its not that easy to get right. The first simple separation we might make is separating the calculator out into its own class, and hence untangle it from the form (whose responsibility it is to draw controls and capture events on the screen). This seems sensible, but returning to our aim of achieving extensibility we still have a problem: how do I add a new function buttons to the class? Requiring future developers to modify the original class is plain bad - we are meant to be providing encapsulation, e.g. hide the complex inner workings of our calculator, it also assumes that the code base will not be compiled. But if it is compiled can we just derive from the base class and implement the new function there? There is a problem with this. Say, one person makes a derived calculator that adds a multiplication function and another derives one that adds a division button. If we wanted to combine these two we would end up with messy code, and as we continue the problem becomes exacerbated and more complex to solve (remember OO is there to break down complexity). Rather than a flaw in inheritance (which of course has its place) the flaw is rooted in a failure to fully separate out our concerns, or better still, correctly apply the single responsibility principle. In our example, then, let's separate the functions out into their own classes. Another crucial part of separating responsibilities is identifying who creates what. Some may have heard of GRASP, and in particular the creator principle, which states that B should create A if one of the following conditions are met:
Great, so I can create my buttons in the calculator class. Or can I? This is where there is a missing proviso in the creator principle. It assumes you have got your design right, how else will you know what aggregates or contains what? In reality deciding who creates what needs to come after you have hammered out your design. So the creator principle falls apart if you haven't correctly separated out your concerns. OK, so let's get back to our scenario. We want to be able to add and remove function buttons as we please, so we need a way of talking to a button without knowing exactly which button we are talking to. The answer to this particular problem is dependency inversion. Great! So, now we are starting to decouple our buttons from the calculator so that it can potentially talk to any button, but it's at this point a lot of people get confused as to how to keep the calculator talking to the abstraction. You might discover code like the following in the calculator class (switch statements are also symptomatic of not understanding dependency inversion): foreach (IFunctionButton functionButton in functionButtons) { if (functionButton is SquareRootFunctionButton) { //do some special rendering for product } else { //do normal rendering } }
Casting down into derivations breaks the basic principle of dependency inversion (you must be talking to an abstraction), and is avoidable. The fact you are breaking the principle means that you have not grasped all the reasons why we use dependency inversion and how it improves your general application design. To explain this let us take the example in hand. In effect the way in which a button is rendered is button specific. Therefore the calculator needs to ask the button how it wishes to be rendered, not visa versa. Once you understand this simple principle, you will see the simplest method would be just to allow the button abstraction to describe its rendering preferences, but for cases where the effect upon the calling class is indeterminate/complex, it might prove a lot easier just to yield the calculator's interface to the button which the button can manipulate itself (inversion of control). Again though if we ran ahead and implemented all of this we would be missing the point that dependency inversion (as with the creator principle) is reliant on us having our concerns separated out properly. We will see how these dependencies disappear once we start considering the views. But for now I want to show how principles can organically reveal the separations of concern. So let's continue with applying the dependency inversion principle. We need to yield to the button an interface to the calculator. But what interface do we yield? Part of SOLID is the interface segregation principle. This means giving the button only access to that interface which is needed. (Just as a footnote to this I would also advise that it is probably best to stick to passing interfaces rather than delegates. Delegates are like method interfaces so it is tempting to pass a delegate where just one method is required, but if you need to expand your interface for a good reason, I think it is preferable not to start swapping over from delegate calls to interface calls - I like the idea of using one paradigm for the same problem domain.) So in answer to which interface we yield to the function button, the answer would be an interface that allows us simply to set the next function the calculator will use once it has collected the two values on which to perform the operation, let's call that interface INextFunctionRegister. Now this is where the interface segregation principle is great at smashing out our separations. According to this principle we should not pass an interface to the entire functionality of the function button to the function register, but only the interface it requires, which we will call IFunction. Should we in fact untangle the function button from the function and just aggregate an IFunction in a function button? The answer is most certainly "yes", as this is separating our modules out into nicely defined single responsibilities. Now, the function button still has the problem of rendering itself on the calculator. If we add a method on the calculator to add the button, all the calculator is going to do is pass that straight on to the view. The calculator is really a redundant and passive agent in this interaction, as well as binding it unnecessarily to an interface it personally does not need. So, the solution is to add the function button directly to the view. Again getting the components talking to only the interfaces required reveals more of the lines of separation - we can start to see potential for breaking the view down into a function button panel, a display panel, etc. So here is the class diagram so far: As you can see the components are broken out now quite well now and offer the potential for nice and granular unit tests. In the next blog I will go on to discuss responsibility of component creation to complete the calculator example, and I might have a think about how this all fits into the world of MVC/P. I hope that this article might have served as food for thought, particularly regarding the importance of SoC. If you have any ideas or comments about what I have written I'd be very interested to hear them. February 18 You Are Gonna Need ItI wanted to mention briefly in my blog that a few people I have spoken with over the past (and in an article I read today) have applied the YAGNI principle when deciding whether or not to implement a design pattern. Wikipedia even mentions that there is a sort of trade off between YAGNI and the "benefits of refactoring code". For my mind there should never be a trade off, since both principles have completely separate motivations. The principle of YAGNI is to only implement that functionality that is required in a given story, and in my mind this, because of the disparity between the two principles, should be regardless of the patterns and practices you utilize. Design patterns, and OO in general, are used to reduce complexity and increase maintainability through the use of recognised and proven patterns and practices for a given problem domain. To assume that you aren't going to need maintainability and simplicity in your code base implies we could all revert to functional programming, which of course is not something anyone would advocate. February 08 Chain of Responsibility Hybrid II : ImprovementsFollowing on from my last post I have identified a flaw in the previous design. IMapperChain<A,C> baseMapper = new MapFrom<A>().To<B>(abMapper).To<C>(bcMapper);IMapper<A,X> axMapper = baseMapper.To<X>(cxMapper); IMapper<A,Y> ayMapper = baseMapper.To<Y>(cyMapper); X myX = axMapper.Map(new A());
This does not work. The reason is because each mapper passed to baseMapper.To overwrites the previously attached mapper. To overcome this in my latest version, each time I add a mapper, I clone the entire MapFrom object graph and attach the new MapTo to this clone. Since the last post, I have also made the following additions:
So, here is the next evolution of the mapping code: public interface IMapper<SOURCE, TARGET> { TARGET Map(SOURCE source); } public interface IProcessor<SOURCE> { void Process(SOURCE source); } public interface IMapperContext { IDictionary<string, string> State { get; } } public interface IMapperChain<ROOTSOURCE, TARGET> : IMapper<ROOTSOURCE, TARGET> { IMapperChain<ROOTSOURCE, NEWTARGET> To<NEWTARGET>(IMapper<TARGET, NEWTARGET> mapper); IProcessor<ROOTSOURCE> To(IProcessor<TARGET> process); } public interface IWorkflowChainable { void AddHandler<HANDLETYPE>(IWorkflowHandler<HANDLETYPE> handler); } public interface IWorkflowHandler<SOURCE> : ICloneable { void Handle(SOURCE source, IMapperContext context); } public abstract class ContextualMapper<SOURCE, TARGET> : IMapper<SOURCE, TARGET> { protected IMapperContext Context; public TARGET Map(SOURCE source, IMapperContext context) { Context = context; return Map(source); } public abstract TARGET Map(SOURCE source); } public abstract class ContextualProcessor<SOURCE> : IProcessor<SOURCE> { protected IMapperContext Context; public void Process(SOURCE source, IMapperContext context) { Context = context; Process(source); } public abstract void Process(SOURCE source); } public class MapperContext : IMapperContext { private readonly IDictionary<string, string> _state = new Dictionary<string, string>(); public IDictionary<string, string> State { get { return _state; } } } public class MapFrom<SOURCE> : ICloneable { private IWorkflowHandler<SOURCE> _handler; public MapFrom() { } private MapFrom(IWorkflowHandler<SOURCE> handler) { _handler = handler; } public IMapperChain<SOURCE, TARGET> To<TARGET>(IMapper<SOURCE, TARGET> mapper) { return new MapTo<SOURCE, SOURCE, TARGET>(mapper, this.Clone() as MapFrom<SOURCE>); } public IProcessor<SOURCE> To(IProcessor<SOURCE> process) { return new Processor<SOURCE, SOURCE>(process, this.Clone() as MapFrom<SOURCE>); } public void Handle(SOURCE source) { IMapperContext context = new MapperContext(); _handler.Handle(source, context); } internal void AddHandler<HANDLETYPE>(IWorkflowHandler<HANDLETYPE> handler) { if (_handler == null) { _handler = handler as IWorkflowHandler<SOURCE>; } else { IWorkflowChainable chain = _handler as IWorkflowChainable; if (chain != null) { chain.AddHandler<HANDLETYPE>(handler); } } } public object Clone() { if (_handler != null) { return new MapFrom<SOURCE>(_handler.Clone() as IWorkflowHandler<SOURCE>); } else { return new MapFrom<SOURCE>(); } } } public class MapTo<ROOTSOURCE, SOURCE, TARGET> : IWorkflowHandler<SOURCE>, IMapperChain<ROOTSOURCE, TARGET>, IWorkflowChainable { private MapFrom<ROOTSOURCE> _rootChain; private IMapper<SOURCE, TARGET> _mapper; private IWorkflowHandler<TARGET> _handler; private bool _terminateChain; private TARGET _value; private MapTo( IWorkflowHandler<TARGET> handler, IMapper<SOURCE, TARGET> mapper, MapFrom<ROOTSOURCE> rootChain) : this(mapper, rootChain) { _handler = handler; } public MapTo( IMapper<SOURCE, TARGET> mapper, MapFrom<ROOTSOURCE> rootChain) { _mapper = mapper; _rootChain = rootChain; _rootChain.AddHandler<SOURCE>(this); } public IMapperChain<ROOTSOURCE, NEWTARGET> To<NEWTARGET>(IMapper<TARGET, NEWTARGET> mapper) { return new MapTo<ROOTSOURCE, TARGET, NEWTARGET>(mapper, _rootChain.Clone() as MapFrom<ROOTSOURCE>); } public IProcessor<ROOTSOURCE> To(IProcessor<TARGET> process) { return new Processor<ROOTSOURCE, TARGET>(process, _rootChain.Clone() as MapFrom<ROOTSOURCE>); } public TARGET Map(ROOTSOURCE source) { _rootChain.Handle(source); return _value; } public void Handle(SOURCE source, IMapperContext context) { ContextualMapper<SOURCE, TARGET> contextualMapper = _mapper as ContextualMapper<SOURCE, TARGET>; if (contextualMapper != null) { _value = contextualMapper.Map(source, context); } else { _value = _mapper.Map(source); } if (_handler != null) { _handler.Handle(_value, context); } } public object Clone() { if (_handler != null) { return new MapTo<ROOTSOURCE, SOURCE, TARGET>( _handler.Clone() as IWorkflowHandler<TARGET>, _mapper, _rootChain); } else { return new MapTo<ROOTSOURCE, SOURCE, TARGET>(_mapper, _rootChain); } } public void AddHandler<HANDLETYPE>(IWorkflowHandler<HANDLETYPE> handler) { if (_handler == null) { _handler = handler as IWorkflowHandler<TARGET>; } else { IWorkflowChainable chain = _handler as IWorkflowChainable; if (chain != null) { chain.AddHandler<HANDLETYPE>(handler); } } } } public class Processor<ROOTSOURCE, TARGET> : IProcessor<ROOTSOURCE>, IWorkflowHandler<TARGET> { private IProcessor<TARGET> _process { get; set; } private MapFrom<ROOTSOURCE> _rootChain { get; set; } public Processor(IProcessor<TARGET> process, MapFrom<ROOTSOURCE> rootChain) { _process = process; _rootChain = rootChain; _rootChain.AddHandler<TARGET>(this); } public void Process(ROOTSOURCE source) { _rootChain.Handle(source); } public void Handle(TARGET source, IMapperContext context) { ContextualProcessor<TARGET> contextualProcess = _process as ContextualProcessor<TARGET>; if (contextualProcess != null) { contextualProcess.Process(source, context); } else { contextualProcess.Process(source); } } public object Clone() { return new Processor<ROOTSOURCE, TARGET>(_process, _rootChain); } } February 01 Chain of Responsibility Hybrid : Sequential MappersRecently I was faced with the simple task of persisting a domain object into a file in as XML, so that later I could re-hydrate the domain object from my file. So, here was my intention:
Points one to three are quite straight forwards, but it was when I came to point four that I had a think about the way in which this should be executed. Breaking down the sequence of processes:
Expressed another way:
Insignificant as it may seem, there is undeniably a pattern here:
Yes, crazy as it seems I am actually going to write three mappers instead of the original one. Why?
Traditionally we might want to use the chain of responsibility pattern to execute the workflow sequence here, but I decided instead to build a hybrid of this design pattern, so that I could declare my sequence of mappers using fluent interfaces, and also I wanted to yield a mapper that spanned the entire chain of mappers. So my aim is to be able to write something like: IMapper<A,D> mapper =
new MapFrom<A>()
.To<B>(abMapper)
.To<C>(bcMapper)
.To<D>(cdMapper);
So, my first implementation is as follows: public interface IMapper<SOURCE, TARGET> { TARGET Map(SOURCE source); } public interface IMapperChain<ROOTSOURCE, TARGET> : IMapper<ROOTSOURCE, TARGET> { IMapperChain<ROOTSOURCE, NEWTARGET> To<NEWTARGET>(IMapper<TARGET, NEWTARGET> mapper); } public interface IMappingHandler<SOURCE> { void Handle(SOURCE source); } public class MapFrom<SOURCE> { private IMappingHandler<SOURCE> _handler; public IMapperChain<SOURCE, TARGET> To<TARGET>(IMapper<SOURCE, TARGET> mapper) { _handler = new MapTo<SOURCE, SOURCE, TARGET>(mapper, this); return _handler as IMapperChain<SOURCE, TARGET>; } public void Handle(SOURCE source) { _handler.Handle(source); } } public class MapTo<ROOTSOURCE, SOURCE, TARGET> : IMappingHandler<SOURCE>, IMapperChain<ROOTSOURCE, TARGET> { private bool _terminateChain; private MapFrom<ROOTSOURCE> rootChain; private IMapper<SOURCE, TARGET> _mapper; private IMappingHandler<TARGET> _handler; private TARGET _value; public MapTo( IMapper<SOURCE, TARGET> mapper, MapFrom<ROOTSOURCE> rootChain) { _mapper = mapper; this.rootChain = rootChain; } public IMapperChain<ROOTSOURCE, NEWTARGET> To<NEWTARGET>(IMapper<TARGET, NEWTARGET> mapper) { _handler = new MapTo<ROOTSOURCE, TARGET, NEWTARGET>(mapper, rootChain); return _handler as IMapperChain<ROOTSOURCE, NEWTARGET>; } public TARGET Map(ROOTSOURCE source) { _terminateChain = true; rootChain.Handle(source); _terminateChain = false; return _value; } public void Handle(SOURCE source) { _value = _mapper.Map(source); if (!_terminateChain && _handler != null) { _handler.Handle(_value); } } } |
|
|