Reputation: 11032
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
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