Pages

Monday, March 5, 2012

Behavioral Object Pattern

Behavioral Object Pattern


When I created Cspec I had to solve the problem of creating a expressive method of writing code so the test's could very expressively describe behavior. For better or worse I ended up with and object that consisted entirely out of delegations. From the outside they look like methods but actually they are not, this method of writing code got me thinking about the potential benefits in high transactional computing and concurrent programs, as well as the benefit's for developer meaning better control and more clean code, so I give you a Behavioral Object Pattern (perhaps it should be called delegational object pattern).

Basics


Consider the folowing code:


public class BehavioralCalculator
{
    // State.
    private long total;

    public BehavioralCalculator()
    {
        Initalize();
    }

    // Behavioral initalier
    private void Initalize()
    {
        Add = ( x ) => total += x;
        Sub = ( x ) => total -= x;
        Div = ( x ) => total /= x;
        Mul = ( x ) => total *= x;
        GetTotal = () => total;
        SetTotal = ( x ) => total = x;
    }

    // Header functions.
    public Func<long, long> Add;
    public Func<long, long> Sub;
    public Func<long, long> Div;
    public Func<long, long> Mul;
    public Func<long> GetTotal;
    public Action<long> SetTotal;

    // Inline functions.
    public Func<long, long, long> InSum =
        ( x, y ) => x + y;

    public Func<long, long, long> InSub =
        ( x, y ) => x - y;
}

This is a very simple example that can be improved by a behavioral interfaces (more on that later),
at first glance this is nothing special, and could be considered as a very awkward way or writing code
without any benefits. However it enforces a method of wiring clean code as the class now can be split
into header and implementation, moreover all methods that don't modify any state can be implemented inline
and all methods that do modify state have to be initialized, as delegations only enter externally scoped
members when in a method. It get's better as all delegations can be considered virtual by design, as their
implementation can be altered in runtime.

Let's consider a simple example and further explore the features and anomalies
(yes we have those and we need to be aware of them) of such technique.


    BehavioralCalculator calculator = new BehavioralCalculator();

    // state modification.
    calculator.Add(10L);
    calculator.Add(20L);

    // get the accumulated value.
    long total = calculator.GetTotal();

    // virtualize the add function.
    calculator.Add = (x) =>
    {
        long newTotal = calculator.GetTotal();
        long value = x * 2;
        calculator.SetTotal(newTotal += value);
        return value;
    };

    calculator.Add(10);

    total = calculator.GetTotal();

This simple example demonstrates how we can create a behavioral object and use it, then we can virtualize the functions at will.

Mashups


The next interesting property is that we are now fully object oriented, what do I
mean by that? Well now *All* thins in a class are objects, even methods so we can treat them as ones, meaning
that we can expose, modify, and pass them around. This enables us to create mixing or more properly in this
context something that I like to call mashups. As we can pass methods around to other object's so we can move
their state! This is very cool as many we can create a generic mashup object that can combine and mix state
of other objects in runtime.


Consider the most simplest mashup object:


    public class BehavioralMashup
    {
        public Func<long, long> Function;

    }

Now we can pass methods to that mashup object:


    BehavioralCalculator calculator = new BehavioralCalculator();

    // state modification.
    calculator.Add(10L);
    calculator.Add(20L);

    BehavioralMashup mashup = new BehavioralMashup();

    mashup.Function = calculator.Add;

    mashup.Function(10);

    // total = 40.
    long total = calculator.GetTotal();

Note that we are passing the behavior by reference so actually this is very similar to normal pass by ref
but instead so each time we will call mashup Function now we will Add a total to the calculator object.

Virtualization & Immutability


The very interesting property arises from the fact that delegates are immutable, but the references that they
are pointing to are not, if we pass the reference to a mashup object and then virtualize the delegation
in calculator object like so:


    BehavioralCalculator calculator = new BehavioralCalculator();

    // state modification.
    calculator.Add(10L);
    calculator.Add(20L);

    BehavioralMashup mashup = new BehavioralMashup();

    // virtualize
    mashup.Function = calculator.Add;
    calculator.Add = (x) => 0;

    mashup.Function(10);
    calculator.Add(100);

    // total = 40.
    long total = calculator.GetTotal();

The original value is not going to change, which is very expected if you know how delegations work. It
more or less the same like with object references but with this pattern it's method scoped.

