Friday 17 December 2010

Bridge Pattern

"Decouple an abstraction from its implementation so that the two can vary independently."
Unlike the "Adapter pattern" I think that you have to set out coding with the intention to use this pattern, so I think that this pattern is more applicable to new systems than to prevent atrophy in existing ones.
Unfortuately for me, this is one of those patterns that I've struggled with...
I get the concepts, but I just an issue with an example I've found on the Web and I also struggle to imagine real implementations.  When I hunt around for examples, I'm always slightly disappointed with what I find.
I absolutely get the idea that you have two things, both of which can change independently (i.e. you can create new concrete instances of things on the LHS of the 'bridge',  the line between the abstraction and the implementor, and new concrete instances of things on the RHS without the opposite side needing to change.)  Furthermore, I can also see that if the Client gets hold of the Abstraction through a class dispenser, the Client never breaks when new Abstractions or Implementors are introduced. 
In the "real world" (or at least the first hit I get when I google "Bridge Pattern") the canonical example (http://en.wikipedia.org/wiki/Bridge_pattern) are Shapes (an "abstract Abstraction") such as Circles (Refined Abstraction) being drawn on a couple of specific "Devices" (Concrete Implementations).  "Bridge" enables new Shapes to be introduced without effecting the Devices they are drawn on and new Devices to be introduced without effecting the Shapes hierarchy. Circle is a Refined Abstraction because it’s a subclass of Shape, but its still an "abstraction" because it doesn't exist until it is rendered.  All these concepts I'm entirely comfortable with, but the example leaves me somewhat bemused.  Maybe its just the example which is a bit confusing: The Shapes example has Draw() on the Abstraction calling DrawCircle() on the Implementor.  To me this defeats the whole idea of the Bridge pattern because if I introduce a Rectangle Refined Abstraction on the LHS I have to change the Implementor interface on the RHS to include a DrawRectangle() method.  You could argue that this should be a breaking change because the Devices don't know how to render a rectangle.  But should I really be changing the interface and putting implementations of DrawRectangle() into the Concrete Implementations?  If that is the case then this is a poor example IMO because for a pattern that sells itself as one that allows abstractions and implementations to change independently, then it's not a very helpful example when adding something to one side forces a change to the other!
My example illustrates the two sides of the bridge chaging independently.  We start with RoadRunners and FellRunners, who are equally able to enter RoadRaces and FellRaces. Firstly, we introduce a new Race type Implementor of typeCrossCountryRace, which might attract both the existing RoadRunner and FellRunner types.  Secondly we then introduce a new Runner type abstraction called CrossCountryRunner who might enter any of our Race types.  All this change can occur without client code breaking.
namespace Bridge
{
    class Program
    {
        static void Main(string[] args)
        {
            Example1();
            Example2();
            Example3();
            Console.ReadLine();
        }   
    
        private static void Example1()
        {
            Runner paulaRadcliffe = new RoadRunner(new RoadRace());
            paulaRadcliffe.EnterRace(Runner.RacePurpose.Race);
 
            Runner jossNaylor = new FellRunner(new FellRace());
            jossNaylor.EnterRace(Runner.RacePurpose.Train);
        }
 
        /// <summary>
        /// We can add a new Implementor
        /// </summary>
        private static void Example2()
        {
            Runner joPavey = new RoadRunner(new CrossCountryRace());
            joPavey.EnterRace(Runner.RacePurpose.Race);
        }
    
        /// <summary>
        /// We can also add a new Refined abstraction
        /// </summary>
        private static void Example3()
        {
            Runner kenenisaBekele =
            new CrossCountryRunner(new CrossCountryRace());
            
            kenenisaBekele.EnterRace(Runner.RacePurpose.Train);
        }   
    }
 
    public abstract class Runner
    {
        public enum RacePurpose
        {
            Race,
            Train
        }
 
        protected Race Race { getset; }
 
        private string RunnerType
        {
            get
            {
                return this.GetType().Name;
            }
        }
 
        private string RaceType
        {
            get
            {
                return Race.GetType().Name;
            }
        }
 
        public virtual void EnterRace(RacePurpose purpose)
        {
            if (purpose == RacePurpose.Race)
            {
                Race.Compete(RunnerType, RaceType);
            }
            else
            {
                Race.Enjoy(RunnerType, RaceType);
            }
        
        }
    }
 
    public class RoadRunner : Runner
    {
        public RoadRunner(Race race)
        {
            base.Race = race;
        }
    }
 
    public class FellRunner : Runner
    {
        public FellRunner(Race race)
        {
            base.Race = race;
        }
    }
 
    public interface Race
    {
        void Compete(string runnerType, string raceType);
        void Enjoy(string runnerType, string raceType);
    }
 
    public class RoadRace : Race
    {
        public void Compete(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Run even paced!"
,
            runnerType, raceType);
        }
 
        public void Enjoy(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Enjoy the buzz of the crowd!"
,
            runnerType, raceType);
        }
    }
 
    public class FellRace : Race
    {
        public void Compete(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Push up the hills, throw yourself down them!"
,
            runnerType, raceType);        
        }
 
        public void Enjoy(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Enjoy the fabulous views!"
,
            runnerType, raceType);
        }
    }
 
    public class CrossCountryRace : Race
    {
        public void Compete(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Run hard round a muddy field!"
,
            runnerType, raceType);
        }
 
        public void Enjoy(string runnerType, string raceType)
        {
            Console.WriteLine("
            {0} {1}: Not much to enjoy here!"
,
            runnerType, raceType);
        }
    }
 
    public class CrossCountryRunner : Runner
    {
        public CrossCountryRunner(Race race)
        {
            base.Race = race;
        }
    }
}
Outputs:

RoadRunner RoadRace: Run even paced!
FellRunner FellRace: Enjoy the fabulous views!
RoadRunner CrossCountryRace: Run hard round a muddy field!
CrossCountryRunner CrossCountryRace: Not much to enjoy here!



No comments:

Post a Comment