views:

107

answers:

3

I made the example below which enables a factory to pack objects with functionality, but the problem is that the functionality is divorced from the object.

My ultimate goal is attach functionality such as log, and save and display which operates on the specific properties that each different object has.

How would I keep the exterior adorning aspect of this example but enable functionality such as "save" which saves the object's data to a database or "log" which logs its activity?

using System;
using System.Collections.Generic;

namespace FuncAdorn3923
{
    class Program
    {
        static void Main(string[] args)
        {

            Customer customer = new Customer();
            ObjectFactory.Instance.AdornFunctionality(customer, "add");
            Console.WriteLine(customer.CallAlgorithm("add", 64, 36));

            Employee employee = new Employee();
            ObjectFactory.Instance.AdornFunctionality(employee, "add");
            ObjectFactory.Instance.AdornFunctionality(employee, "subtract");
            Console.WriteLine(employee.CallAlgorithm("add", 5, 15));
            Console.WriteLine(employee.CallAlgorithm("subtract", 66, 16));

            Console.ReadLine();
        }
    }

    public class ObjectFactory
    {
        private static ObjectFactory singleton;

        public void AdornFunctionality(AdornedObject ao, string idCode)
        {
            Func<int, int, int> add = (i, j) => i + j;
            Func<int, int, int> subtract = (i, j) => i - j;

            switch (idCode)
            {
                case "add":
                    ao.LoadAlgorithm(idCode, add);
                    break;
                case "subtract":
                    ao.LoadAlgorithm(idCode, subtract);
                    break;
            }
        }

        public static ObjectFactory Instance
        {
            get
            {
                if (singleton == null)
                    singleton = new ObjectFactory();
                return singleton;
            }
        }

    }

    public abstract class AdornedObject
    {
        private Dictionary<string, Func<int, int, int>> algorithms = 
            new Dictionary<string, Func<int, int, int>>();

        public void LoadAlgorithm(string idCode, Func<int,int,int> func)
        {
            algorithms.Add(idCode, func);
        }

        public int CallAlgorithm(string idCode, int i1, int i2)
        {
            Func<int,int,int> func = algorithms[idCode];
            return func.Invoke(i1, i2);
        }
    }

    public class Customer : AdornedObject
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int NumberOfProductsBought { get; set; }
    }

    public class Employee : AdornedObject
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }

}
+1  A: 

This looks like a classic case for the visitor pattern.

The algorithms (visitors) will need to be tailored to the objects they adorn (or visit), or at least tailored to some interface that your adorned objects implement.

For example, your Employee object might have a method like the following:

public class Employee: IEmployee {
    public void Accept(IEmployeeAlgorithm algorithm) {
        algorithm.Visit(this);
    }
}

IEmployeeAlgorithm objects would have an interface similar to this (these could just as easily be Action<Employee> delegates, or use other signatures as needed):

public interface IEmployeeAlgorithm {
    void Visit(IEmployee employee);
}

Finally, if you want to give the algorithms keys and invoke them dynamically, you could do that in a fashion similar to what you've got now by storing them in an IDictionary<string, IEmployeeAlgorithm> member.

Jeff Sternal
A: 

I would check out the PostSharp project. They allow this kind of separation of concerns and enable some easy ways to accomplish this. They allow you to externally define code which is added to classes/properties at run time. I'm not sure about your specific requirements (or this particular example) but you should check it out.

luke
+2  A: 

I would personally recommend a better design pattern, like the visitor pattern, but for what its worth you can make your code work by throwing away type safety. Use Delegate rather than its derived classes Func and Action:

    static void Main(string[] args)
    {

        Customer customer = new Customer();
        ObjectFactory.Instance.AdornFunctionality(customer, "add");
        Console.WriteLine(customer.CallAlgorithm("add", 64, 36));

        Employee employee = new Employee();
        ObjectFactory.Instance.AdornFunctionality(employee, "add");
        ObjectFactory.Instance.AdornFunctionality(employee, "subtract");
        ObjectFactory.Instance.AdornFunctionality(employee, "save");
        Console.WriteLine(employee.CallAlgorithm("add", 5, 15));
        Console.WriteLine(employee.CallAlgorithm("subtract", 66, 16));
        Console.WriteLine(employee.CallAlgorithm("save"));

        Console.ReadLine();
    }
}

public class ObjectFactory
{
    private static ObjectFactory singleton;

    public void AdornFunctionality(AdornedObject ao, string idCode)
    {
        Func<int, int, int> add = (i, j) => i + j;
        Func<int, int, int> subtract = (i, j) => i - j;
        Action save = () => Console.WriteLine("{0} has been saved", ao.ToString());

        switch (idCode)
        {
            case "add":
                ao.LoadAlgorithm(idCode, add);
                break;
            case "subtract":
                ao.LoadAlgorithm(idCode, subtract);
                break;
            case "save":
                ao.LoadAlgorithm(idCode, save);
                break;
        }
    }

    public static ObjectFactory Instance
    {
        get
        {
            if (singleton == null)
                singleton = new ObjectFactory();
            return singleton;
        }
    }

}

public abstract class AdornedObject
{
    private Dictionary<string, Delegate> algorithms = new Dictionary<string, Delegate>();

    public void LoadAlgorithm(string idCode, Delegate func)
    {
        algorithms.Add(idCode, func);
    }

    public object CallAlgorithm(string idCode, params object[] args)
    {
        Delegate func = algorithms[idCode];
        return func.DynamicInvoke(args);
    }
}
Juliet
Why visitor instead of decorator?
SnOrfus
Decorator is plausible, for sure, but decorators are supposed to implement the interface of the classes they decorate, so it can be a lot more work. (Additionally, given Edward's example, it looks like decorators would provide a different abstraction than the one he's looking for: encapsulating extended versions of existing interfaces rather than encapsulating algorithms that operate on those interfaces.)
Jeff Sternal