Object-oriented – Extend, wrap, or both to add generics to a class that should have had them

design-patternsgenericsobject-orientedobject-oriented-design

So I'm using a C# framework that has a great example of where generics would be useful, except they weren't used. For simplicity's sake, we will say it was a list (I know C# has its own list), but instead of being List where I can specify the T, they made it List, resulting in nightmarish type checking throughout the code.

I want to fix this, but I can't edit the framework.

So I see three ways to fix this.

Extend, wrap, or both.

So the basic question is, which is the best way to add generics?

Is there a design pattern particulary for this?

Now, while I'm not sure wrapping or extending is better, if this was a simple case I would have gone with one of them and not even considered doing both.

But, the CustomList is used everywhere, and I only want to replace it bit by bit.

Extending allows me to use the TypedCustomList as a CustomList, so that if I change where CustomList are being created to instead by TypedCustomList, later in code's execution the code can still treat it like a CustomList.

But, I can't treat a CustomList object as a TypedCustomList (even if I ensure the generic type is correct for that given CustomList). (Or am I missing how to do this, which would solve the problem?)

If a wrap it, I can now work with existing CustomLists, but I have to expose the internal CustomList object to pass it on to other code (for example, if I call a method expecting CustomList, I have to do instanceOfTypedCustomList._customList, I cannot pass instanceOfTypedCustomList).

If I wrap and extend it, I get take an existing CustomList and treat it like a TypedCustomList while still being able to treat my TypedCustomList as a CustomList. But, as you can see below, the 'Both' option looks the worst.

Extending

public class TypedCustomList<T> : CustomList
{
  public TypedCustomList()
    :base()
  {
  }

  public T getElementAt(int i)
  {
    return (T) base.getElementAt(i);
  }

  public void addElement(T element)
  {
    base.addElement(element);
  }

  ...ect.
}

Wrapping:

public class TypedCustomList<T>
{
  CustomList _customList { get; private set;}
  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ...ect.
}

Both:

public class TypedCustomList<T> : CustomList
{
  private CustomList _customList;

  public TypedCustomList()
    :base()
  {
    _customList = null;
  }

  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    if(_customList == null)
    {
      return (T) base.getElementAt(i);
    }
    else
    {
      return (T) _customList.getElementAt(i);
    }
  }

  public void addElement(T element)
  {
    if(_customList == null)
    {
      base.addElement(element);
    }
    else
    {
      _customList.addElement(element);
    }
  }

  ...ect.
}

P.S.

More background which may be relevant to the problem.

There is one class that has a CustomList that is used all over the application, with different parts of the application with each instance having different object types (over a dozen in total). I want to eventually have this class have a unique TypedCustomList for every use. But until I can be sure that all references to it's CustomList are gone, I wanted to add some error handling. So I was adding something like the following:

public ClassThatUsesCustomList
{
...
private CusotmList _list;
private TypedCustomList<SomeObject> _SomeObjectCustomList;

public CustomList list
{
    get
    {
      //Already existing code
      if(_list == null)
      {
        _list = new CustomList();
      }

      //My code
      catchMissedGetSet(_list);


      return _list;
    }
    set
    {
      catchMissedGetSet(value);
      _list = value;
    }
}

public SomeObjectCustomList
{
  get
  {
    if(_SomeObjectCustomList == null)
    {
      _SomeObjectCustomList = new TypedCustomList<SomeObject>();
    }

    return _SomeObjectCustomList;
  }
  set
  {
    _SomeObjectCustomList = value;
  }
}


private void catchMissedGetSet(CustomList cl)
{
  bool findMissedSpots = true;

  if(cl == null || cl.Count == 0 || cl[0] is SomeObject)
  {
    SomeObjectCustomList = new SomeObjectCustomList(cl);  //Using wrapping.
    if(findMissedSpots && System.Diagnostics.Debugger.IsAttached)
    {
      System.Diagnostics.Debugger.Break();  
      //Hey, you missed a place.  Use step out to find it.
    }
  }
}


...
}

I did this to catch any spots I missed and to avoid any bugs. It requires me wrapping it (as far as I can tell).

Best Answer

The answer is wrapping, but it is considerably more work than implied here. I see why you are trying to do this though, because after a certain size of the code base it indeed can be a nightmare to work with typecasting every time you call a library function. Subclassing CustomList is not advisable, as it would expose all the functions that return or expect an object. Having a public property as in the second example is, for the same reason, also unadvisable. I believe you are facing a situation similar to the following:

interface Producer
{
  CustomList produce();
}

interface Consumer
{
  void consume(CustomList list);
}

This is how I would wrap it:

public class TypedCustomList<T>
{
  private readonly CustomList _customList;
  internal CustomList customList
  {
    get { return _customList; }
  }

  internal TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ... ect.
}

The producer:

class TypedProducer<T>
{
  private readonly Producer producer = new Producer();
  public TypedCustomList<T> produce()
  {
    return new TypedCustomList<T>(producer.produce())
  }
}

The consumer:

class TypedConsumer<T>
{
  private readonly Consumer consumer = new Consumer();
  public void consume(TypedCustomList<T> list)
  {
    consumer.consume(list.customList);
  }
}

You will need to put the wrapping classes to their own assembly, so that they hide the internal parts.

I can safely suggest you this solution, which I beleive generally considered a good practice. You will need to wrap the subset of the library you are using in this manner.

Related Topic