Making the framework easier to use

Coordinator
Apr 29, 2010 at 9:38 PM
Edited May 25, 2010 at 7:08 PM

We're looking for ideas to make the client code more concise and would appreciate all ideas.

Below is a naive client code implementation.  This required the creation of a number of classes;

  • Pipeline class that inherited from the abstract Pipeline<T>
  • Loader class that returns the data to get it into the pipeline
  • StringOperation that inherits from BasicOperation<T>
  • POCO object the pipeline works on.

 

Additionally we also had to create a constructor to take in the data on the pipeline.  

This seems like a lot of work so I've been thinking about how we can improve the situation.

A couple of ideas sprung to mind;

  1. Put a default memory loader into the framework.
  2. Make the Pipeline<T> a non-abstract class.

I think both of these are good ideas.  Does anyone have other ideas?

    class Program
    {
        static void Main(string[] args)
        {
            var colourList = new ColourPipeline(GetColours());
            colourList.Start();
            Console.WriteLine("\r\nPress any key");
            Console.ReadKey();
        }
        private static IEnumerable<Colour> GetColours()
        {
            var team = new Colour[]
                           {
                               new Colour("Red"), 
                               new Colour("Yellow"), 
                               new Colour("Green"),
                               new Colour("Pink"), 
                               new Colour("Purple")
                           };
            return team;
        }
    }
 public class Colour
    {
        public Colour(string name)
        {
            Name = name;
        }
        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }
    }
    /// <summary>
    /// Concrete pipeline for Colour type.  This class specifies the operations that should occur and 
    /// the order they occur in to transform the data encapsualted in Colour members
    /// </summary>
    public class ColourPipeline : Pipeline<Colour>
    {
        public ColourPipeline()
        {
        }
        public ColourPipeline(IEnumerable<Colour> colour)
        {
            Execute(new PipelineMemoryLoader(colour));
        }
    }
    /// <summary>
    /// The pipeline requires a loader to get data into pipeline.  
    /// This could be a text file, database or other external resource.
    /// </summary>
    internal class PipelineMemoryLoader : BasicOperation<Colour>
    {
        private readonly IEnumerable<Colour> _colours= new List<Colour>();
        public PipelineMemoryLoader(IEnumerable<Colour> colours)
        {
            _colours = colours;
        }
        public override IEnumerable<Colour> Execute(IEnumerable<Colour> colours)
        {
            return _colours;
        }
    }
   public class DoubleSpace : BasicOperation<Colour>
    {
        public override IEnumerable<Colour> Execute(IEnumerable<Colour> input)
        {
            foreach (Colour member in input)
            {
                string name = string.Empty;
                char[] chars = member.Name.ToCharArray();
                foreach (var c in chars)
                {
                    name = name + c + " ";
                }
                member.Name = name.Trim();
            }
            SetSuccessResult(true);
            return input;
        }
    }
}

Coordinator
May 30, 2010 at 7:12 PM
Edited May 31, 2010 at 3:55 AM

Following the changes outlined above we have the following client code;

class Program
    {
        static void Main(string[] args)
        {
            var colourPipe = new Pipeline<Colour>();
            colourPipe
                    .Execute(new  PipelineMemoryLoader(GetColours())
                    .Execute(new DoubleSpace())
            colourPipe.Start();
            Console.WriteLine("\r\nPress any key");
            Console.ReadKey();
        }
        private static IEnumerable<Colour> GetColours()
        {
            var team = new Colour[]
                           {
                               new Colour("Red"), 
                               new Colour("Yellow"), 
                               new Colour("Green"),
                               new Colour("Pink"), 
                               new Colour("Purple")
                           };
            return team;
        }
    }
 public class Colour
    {
        public Colour(string name)
        {
            Name = name;
        }
        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }
    }

   public class DoubleSpace : BasicOperation<Colour>
    {
        public override IEnumerable<Colour> Execute(IEnumerable<Colour> input)
        {
            foreach (Colour member in input)
            {
                string name = string.Empty;
                char[] chars = member.Name.ToCharArray();
                foreach (var c in chars)
                {
                    name = name + c + " ";
                }
                member.Name = name.Trim();
            }
            SetSuccessResult(true);
            return input;
        }
    }
}

This is a lot more concise. However, there are two concerns

  • The PipelineMemoryLoader class
  • How to use the results  

Ignoring the first issue for the minute, the method Start() returns an IEnumerable<T>.  However, most of the worflows I've implemented transform one object (an XmlDocument for example) rather than a collection of objects.

Therefore I'm always using the static helper method to get the first member of the collection;

var firstresult = Pipeline<Colour>.GetItem(results, 0);
It may be better to return T from the Start() method.

 

Coordinator
Jun 18, 2010 at 9:46 PM
Edited Jun 18, 2010 at 9:51 PM

Following our most recent changes our example is more concise;

        static void Main(string[] args)
        {
            Workflow<IEnumerable<Colour>>.Definition()
            .Do(new DoubleSpace())
            .Do(new Func<IEnumerable<Colour>, IEnumerable<Colour>>(PrintResult))
            .Start(GetColours());

            Console.WriteLine("\r\nPress any key");
            Console.ReadKey();
        }
        // Removed Colour and Doublespace<Colour> classes fore brevity

Changing the name from Pipeline<T> to Workflow<T> makes the framework easier to start using as its more intuitive.

We kept the MemoryLoader class (renamed to workflow) as its a good practise to include a concrete class for all abstract classes in a framework.

However, in the simple case it is not needed because we have an overloaded Start() method that takes T.

 

Coordinator
Nov 30, 2010 at 7:38 PM
Edited Nov 30, 2010 at 7:43 PM

Using a generic method removes the need to new up an instance of DoubleSpace as the framework takes care of instantiation;

  static void Main(string[] args)
        {
            Workflow<IEnumerable<Colour>>.Definition()
            .Do<DoubleSpace>()
            .Do(new Func<IEnumerable<Colour>, IEnumerable<Colour>>(PrintResult))
            .Start(GetColours());

            Console.WriteLine("\r\nPress any key");
            Console.ReadKey();
        }
        // Removed Colour and Doublespace<Colour> classes fore brevity