5
votes
\$\begingroup\$

If I had many different Models that could all, through logic encapsulated by an adapter, produce items of type T, then I might maintain a producer/consumer component through the techniques demonstrated in the code.

When I came up with this, it seemed novel to overlay a producer onto a linked list - the reflection in the Add() method is the most interesting thing to me, because of the way a new adapter is supported through an additional constructor in the derived class.

The concept for reuse is that over time, if I need to place any arbitrary model data in the Producer queue:

  1. add client code: call producer.Add(AnySource) in client
  2. create adapter class for typeof(AnySource) to T
  3. add constructor: DerivedProdcuer(AnySource) - init enumerator

A couple things I think are interesting and was hoping to hear feedback on - they are original ideas to me (surely unoriginal to others).

  • the use of reflection in a base class that leverages the polymorphism of "this"
  • the combination a factory method and a collection method - Add(object) both creates a new instance of Producer and adds that instance to a linked list of which it is currently the tail of.
void Main()
{
    Producer<Foo> producer = new EntityProducer<Foo>();

    var fooConsumer = new Consumer<Foo>();
    fooConsumer.ItemReady += (sender, item) =>
    {
        Console.Write(item.Data);  
    };

    var modelA = new ModelA();
    modelA.Foos.Add(new Foo { Data = 1 });
    modelA.Foos.Add(new Foo { Data = 2 });
    modelA.Foos.Add(new Foo { Data = 3 }); 
    producer = producer.Add(modelA);    

    var modelB = new ModelB();
    modelB.Bars.Add(new Bar { Data = "4" });
    modelB.Bars.Add(new Bar { Data = "5" });
    modelB.Bars.Add(new Bar { Data = "6" });
    producer = producer.Add(modelB);

    fooConsumer.Consume(producer);
}

class Producer<T>
{
    protected IEnumerator<T> _enum;

    Producer<T> _parent;

    public Producer<T> Add(object obj)
    {
        Producer<T> res = null;
        var constructor = this.GetType().GetConstructor(new [] {obj.GetType()});
        res = (Producer<T>)constructor.Invoke(new [] {obj});       
        res._parent = this;
        return res;
    }

    public IEnumerable<T> Items
    {
        get 
        {
            if(_parent != null && _parent._enum != null)
                foreach (var foo in _parent.Items)
                    yield return foo;

            if(this._enum != null)
                foreach (var foo in Produce(this._enum))
                    yield return foo;
        }
    }

    IEnumerable<T> Produce(IEnumerator target)
    {
        while(target.MoveNext())
        {
            yield return (T)target.Current;
        }
    }
}

class EntityProducer<Foo> : Producer<Foo>
{
    public EntityProducer() { }

    public EntityProducer(ModelA modelA)
    {
        _enum = modelA.Foos.Cast<Foo>().GetEnumerator();
    }

    public EntityProducer(ModelB modelB)
    {
        _enum = new FooAdapter(modelB).Foos.Cast<Foo>().GetEnumerator();
    }
}

class Consumer<T>
{
    public delegate void ItemReadyHandler(Consumer<T> sender, T item);

    public event ItemReadyHandler ItemReady;

    void OnItemReady(T item)
    {
        if(ItemReady != null)
        {
            ItemReady(this, item);
        }
    }

    public void Consume(Producer<T> producer)
    {
        foreach (T item in producer.Items) 
        {   
            OnItemReady(item);
        }
    }
}

class Model
{ 
}

class Entity
{
}

class ModelA : Model
{
    public List<Foo> Foos { get { return _foos; } }
    readonly List<Foo> _foos = new List<Foo>();
}

class Foo : Entity
{
    public int Data { get; set; }
}

class ModelB : Model
{
    public List<Bar> Bars { get { return _bars; } }
    readonly List<Bar> _bars = new List<Bar>();
}

class Bar : Entity 
{
    public string Data { get; set; }
}

class FooAdapter 
{
    readonly ModelB _modelB;

    public FooAdapter(ModelB modelB)
    {
        _modelB = modelB;
    }

