Object design question - make 2 collections interchangeable

Object design question - make 2 collections interchangeable

Old forum URL: forums.lhotka.net/forums/t/5870.aspx


xxxJasonxxx posted on Monday, November 24, 2008

Object design question...

We have three CSLA-based readonly collections and we want to be able to use them interchangeably.

Our first thought was to implement a common interface on the child objects. 

But I don't think that gets us all the way to where we need to be.

Implementing an interface on the child objects would make the child objects interchangeable but not the parent objects -- the parent collection objects would still be dealt separately as their different types.

We toyed with the idea of implementing an interface on the parent collection objects, but everybody seemed stumped when we tried to work out the details.

How do we make our three collections interchangeable?

JonStonecash replied on Monday, November 24, 2008

The simplest way to do this is to create a parent class from which all three of the CSLA child objects inherit.  That is, you define a "Super Child" class that inherits from BusinessBase.  Your three child classes inherit from "Super Child". 

The next step is to define a "Super Collection" class that contains "Super Children".  Your three collections can inherit from "Super Collection".  Each of these derived collections might provide type safe methods for manipulating the collection.   

The next step is to define a factory method/class to create the appropriate collection.  It will return an object of type "Super Collection".  All of your general application logic will work with "Super Collection"; one time you might get a "red" collection, the next time you might get a "blue" collection.  If you do not have any requirements for maintaining type safety for the members of a collection, you could skip the child specific collections and just use the "Super Collection" object directly. 

One of the issues that you will have to deal with is the extent to which your general application logic has to deal with the differences between the child objects.  If you can define a set of abstract methods that covers all of the functionality, you could easily ignore all of the differences in your general application logic.  

There are a lot of small design decisions that you have to deal with but it is not that difficult to do what you want to do. 

Jon Stonecash

JoeFallon1 replied on Monday, November 24, 2008

I have some requirements to be able to treat BOs and Collections polymorphically so I implemented Interfaces for them.

BOs:

Public Interface IMyBusinessObject
  Inherits IEditableBusinessObject
  Function GetIdValue() As Object
  ReadOnly Property BrokenRulesCollection() As Validation.BrokenRulesCollection
  Property IsOwner() As Boolean
  ReadOnly Property IsSelfValid() As Boolean
End Interface

Collections:

Public Interface IBusinessCollection
  ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As IMyBusinessObject
  ReadOnly Property IsDirty() As Boolean
  ReadOnly Property IsValid() As Boolean
  ReadOnly Property Count() As Integer
  ReadOnly Property IsSelfValid() As Boolean
End Interface

Then in my Base class for MyBusinessListBase:

#Region " IBusinessCollection Implementation "

'useInterface is a fake parameter and is only used to change the method signature so we can Overload the Item method. It does not use the Boolean value at all.
'This way the Base Item method can still return a strongly typed child BO.
'This Item method returns the Interface version of the child BO as IEditableBusinessObject.
Public Overloads ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As IMyBusinessObject Implements IBusinessCollection.Item
  Get
    Return MyBase.Item(index)
  End Get
End Property

Public Overloads ReadOnly Property Count() As Integer Implements IBusinessCollection.Count
  Get
    Return MyBase.Count
  End Get
End Property

Public Overloads ReadOnly Property IsDirty() As Boolean Implements IBusinessCollection.IsDirty
  Get
    Return MyBase.IsDirty
  End Get
End Property

'JF - no short circuiting involved here.
'run through all the child objects and if any are invalid then the collection is invalid
'call IsValid on all children so that when we examine Broken Rules we get the right set of values.
'Rocky stopped looping after he found the first invalid child.
Public Overrides ReadOnly Property IsValid() As Boolean Implements IBusinessCollection.IsValid
  Get
    Dim result As Boolean = True
    For Each child As C In Me
      If Not child.IsValid Then
       result = False
     End If
    Next
    Return result
  End Get
End Property

Public ReadOnly Property IsSelfValid() As Boolean Implements IBusinessCollection.IsSelfValid
  Get
    Dim result As Boolean = True
    For Each child As C In Me
      If Not child.IsSelfValid Then
        result = False
      End If
    Next
    Return result
  End Get
End Property

#End Region

 

Joe

SonOfPirate replied on Tuesday, November 25, 2008

I can summarize the steps as follows:

  1. Implement a common interface on all of your child objects.
  2. Create an interface for your parent collection that exposes all of the methods you need using the common interface.
  3. Explicitly implement the collection interface on your parent collection.
  4. Implement strongly-typed equivalents, if appropriate, in your parent collection.

Note: Try to avoid inheriting interfaces unless the explicitly extend the base interface or are required in order to satisfy the sub-interface.

Joe gives a great example but I'm not sure about the useInterface parameter.  Here's how I accomplish this (using C# - just to mix things up):

public interface IChildObject
{
    void DoSomething();
}
 
public interface IParentCollection
{
    IChildObject this[int index] { get; }
}
 
public class ChildObject1 : IChildObject
{
    public void DoSomething() { Console.WriteLine("ChildObject1"); }
}
 
public class ChildObject2 : IChildObject
{
    public void DoSomething() { Console.WriteLine("ChildObject2"); }
}
 
public class ParentCollection1 : ..., IParentCollection
{

    public ChildObject1 this[int index]
    {
        get { ... }
    }
 
    // This will be pseudo-private
    IChildObject IParentCollection.this[int index]
    {
        get { return this[index] as IChildObject; }
    }
}
 
public class ParentCollection2 : ..., IParentCollection
{
    public ChildObject2 this[int index]
    {
        get { ... }
    }
 
    // This will be pseudo-private
    IChildObject IChildObject.this[int index]
    {
        get { return this[index] as IChildObject; }
    }
}

Now, you can make use of the collections as follows:

IParentCollection col = new ParentCollection1();
IChildObject child = col[0];
child.DoSomething();

col = new ParentCollection2();
col[0].DoSomething();

In this case, the output will be "ChildObject1" for the first call and "ChildObject2" for the second.

As long as you implement what you need in the IChildObject interface, you can get everything in this manner.

Note: You could implement a common base class for your collections that implements the IParentCollection interface so you don't have to repeat this code.  I would suggest making it generic so that you can allow the this[...] (Item in VB) property to be strongly-typed as shown in the example.  By placing a constraint that T must implement the IChildObject interface, you'll be all set.  Here's how the base collection would look:

public abstract class ParentCollectionBase<T> : ..., IParentCollection
    T : IChildObject
{
    public abstract T this[int index] { get { ... } }
 
    // This will be pseudo-private
    IChildObject IParentCollection.this[int index]
    {
        get { return this[index] as IChildObject; }
    }
}

Now you can derive from this class, specify the appropriate child object type for T, and implement/override this (Item in VB) and you'll have the behavior you are looking for.

I hope that clears things up.

 

Copyright (c) Marimer LLC