M.Hassan
M.Hassan

Reputation: 11032

Is there a suitable design pattern to refactor this C# code

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.

Upvotes: 1

Views: 442

Answers (1)

NightOwl888
NightOwl888

Reputation: 56849

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.

Upvotes: 5

Related Questions