6

I have a situation where I have an external library. In short, I need to be able to adapt my type to theirs, and theirs to mine. The library has a collection like so:

interface IExternalCollection
{
    void Add(IExternal item);
    IExternal Get(int index);
}

Now, let me start off by explaining the most straightforward solution. I have a regular adapter for this collection, and regular adapters for IExternal and IInternal:

interface IInternalCollection
{
    void Add(IInternal item);
    IInternal Get(int index);
}

class InternalCollectionAdapter : IInternalCollection
{
    private IExternalCollection collection;

    public InternalCollectionAdapter(IExternalCollection collection)
    {
        this.collection = collection;
    }

    public void Add(IInternal item)
    {
        collection.Add(new ExternalAdapter(item));
    }

    public IInternal Get(int index)
    {
        return new InternalAdapter(collection.Get(index));
    }
}

This solution requires 2 adapters for the same type, and my ExternalAdapter adapts InternalAdapter (there is no internal concrete type). What's worse is, my internal interface only has 2 methods, but the external interface has many (15-ish). In my code, I only need those 2, which is why I designed my interface to only have those 2, but now I need to cluster my interface with all 15 in order to be able to create the ExternalAdapter.

So I figured, surely there is a better way. I read up on the 2WayAdapter, and it looks like this:

class TwoWayAdapter : External, IInternal
{
    public void Show()
    {
        base.Display();
    }     
}

This allow me to write a CollectionAdapter like this:

class InternalCollectionAdapter : IInternalCollection
{
    private IExternalCollection collection;

    public InternalCollectionAdapter(IExternalCollection collection)
    {
        this.collection = collection;
    }

    public void Add(IInternal item)
    {
        collection.Add((IExternal)item);
    }

    public IInternal Get(int index)
    {
        return (TwoWayAdapter)collection.Get(index);
    }
}

And call it like so:

collection.Add(new TwoWayAdapter()); 
collection.Get(0);

This works, and I only need 1 adapter for the type, and I can design its API as I please.

However, this external library creates its own objects of type External. So when I call Get on the external collection, it might return a pure newed up instance of External, making my cast to TwoWayAdapter fail.

My last solution is to allow access to the adaptee, like this:

class InternalAdapter : IInternal
{
    private IExternal adaptee;
    public Object Adaptee { get { return adaptee; } }

    public InternalAdapter(IExternal adaptee)
    {
        this.adaptee = adaptee;
    }

    public void Show()
    {
        adaptee.Display();
    }
}

This will change my CollectionAdapter to this:

class InternalCollectionAdapter : IInternalCollection
{
    private IExternalCollection collection;

    public InternalCollectionAdapter(IExternalCollection collection)
    {
        this.collection = collection;
    }

    public void Add(IInternal item)
    {
        collection.Add((IExternal)item.Adaptee);
    }

    public IInternal Get(int index)
    {
        return new InternalAdapter(collection.Get(index));
    }
}

As you can see, this requires my internal interface to have a Object Adaptee { get; } property, which smells really really bad.

I can only imagine that this situation has come up many times, but my efforts on the internet has been in vain.

I would like a solution where I can keep my API clean of all the external methods I don't need, and preferably one where the Adaptee stays hidden.

EDIT

After pondering a bit more on this issue, I've come up with a solution which seems to live up to my requirements, i.e clean API that doesn't include stuff I don't need and doesn't expose the Adaptee.

Is is a combination of my previous iterations, and goes as follews:

  • To adapt their type to mine, I use a regular adapter.

    class InternalAdapter : IInternal
    {
        private IExternal adaptee;
    
        public InternalAdapter(IExternal adaptee)
        {
            this.adaptee = adaptee;
        }
    
        public void Show()
        {
            adaptee.Display();
        }
    }
    
  • To adapt this type back to theirs I create a TwoWayAdapter.

    class TwoWayAdapter : External, IInternal
    {
        public void Show()
        {
            base.Display();
        }     
    }
    
  • Then my CollectorAdapter looks like this:

    class InternalCollectionAdapter : IInternalCollection
    {
        private IExternalCollection collection;
    
        public InternalCollectionAdapter(IExternalCollection collection)
        {
            this.collection = collection;
        }
    
        public void Add(IInternal item)
        {
            collection.Add((IExternal)item);
        }
    
        public IInternal Get(int index)
        {
            return new InternalAdapter(collection.Get(index));
        }
    }
    

Now you might notice that this solution has its problems of its own, namely, I cannot get one of their types, and then convert it to mine, convert it back, and add it to their collection.

This solution works in my case in this project only because get retrieves a new instance, and add is only used with a new instance (newed up by me), i.e the same object is not getted and added.