State Tracking


Caching all of the virtualizations of the method can create an automatic versioning of the objects behavior
that changes over time, so in reality one could create much less objects but mutate their behavior
depending on the Domain or use case, or method call. This sort of behavior could be a desired one
if for example some part of the system would need to do Add on the calculator but in this particular
context the method would require additional operations for the Add to be valid, so what could we do
it to store the method (we should have done it by now, preferably in a history cache in the object)
and create an extended implementation pass it to the history and then execute the extended operation
when upon ending revert to the previous version of the method.

A very basic implementation can be done like so:



    // naive cache.
    public class StateCache
    {
        public static Dictionary<string, MethodInfo> History = new Dictionary<string, MethodInfo>();
    }

    // provide multiple ovverides for more delegations.
    public class Tracker<T,N>
    {
        private static readonly object locker = new object();     
        public static Func<T, N> Track { get; set; }

        static Tracker()
        {
            Track = (x) =>
            {
                Delegate[] list = Track.GetInvocationList();
                string key = list[list.Length - 1].Method.Name;

                if (!StateCache.History.ContainsKey(key))
                {
                    lock (locker)
                    {
                        StateCache.History.Add(key, Track.Method);
                    }
                }

                return default(N);
            };
        }
    }

We have defined our StateCache that for the sake of simplicity is not very well defined but will
do for now. Now all we need to do is to add the tracking method to the initializer so we could track
the history of the methods:



    // Behavioral initalier
    private void Initalize()
    {
        Add = (x) => total += x;
        Sub = (x) => total -= x;
        Div = (x) => total /= x;
        Mul = (x) => total *= x;
        GetTotal = () => total;
        SetTotal = (x) => total = x;

        AddTracking = () => this.Add += Tracker<long, long>.Track;

    }

    public Action AddTracking;

Now after each virtualization of the behavior we need to invoke the AddTrancking delegate to track the history.

Multicasting


There is another great feature here that we get for free when implementing this pattern, do you remember the
AOP pattern? The most basic and most common way of using it is to define implicit loggers for methods ( there
are others but it's the most common ) so that developers wouldn't need to concern themselves with such tedious
operation, that can be easily omitted by mistake. Now considering that all delegations are by default
Multicastable we could say that we get very similar behavior to AOP for free, as we can add logger to PRE and
POST call to method, and this behavior can simply be set up in the Initialize() method of our class.
What's even more interesting that we cann add methods on the fly, modify and remove certain delegations
from the Multicast Delegate thus we have much grater control but we need to enfore grater care when using it.

Multicasting can not only be used as a sort of AOP but when combining them with other data structures they
can act as an extended data structures so for example when considering a Queue that need to process work
items from other threads using this pattern one could create a LinkedQueue with no additional effort, each
item on this queue being a delegation that holds behavior can be multicasted thus a single item can process
multiple behaviors, when considering using MashupObjects this could be extended to a notion that a single
Queue item will process correlated behavior.

A simple example of such Queue:


    BehavioralCalculator calculator = new BehavioralCalculator();
    // add new behaviors.
    calculator.Add += ( x ) =>
        {
            long total = calculator.GetTotal();
            calculator.SetTotal( total + x + 2 );
            return total;
        };

    Queue<Func<long, long>> queue = new Queue<Func<long, long>>();
    queue.Enqueue( calculator.Add );
    // copy, this function wont get fired.
    Func<long, long> f = queue.Peek();
    f += ( x ) =>
        {
            calculator.SetTotal( x + 3 );
            long total = calculator.GetTotal();
            return total;
        };
    // total = 22
    queue.Dequeue()( 10 );

When we combine that with History tracking and logging, we do get a lot of control over the Queue execution
which is very desirable in high traffic applications that use concurrent Queues to process work items that
consist of complex work-flows ( like banking applications ).

Common Behavioral Delegation


Having this basic covered it would be desirable to create a common delegation type as otherwise when
each developer will make use of it's own set of delegations in a class the maintenance and domain (converting
delegate types) will be harder then just plain programming with normal classes, so in order to take
grater control and not force developers to call explicit methods to attach some behaviors like tracking
to behavioral methods we should define some basic building blocks for the pattern.

In order to do that we need to define a custom delegate for the pattern:


    public delegate T Behavior<P,T>(P parameterObj );


