1

I have 2 different 3rd party assemblies that provide the same API for a business service and using the same class names (~40 classes/types/extensions) but located in different assemblies:

    Company.Assemply.V1
    Company.Assemply.V2

I reference both assemblies in the project.

There is no common interface for these assemblies, and no way for the 3rd party to provide a common interface

So, the c# compiler treat every type in the two assemblies as a different type.

I want to implement a class Myservice for every assembly to support both versions V1/V2.

I use the following code to implement Myservice.V1.Myclass

    //#define V1

    #if V1
       using  Company.Assemply.V1;
    #else
       using  Company.Assemply.V2;
    #endif

    #if V1
      namespace Myservice.V1
    #else
      namespace Myservice.V2
    #endif
    {
       //my implementation that use all classes /types in any v1/v2 assembly
        class MyClass {.... }
     }

Then i copy and paste the same code in other c# file MyClassV2.cs (about 400 lines) to get Myservice.V2.Myclass and uncomment the compiler flag #define V1

I can't use Generics

        MyClass  <T> where T:??

because there is no common interface for T

The two class are working fine.

The problem is when maintaining v1, I have to copy/paste the code in the other file MyClassV2.cs and uncomment the compiler flag #define V1 to support V2.

Is there a better way / suitable design pattern/refactoring technique that can solve such a problem. I want to use/maintain one code base and avoid copy/paste for the other class version.

Give me an example of refactoring the above code.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
M.Hassan
  • 10,282
  • 5
  • 65
  • 84
  • how to pass the corresponding assembly that resolve the dynamics at runtime? – M.Hassan Jan 29 '18 at 19:30
  • 1
    Wrap them in your own Classes. Then you can decide how the Inheritance looks freely. When you use the MVVM pattern, you have to do a lot of wrapping like that. – Christopher Jan 29 '18 at 19:30
  • @Christopher, can you give me an example for the both assemblies. – M.Hassan Jan 29 '18 at 19:33
  • When you use the MVVM pattern, you need ChangeNotification on all the Properties of every ViewModel class. 95% of all Model classes do not have change notificaiton. Or even properties that you can override (mostly they have fields). Solution: You write your own classes. Their sole purpose is to hold a Model class in a private variable. All their Properties do nothing but wrap around the calls to the fields. All their functions jsut wrap around the model functions. End result: The Model classes can stay oblivious of realiy. You get the exact proeprties and classes you want. – Christopher Jan 29 '18 at 20:01

1 Answers1

5

One option is to use the adapter pattern, which is a common way to add abstractions to BCL and 3rd party code that doesn't utilize them. For example, you have a type in the 3rd party assembly named MyClass and both V1 and V2 share the same members:

public interface IMyClass
{
    // All members of MyClass 
    // (except we have a special case for DoSomething() because it
    // has a return type SomeType we also need to adapt to ISomeType).

    ISomeType DoSomething();
}

public class MyClassV1 : V1.MyClass, IMyClass
{
    // No need to re-implement members (base class satisfies interface)
    // However, if there are return parameters, you will need to 
    // also use a decorator pattern to wrap them in another adapter.

    public override ISomeType DoSomething()
    {
        return new SomeTypeV1(base.DoSomething());
    }

}

public class MyClassV2 : V2.MyClass, IMyClass
{
}

public interface ISomeType
{
     // All members of SomeType
}

public class SomeTypeV1 : ISomeType
{
    private readonly SomeType someType;

    public SomeType(SomeType someType)
    {
        this.someType = someType;
    }

    // re-implement all members and cascade the call to someType
}

And then you can just use IMyClass in your application, using DI to inject whichever one you need.

public class HomeController : Controller
{
    private readonly IMyClass myClass;

    public HomeController(IMyClass myClass)
    {
        this.myClass = myClass
    }
}

If you need to switch between implementations at runtime, consider the strategy pattern.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Some methods in the classes return/have parameters of DataType located in the assembly. Do I have to build a separate adapter for these types? – M.Hassan Jan 29 '18 at 20:34
  • In that case you will need a decorator pattern to wrap the returned instance from the API. See my updated answer. There may be other special cases as well that need wrapping in a decorator depending on how many other types from the library the API exposes. – NightOwl888 Jan 29 '18 at 21:04