The reason I have made this an edit and not an answer is that, this doesn't solve the more general problem I propose in this question.

Just to clarify what I'm asking for: I would love if you know of any articles, blogs or books that solve this issue with a technique or pattern. I would also appreciate if someone could reference what they have done in previous situations.

Chris Wohlert
  • 529
  • 3
  • 15
  • 1
    Have you considered just using `dynamic` variables, and not bothering with all these interfaces? – Robert Harvey Jun 10 '16 at 15:26
  • Can you make it clear as to why you need to adapt the types to one another? Maybe there is some other solution. – Snoop Jun 10 '16 at 16:28
  • @RobertHarvey, Could you provide an example? Link or answer to how that would look in my example is both fine. – Chris Wohlert Jun 10 '16 at 18:23
  • @StevieV, Well, I need to adapt the external type so that my Library is not dependent upon it. I need to adapt my type to theirs because they need to do some work with it, and they obviously is independent of mine. – Chris Wohlert Jun 10 '16 at 18:25
  • Why don't you just have your concrete implementation depend on an *interface* to the external type? So then your library is not directly dependent on that type. – Snoop Jun 10 '16 at 19:08
  • @StevieV, How would I not be dependent on it if I use its type in my library? Not to mention that I dont have a contrete implementation, I only have the one created by the external library, as I state in the question. – Chris Wohlert Jun 10 '16 at 21:28
  • 3
    Without some sort of workflow (i.e. what you actually do with your/their objects) it's hard to provide advice. Maybe your proposals are sound; maybe they're not. For instance, why do you need to adapt collections of items? Why not simply items (i.e. let your internal object have a constructor using an external thingy, and a method to convert to an external) ? To me what you're proposing sounds like you constantly switch from external to internal and vice versa. Is it the case? Aren't you overengineering this whole thing? –  Jun 13 '16 at 08:27
  • I'm not sure what you mean by (let your internal object have a constructor using an external thingy, and a method to convert to an external), I don't have an internal object, only an interface. I adapt the collection because it is an external dependency, this collection exists in the external library. – Chris Wohlert Jun 13 '16 at 08:32
  • "I only need those 2, which is why I designed my interface to only have those 2, but now I need to cluster my interface with all 15 in order to be able to create the ExternalAdapter" Sounds like the very definition of a [mixin](https://en.wikipedia.org/wiki/Mixin). Also, the visitor pattern may be worth considering. Don't have time to give a full fledged answer now but see if those are of interest to you. – candied_orange Jun 14 '16 at 15:17
  • @CandiedOrange, Thank you, I will look into these. – Chris Wohlert Jun 15 '16 at 06:26
  • I think Jon Skeet's answer to a different question crushes my dreams a bit. http://stackoverflow.com/questions/17548605/is-it-possible-to-superimpose-an-interface-on-an-external-class-in-c The last part of his answer suggests there is no clean way of doing this. – Chris Wohlert Jun 15 '16 at 08:09

1 Answers1

0

I've recently become a huge fan of functional programming styles. Here's a demo of how I would start working on this, note I didn't compile or even put the code into visual studio. If you carry this idea forward I think you only need two more methods to go the other way! If I had more time I would try to take it to using Generics as well which would reduce the coding 50%.

public static class XAdapters{
   public static IExternalCollection AsExternalCollection(this InternalCollection intneralCollection) where Q:new(){
        var externalCollection = new ExternalCollection();
        inputCollection.ToList().ForEach(input=>{
           //create output item is an extenion method too
           output.Add(input.CreateNewOutputItem());
       }
       return externalCollection;
   }

  public static IExternalCollection Add(this IExternalCollection externalCollection, InternalCollectionItem item){

    var x =  item.CreateNewExternalItem();
    externalCollection.Add(x);
    return externalCollection;
   }

}

To use the code above:

var externalCollection = InternalCollection.AsExternalCollection.Add(someInternalItem);

Here's the factory converters:

public static extneralItem createNewExtneralItem(this intneralItem internal)          {
     var extern = new externalItem();
     extern.property = internal.property; //etc...
     return extern;
}
John Peters
  • 167
  • 6
  • An interesting method of doing adapters. Looking at your factory, would `external` and `internal` need to have the same API, to do a mapping like that? – Chris Wohlert Jun 20 '16 at 06:26
  • The example above is more than "doing adapters" it's a functional approach which shows how extension methods "do things for you". In this case it's to adapt one thing to the other. They do not need to have the same API. Of course we are guessing a bit because we can't see the total relationships between the two item types. This is typically referred to as Mapping. Mapping tries to convert one object type to another. – John Peters Jun 20 '16 at 18:55