Every single behavior will either return a value or null, or Boolean if it's required. By having such
delegate we can be consistent and we can start creating more complex types and methods based of this
very simple singe delegate principle. To avoid situations to add Loggers or other implicit behavior
manually when initializing or virtualizing ( unless we want to explicitly redefine logging ) we will
create a custom "Delegate" class that will be used to assign and combine behaviors.


public class BehavioralDelegate<P,T>
    {
        private readonly Behavior<P, T> delegation;

        private BehavioralDelegate( Behavior<P, T> delegation )
        {
            this.delegation = delegation;
        }

        public static BehavioralDelegate<P,T>
            operator +( BehavioralDelegate<P,T> source, BehavioralDelegate<P,T> target )
        {
            //add loging, tracking, auditing, validation,
            //everything that's needed *but* as delegations!
            //as that's the principle of the design.

            return new BehavioralDelegate<P, T>(
                (Behavior<P, T>) Delegate.
                    Combine( source.Delegation, target.Delegation, Tracker<P,T>.Track )
                );
        }

        public static BehavioralDelegate<P,T>
            operator -( BehavioralDelegate<P,T> source, BehavioralDelegate<P,T> target )
        {
            return new BehavioralDelegate<P, T>(
                (Behavior<P, T>) Delegate.
                    Remove( source.Delegation, target.Delegation ) );
        }

        public static BehavioralDelegate<P, T> Create( Behavior<P, T> target )
        {
            return new BehavioralDelegate<P, T>( target );
        }

        static public implicit operator BehavioralDelegate<P, T>( Behavior<P, T> target )
        {
            return new BehavioralDelegate<P, T>( target );
        }

        static public implicit operator Behavior<P, T>( BehavioralDelegate<P, T> target )
        {
            return target.Delegation;
        }

        public Behavior<P, T> Delegation
        {
            get { return delegation; }
        }
    }

By having such custom Delegate class defined that's implicitly castable we have a single delegation pointing
where w can add on many custom functionality, so instead defining an anonymous method now in Initialization
or Virtualization we pass it through an Create method to assign it to the target delegate.


        Add +=
            BehavioralDelegate<long, bool>.
                Create( ( x ) => { total = 1; return true; } );

If you wonder why this custom class did not have implicit operators on Lambdas like for example:


        static public implicit operator
            BehavioralDelegate<P, T>( Expression<Behavior<P, T>> expr )
        {
            return new BehavioralDelegate<P, T>( expr.Compile() );
        }

        static public implicit operator
            BehavioralDelegate<P, T>( LambdaExpression expr )
        {
            return new BehavioralDelegate<P, T>( (Behavior<P, T>) expr.Compile() );
        }

Well as much as it would be cool, and convenient to have such feature unfortunately it's impossible as
we still need to define explicit cast, so therefor it's better to just define a Create method and pass
the delegate there.

Behavior Sourcing


The final addition to the pattern is that if we now in therms of behavior then we should also think about
the object as a state machine that modifies data through behavior, and it would be ideal if we could serialize
such state, this approach is very similar to a pattern called event sourcing where data is represented by
a history of events and only those get serialized, it feels more natural to do that when using behaviors.
There is a question here how should we do it, and should we serialize behaviors? Well in order to recreate
state of the system we only need to keep track of the invocation list and methodInfos, all anonymous methods
are compiled into module therefor we can recreate them with little effort, namely having the custom delegate
we would create a sourcer delegate and pass it like so:


        public static BehavioralDelegate<P,T>
            operator +( BehavioralDelegate<P,T> source, BehavioralDelegate<P,T> target )
        {
            BehavioralDelegate<P, T> sourcer = BehavioralDelegate<P, T>.Create(
                    ( x ) =>
                    {
                        //save x, and the behavior name to dictionary
                        //and serialize this data.
                        return target.Delegation( x );
                    } );

            //add loging, tracking, auditing, validation,
            //everything that's needed *but* as delegations!
            //as that's the principle of the design.
            return new BehavioralDelegate<P, T>
                ( (Behavior<P, T>) Delegate.Combine( source.Delegation, sourcer ));
        }

This simple association on the target delegation could be responsible for serializing data and therefor
maintain state, through behaviors, so we do have persistent behavioral objects, which leads naturally
to a question, do we need other types of persistence? It's arguable but having such simple persistence
and simply having data in memory in the forms of Key Value storage's that are serializable potentially can
replace the Entire database tier with persistence layer.