    public IEnumerable<Foo> Foos 
    {
        get
        { 
            foreach (var bar in _modelB.Bars) 
            {
                int converted;

                if(!int.TryParse(bar.Data, out converted))
                    throw new Exception("Conversion error while adapting Bar to Foo");

                yield return new Foo { Data = converted };
            }
        }
    }
}
\$\endgroup\$
0

1 Answer 1

4
votes
\$\begingroup\$

I think you have unclear vision of what the design patterns are.

Patterns are an idea (design pattern) or code (code pattern) proven by its effectiveness and adopted for usage and proven by practice behavior.

If this is a method, than you have a very strange vision of what the method Add looks like:

public Producer<T> Add(object obj) 
{
    //...
} 

You've made an assumption that object is instantiable class with a public constructor. Additionally, you assume that the object being constructed is of a concrete type. On top of all design errors, you assume that the object being constructed will be of type compatible with the generic type T where the method is located (later in code).

Then, you have created too many, too complex and excessive amounts of classes.

Here is the code after the design refactoring, implementing your design/code pattern:

public interface IEntity { }
public interface IEntity<T> : IEntity { T Data { get; set; } }

class Entity<T> : IEntity<T>
{
    public T Data { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<IEntity> items = new List<IEntity>();
        items.Add(new Entity<int> { Data = 1 });
        items.Add(new Entity<int> { Data = 2 });
        items.Add(new Entity<int> { Data = 3 });
        items.Add(new Entity<string> { Data = "4" });
        items.Add(new Entity<string> { Data = "5" });
        items.Add(new Entity<string> { Data = "6" });
        items.ForEach((item) =>
        {
            IEntity<string> str = item as IEntity<string>;
            if (str != null)
            {
                Console.Write(str.Data);
            }
            IEntity<int> val = item as IEntity<int>;
            if (val != null) 
            {
                Console.Write(val.Data);
            }
        });
    }
}

First, this code is working exactly the same, because, Event pattern can be implemented not only using events, but also using actions, because an event handler is an internal array for actions, associated with a particular event. In your scenario, there is only one event, so it can be implemented using the ForEach<T> extension method.

Second, if your classes differ only on the types of property, it's a good idea to move the code into a generic class. To support both hierarchies you can create a common interface (IEntity) for both classes, then you can totally replace the all appearance with generic implementation.

Lastly, you do not need the producer, because List<T>already implements IEnumerable<T>, which can be effectively used to process (consume) the collection of items by the extension methods in action, applied for each item.

The code you have created does not seems have proven effectiveness, safety and ease of use, so it not likely to be called a pattern. At most I can call your code an erroneous and simple implementation of a producer/consumer pattern, overloaded by generics and code invocation through reflection.

Final code will be very simple and easy to understand:

public interface IEntity { } 
public interface IEntity<T> : IEntity { T Data { get; set; } } 
public class Entity<T> : IEntity<T> 
{ 
    public T Data { get; set; } 
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ you make a few good points but, fankly, your tone is offending - i'm new to design patterns, not design \$\endgroup\$ Oct 8, 2011 at 20:04
  • \$\begingroup\$ @Gabriel: I'm not a native speaker, i'm sorry if my past make you unhappy. I can remove some parts, but the idea is that we can always benefit from generics, and nowadays there is only a few situations where you need to use non-genetic version of classes, intefaces or methods. \$\endgroup\$ Oct 9, 2011 at 4:46
  • \$\begingroup\$ Thanks for the clarification. I understand and appreciate the feedback, Artur. \$\endgroup\$ Oct 9, 2011 at 18:28
  • \$\begingroup\$ I would do two things in addition. I would change the signature of Add(T instance) into Add(param T[] instances) so I could add a collection of entities at once. I also don't see why you need to perform the type check to see if the item is an int or a string before writing it to the console. Every type has ToString() through object, so you should be able to remove some code in that section. \$\endgroup\$
    – neontapir
    Oct 13, 2011 at 14:24
  • \$\begingroup\$ +1: Despite the cultural missteps of a non-native speaker, this is very good code. \$\endgroup\$
    – surfasb
    May 11, 2013 at 16:50

Not the answer you're looking for? Browse other questions tagged or ask your own question.