Common interface for two third-party classes I don't control. External Polymorphism?
Asked Answered
R

2

8

I need a little pattern direction here. New to C#.

I'm working with a third-party development kit that wraps a web service. There are two specific classes I deal with that, while relatively similar, are in two different namespaces in the dev kit and there's no common base class. I'd like to program against a common interface for them both however. I haphazardly threw together an implementation that essentially wraps the wrapper, but I feel rather certain it's not the most efficient method due to the incessant type casting.

I've been digging through articles on adapters, interfaces, extension methods, etc., but I'm running low on time, so if I could get a push in one direction that'd be greatly appreciated.

using ThirdParty.TypeA.Employee;
using ThirdParty.TypeB.Employee;

public class Employee
{
     private object genericEmployee;

     private EmployeeType empType;

     public enum EmployeeType
     {
          TypeA = 0;
          TypeB = 1;
     }   

     public Employee(Object employee, EmployeeType type)
     {
         genericEmployee = employee;
         empType = type;
     }

     public String Name
     {
         if (empType == EmployeeType.TypeA)
             return (ThirdParty.TypeA.Employee)genericEmployee.Name;
         else
             return (ThirdParty.TypeB.Employee)genericEmployee.Name;
     }

     public String Age
     {
         if (empType == EmployeeType.TypeA)
             return (ThirdParty.TypeA.Employee)genericEmployee.Age;
         else
             return (ThirdParty.TypeB.Employee)genericEmployee.Age;
     }
 }

Rev 2:

class EmployeeTypeAAdapter : TypeA, IEmployeeAdapter
{
    TypeA _employee;
    public EmployeeTypeAAdapter(TypeA employee) 
    { 
         _employee = employee
    }

     public String Name
     {
        get { return _employee.Name; }  
        set { _employee.Name = value; }
     } 

      public String Balance
      {
        get
        {
            if (_employee.Balance != null)
            {
                decimal c = _employee.Balance.Amount;
                return String.Format("{0:C}", c);
            }
            else
            {
                return "";
            }
          }
       }  

       //...
}

class EmployeeTypeBAdapter : TypeB, IEmployeeAdapter
{
    TypeB _employee;
    public EmployeeTypeAAdapter(TypeB employee) 
    { 
         _employee = employee
    }

     public String Name
     {
        get { return _employee.Name; }  
        set { _employee.Name = value; }
     } 

      public String Balance
      {
        get
        {
            if (_employee.Balance != null)
            {
                decimal c = _employee.Balance.Amount;
                return String.Format("{0:C}", c);
            }
            else
            {
                return "";
            }
          }
       }  

     //....
}
Rittenhouse answered 23/1, 2013 at 18:48 Comment(3)
Are TypeA and TypeB public and well-known, I mean do you really need to introduce enum? You could go a generic way.Norean
What is the types of TypeA and TypeB, how are the classes declared? Just equal set of properties and no base class? So you can generalize them only by object?Norean
There is no base class in the third party library. The enum was just a convenience property I created to avoid having to use TypeOf or IS to determine the type of object (TypeA or TypeB) every time I accessed a property.Rittenhouse
N
9

Try this approach:

public interface IEmployeeAdapter
{
    string Age { get; set; }
    string Name { get; set; }
}

class EmployeeTypeAAdapter : TypeA, IEmployeeAdapter
{
    public EmployeeTypeAAdapter(TypeA employee) { }
}

class EmployeeTypeBAdapter : TypeB, IEmployeeAdapter
{
    public EmployeeTypeBAdapter(TypeB employee) { }
}

public static class EmployeeAdapterFactory
{
    public static IEmployeeAdapter CreateAdapter(object employee, EmployeeType type)
    {
        switch (type)
        {
            case EmployeeType.TypeA: return new EmployeeTypeAAdapter((TypeA)employee);
            case EmployeeType.TypeB: return new EmployeeTypeBAdapter((TypeB)employee);
        }
    }

    // or without enum

    public static IEmployeeAdapter CreateAdapter(object employee)
    {
        if (employee is TypeA) return new EmployeeTypeAAdapter((TypeA)employee);
        if (employee is TypeB) return new EmployeeTypeABdapter((TypeB)employee);
    }

    // or better introduce sort of type map
}

Another proper name is EmployeeProxy, as you prefer.

Norean answered 23/1, 2013 at 18:54 Comment(7)
Would I then return the properties in the adaptors detailed above? Considering how the two objects (TypeA and TypeB) are 99% identical (there might be one or two out of 30 properties/methods that have a different name), and only real difference in type (one is of type ThirdPartyAdaptor.TypeA.Employee and the other is ThirdPartyAdaptor.TypeB.Employee) this would seemingly create almost duplicate classes. See Rev 2 above.Rittenhouse
@RyanMac: So what is the difficulties? Sorry don't follow you. You put all the shared properties into the interface, it will be implemented automatically. If a property name is different in underlying type and interface you can "help" to implement/match it properly.Norean
@RyanMac: Also you can introduce an abstract base class containing shared properties and other helper methodsNorean
Sorry if there's some OO concept that I'm missing. The problem is there's implementation code I generally need to take on each property before I return it to the client (the Balance property above being one example). I assume this logic would go in the adaptor, the only problem then being, it would have to go in the adaptor for TypeA and TypeB creating essentially two identical classes. What I think I need is a base class (that seemingly should exist in the third party library but doesn't) that would encapsulate all the shared implementation logic. Does that help?Rittenhouse
It seems as if I'm attempting what some refer to as external polymorphism.Rittenhouse
I like this, but you lose me at the adapters' empty constructors. Do they copy the TypeX objects into themselves somehow, or do you have to write a line to copy each property?Rhadamanthus
@EricEskildsen: You have to write each property by hand if you want to expose just some of them. However if you want to expose all of them and automatically then try AutoMapper.Norean
T
3

What you're trying to do is known as Duck typing. You can do this using adapter classes and a shared interface, but creating these adapters manually requires a lot of repetitive glue code. One way you can get around writing the glue code is to construct the adapter type dynamically. You can do this yourself via IL Emit (a worthwhile exercise if you've never had a chance to play with it before, though there can be quite a few boundary cases to consider.) If you're just interested in getting it working, however, you might check out this project as a place to start. The C# 'dynamic' type could also be used (and winds up doing some of the same code generation behind the scenes), but it doesn't give you a reference you can pass around to non-dynamic code as if it were an interface type.

Typehigh answered 23/1, 2013 at 23:54 Comment(1)
Could you please tell me what this project is about please : github.com/deftflux/DuckTyping/tree/master/DeftTech.DuckTypingHeinrike

© 2022 - 2024 — McMap. All rights reserved.