When doing serialization of behaviors another interesting thing could be potentially done, meaning we
could hold the Assembly delegations serialized which in turn could give us versioning of the behaviors
even after implementation changes in the Assembly provided that the interfacing remains the same. Now
keep in mind that the following code is unsafe when doing serialization:


    BinaryFormatter formatter = new BinaryFormatter();
    //for real uses user file stream.
    using ( Stream stream = new MemoryStream() )
    {
        formatter.Serialize( stream, calculator.Add );

        stream.Position = 0;
        object obj = formatter.Deserialize( stream );

        //We actually serialized the entire graph with target!
        Behavior<long, bool> ff = (Behavior<long, bool>) obj;

        calculator = (BehavioralCalculator2) ff.Target;
    }

As whether this will not throw Serialization exception is only decided by the place where the anonymous
delegation was defined if for example it was defines in operator overload using the sources it will break
horribly, as it will complain that the c_DisplayClass is not serializable, it would seam that the custom delegation
class with overloaded operator method generates a special class before handling it to our method and it's
*Not* serializable, but even if this would work serialization of delegation types is wrong as it serializer
the delegate itself the method and the target so we would have a redundant serialization due to the target for
each method remains the same. The reason for DisplayClass is that we are creating a delegation that uses
an external delegation therefor the compiler will generate a helper class that will unfold the outer delegate
to a method call, this makes the class non serializable, and it's one of the major pitfalls of this pattern.


To overcome this issue and make anonymous methods serializable without serializing delegations and their
redundant targets, we need to come up with our own class to unfold the delegation, and change the sourcer,
to use it.



    //naive sourcer class, there is a place
    //for improvements and other features here.
    [Serializable]
    public class Sourcer<P,T>
    {
        public Behavior<P, T> Delegation { get; set; }

        public T Source( P x )
        {
            T result = this.Delegation( x );
            return result;
        }
    }

With the Sourcer class defined we can now proceed to change the BehavioralDelegate and exchange the sourcer
anonymous virtualization for the Sourcer class.


    public static BehavioralDelegate<P,T> operator +( BehavioralDelegate<P,T> source, BehavioralDelegate<P,T> target )
    {
        Sourcer<P, T> sourcer = new Sourcer<P, T>();
        sourcer.Delegation = target.Delegation;

        Behavior<P, T> sourcerBehavior = sourcer.Source;

        //add loging, tracking, auditing, validation, everything that's needed *but* as delegations!
        //as that's the principle of the design.
        return new BehavioralDelegate<P, T>( (Behavior<P, T>) Delegate.Combine( source.Delegation, sourcerBehavior ));
    }

Now the code to verify if the serialization works needs to be changed slightly to take the sources into account,
please do note that the serialization should be done within the boundaries of the sourcer and deserialization
outside.


    BinaryFormatter formatter = new BinaryFormatter();

    using ( Stream stream = new MemoryStream() )
    {
        formatter.Serialize( stream, calculator.Add.Target );

        stream.Position = 0;
        object obj = formatter.Deserialize( stream );

        Sourcer<long, bool> sourcer = (Sourcer<long,bool>)obj;
        calculator.Add = sourcer.Delegation;
    }

Ending Words


Summing this article up is this pattern for everyone, and should it replace the standard means of
programming? The answer is it depends, there are some very good applications for it, but it also can
introduce problems, as it's harder to change the style of writing and thinking about object's and data
as behavior, and the consequences of such shift. The grater control and virtualization give this pattern
grater flexibility over traditional style of writing plus we get a lot of this simply for free but we need
to be more aware how those things work and how can they break, combining this pattern with State or Behavior
serialization can yield dramatic performance benefit's as we can skip lot's of IO and database processing
or even create a custom behavioral/state data store that will be blazing fast, and this is where I would
look applications for this sort of writing style.

On the other hand this sort of programming is still
experimental and needs time and trial and error to set the proper patterns of handling things I just
barely scratched the surface of what can be done and what problems to expect for example one interesting
question is how garbage collection will react to delegation objects on a very long running process, but
I will try to resolve those issues and bring more what awesome and helpful things can be done with it to
the table in the next articles.

No comments:

 
ranktrackr.net