views:

146

answers:

4

Yesterday 2 of the guys on our team came to me with an uncommon problem. We are using a third-party component in one of our winforms applications. All the code has already been written against it. They then wanted to incorporate another third-party component, by the same vender, into our application. To their delight they found that the second component had the exact same public members as the first. But to their dismay, the 2 components have completely separate inheritance hierarchies, and implement no common interfaces. Makes you wonder... Well, makes me wonder.

An example of the problem:

Incompatible Types

public class ThirdPartyClass1
{
    public string Name
    {
        get
        {
            return "ThirdPartyClass1";
        }
    }

    public void DoThirdPartyStuff ()
    {
        Console.WriteLine ("ThirdPartyClass1 is doing its thing.");
    }
}

public class ThirdPartyClass2
{
    public string Name
    {
        get
        {
            return "ThirdPartyClass2";
        }
    }

    public void DoThirdPartyStuff ()
    {
        Console.WriteLine ("ThirdPartyClass2 is doing its thing.");
    }
}

Gladly they felt copying and pasting the code they wrote for the first component was not the correct answer. So they were thinking of assigning the component instant into an object reference and then modifying the code to do conditional casts after checking what type it was. But that is arguably even uglier than the copy and paste approach.

So they then asked me if I can write some reflection code to access the properties and call the methods off the two different object types since we know what they are, and they are exactly the same. But my first thought was that there goes the elegance. I figure there has to be a better, graceful solution to this problem.

+1  A: 

Add an interface. You could add one wrapper (that implements the interface) for each of the 3rd parties.

Anyway, if you have the code of those 3rd parties, you could skip the wrapper thing and directly implement the interface. I'm quite sure you don't have the source, though.

thelost
Nope. No source. That was the whole problem. :)
Jacques Bosch
+3  A: 

What about some wrappers?

public class ThirdPartyClass1 {
    public string Name {
        get {
            return "ThirdPartyClass1";
        }
    }

    public void DoThirdPartyStuff() {
        Console.WriteLine("ThirdPartyClass1 is doing its thing.");
    }
}

public interface IThirdPartyClassWrapper {
    public string Name { get; }
    public void DoThirdPartyStuff();
}

public class ThirdPartyClassWrapper1 : IThirdPartyClassWrapper {

    ThirdPartyClass1 _thirdParty;

    public string Name {
        get { return _thirdParty.Name; }
    }

    public void DoThirdPartyStuff() {
        _thirdParty.DoThirdPartyStuff();
    }
}

...and the same for ThirdPartyClass2, then you use the wrapper interface in all your methods.

Paolo Tedesco
Damn you guys answer fast! :) My question was answered before I even managed to post my own pre-written answer. I love it! I'm just not that good!
Jacques Bosch
@Quigrim: so it was a rethorical question? :)
Paolo Tedesco
@orsogufo: Yes it is. :) Based on the stackoverflow faq that says: "It's also perfectly fine to ask and answer your own question, but pretend you're on Jeopardy: phrase it in the form of a question."Thought it could be of some help to others.
Jacques Bosch
@Quigrim: I know, I was just kidding ;)
Paolo Tedesco
@orsogufo: Kid whole heartedly appreciated. :)
Jacques Bosch
+9  A: 

My first question was, are the 2 third-party component classes sealed? They were not. At least we have that.

So, since they are not sealed, the problem is solvable in the following way:

Extract a common interface out of the coinciding members of the 2 third-party classes. I called it Icommon.

public interface ICommon
{
    string Name
    {
        get;
    }

    void DoThirdPartyStuff ();
}

Then create 2 new classes; DerivedClass1 and DerivedClass2 that inherit from ThirdPartyClass1 and ThirdPartyClass2 respectively. These 2 new classes both implement the ICommon interface, but are otherwise completely empty.

public class DerivedClass1
    : ThirdPartyClass1, ICommon
{
}

public class DerivedClass2
    : ThirdPartyClass2, ICommon
{
}

Now, even though the derived classes are empty, the interface is satisfied by the base classes, which is where we extracted the interface from in the first place. The resulting class diagram looks like this.

alt text

So now, instead of what we previously had:

ThirdPartyClass1 c1 = new ThirdPartyClass1 ();
c1. DoThirdPartyStuff ();

We can now do:

ICommon common = new DerivedClass1 ();
common. DoThirdPartyStuff ();

And the same can be done with DerivedClass2.

The result is that all our existing code that referenced an instance of ThirdPartyClass1 can be left as is, by just swapping out the ThirdPartyClass1 reference for a ICommon reference. The ICommon reference could then be given an instance of DerivedClass1 or DerivedClass2, which of course in turn inherits from ThirdPartyClass1 and ThirdPartyClass2 respectively. And all just works.

I do not know if there is a specific name for this, but to me it looks like a variant of the adaptor pattern.

Perhaps we could have solve the problem with the dynamic types in C# 4.0, but that would have not had the benefit of compile-time checking.

I would be very interested to know if anybody else has another elegant way of solving this problem.

Jacques Bosch
More or less like my answer, but I would prefer composition to inheritance.
Paolo Tedesco
Even if they were sealed you could build a wrapper that inherits from the interface and just delegates to the wrapped class, but the wrapper couldn't be treated as that class...
saret
@orsogufo: Yes, but then you would have to promote every method and property through the wrapper if you went with composition. And in this case it's a whole lot of needless code as the base classes already satisfy the interface. Now if they were sealed, I'd agree with you. :)
Jacques Bosch
+4  A: 

If you're using .Net 4 you can avoid having to do alot of this as the dynamic type can help with what you want. However if using .Net 2+ there is another (different way) of achieving this:

You can use a duck typing library like the one from Deft Flux to treat your third party classes as if they implemented an interface.

For example:

public interface ICommonInterface
{
    string Name { get; }
    void DoThirdPartyStuff();
}

//...in your code:
ThirdPartyClass1 classWeWishHadInterface = new ThirdPartyClass1()
ICommonInterface classWrappedAsInterface = DuckTyping.Cast<ICommonInterface>(classWeWishHadInterface);

classWrappedAsInterface.DoThirdPartyStuff();

This avoids having to build derived wrapper classes manually for all those classes - and will work as long as the class has the same members as the interface

saret
@saret: Yes, I addressed the DLR in my answer. Did not know about the DuckTyping. Very nice. Thanx.
Jacques Bosch