views:

385

answers:

3

I want to be able to create "Transformation" classes that take a given object, perform a series of transformations on it (i.e. change property values) and keeps track of the transformations performed. The transformation performed will vary based on the properties of the object provided.

I want to be able to apply transformation rules (which are finite and commin) within a given transformation class using a fluent style interface.

At a high level, I understand that I will likely have an ITransformer, an ITransformationRule, and ITransformationResult, and a few other objects to make this happen.

How I would want the code to work when creating Transformation classes...

public OfflinePersonToOnlineTransformation : TransformerBase<Person>
{
   public OfflinePersonToOnlineTransformation()
   {
        Transform(x => x.PersonClassification)
           .WhenCreatedBefore("1/1/2000")
           .ClassifyAs("Online");
   }
}

I understand that my TransformerBase would need to implement the "Transform" method that takes a Func or Expression, and I understand that it would need to keep a collection of ITransformationRules. I also understand that I would likely use Extension methods for the "WhenCreatedBefore" and "ClassifyAs" methods.

The trouble is, I can't figure out how to make it all work. I've looked at source code for Fluent Validation .NET as it does validation this way, but the complexity is killing me. I'm looking for a tutorial that covers this, or someone to spell it out in a way that is a pretty straightforward.

Thanks in advance.

A: 

I had a think; Its only pseudo code but does this help?

public interface IPerson {
  string PersonClassification { get; set; }
  DateTime CreateDate { get; set; }
 }

public class Person :  IPerson {
  public string PersonClassification { get; set; }
  public DateTime CreateDate { get; set; }
 }




public class TransformerBase<T> 
 where T : IPerson {

    T Person { get; set; }

    T Transform(Func<T, PersonClassification> transformer) {
     return transformer(person);
    }
}


public class  OfflinePersonToOnlineTransformation : TransformerBase<Person>
{
   public OfflinePersonToOnlineTransformation()
   {
        Transform(x => x.PersonClassification)
           .WhenCreatedBefore("1/1/2000")
           .ClassifyAs("Online");
   }
}

public static class Extensions {

    public static T WhenCreatedBefore<T>(this T person, string date) where T : IPerson{
     if(person == null || person.CreateDate > DateTime.Parse(date)) 
      return null
     return person; 
    }
    public static T Classify<T>(this T person, string classification)where T : IPerson{
     if(person != null) 
      person.PersonClassification = classification;
     return person; 
    }
}
Preet Sangha
PersonClassification is not a type, it can't be used as a generic type parameter...
Thomas Levesque
A: 

It might help to take a step back and write a simple fluent interface first. You don't need generics or multiple classes to implement one. The main benefit of the fluent interface pattern is easy to read code. It's accomplished by returning this from methods to promote method chaining. Here's a basic example. I would start here and work backward to your desired result.

    public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    class Calculator
    {
        List<int> values = new List<int>();

        public Calculator Add(int value)
        {
            values.Add(value);
            return this;
        }

        public int Count()
        {
            return values.Count;                
        }

        public int Sum()
        {
            return values.Sum();
        }

    }
    private void Form1_Load(object sender, EventArgs e)
    {
        //returns 3
        int sum =
            new Calculator()
            .Add(1)
            .Add(2)
            .Sum();
    }
}
Steve
Creating a Fluent interface is easy, yes. Doing it in a manner that I am asking about is not. Once you start tying in the lambdas expressions trees and extension methods, things get significantly more complicated.
Matthew
+2  A: 

Not quite sure why you want to go to all this effort when linq does most of it for you:

IEnumerable<Person> somePeople; // from wherever
somePeople.Where(x => x.CreateDate < new DateTime(2000,1,1))
   .ForEach(x =>  x.PersonClassification = "Online");

Simply by adding the ForEach from here noting the proisos for why it's not included by default.

If you want to make the WhereCreatedBefore nicer then a simple extension like so:

static class PersonExtensions
{
    public static bool WhereCreatedBefore(this Person p,
        int year, int month, int day)
    {
         return p.CreateDate < new DateTime(year,month,day);
    }
}

which is useful in and of itself and gives you:

somePeople.Where(x => x.CreatedBefore(2000,1,1))
   .ForEach(x =>  x.PersonClassification = "Online");

Why limit yourself when simply expanding on the tools linq gives you makes things easier.

If you want to chain multiple side effects a simple alteration of ForEach like so:

public static IEnumerable<T> Modify<T>(
    this IEnumerable<T> input, Action<T> action)
{
    foreach (var x in input)
    {
        action(x);
        yield return x;
    }
}

giving you:

somePeople.Where(x => x.CreatedBefore(2000,1,1))
   .Modify(x =>  x.PersonClassification = "Online");
   .Modify(x =>  x.LastModifiedBy = Environment.UserName);

Or if you use the language integrated part of it:

(from p in somePeople where p.CreatedBefore(2000,1,1)) select p)
   .Modify(p =>  p.PersonClassification = "Online");
   .Modify(p =>  p.LastModifiedBy = Environment.UserName);

IF you really* wanted to you could write a ClassifyAs extension like so:

public static IEnumerable<Person> ClassifyAs(
    this IEnumerable<Person> input, string classification)
{
    foreach (var p in input)
    {
        p. PersonClassification = classification;
        yield return p;
    }
}

giving you your original of:

(from p in input where p.CreatedBefore(2000,1,1)) select p).ClassifyAs("Online");

Which is a one liner! with no fancy frameworks or type hierarchies required, just some useful extension methods. Linq is generally well designed, well implemented, ubiquitous and well integrated into c#. Reimplementing the query parts of it would be foolish and wasteful, what you want is to add side effect causing operations to it. This is fine (you have mutable objects so this is hardly causing a problem) just add those operations. Just making them continue to yield their input will make your code fluent in style.

ShuggyCoUk
It is worth the trouble for me, mainly because I want to apply this technique to a section of code that will be used very heavily. The ability to do this will drastically pay back the upfront dev.
Matthew
I'm not suggesting not adding helpful extension methods but what about your proposed scheme is netter than my peposed scheme? Mine is less verbose, more flexible (all existing IEnumerable extensions remain available) and more idiomatic. Performance wise it should be a wash. You are attempting to rewrite a querying mechanism when an extremely powerful one exists.
ShuggyCoUk