4

I have to be able to connect to two different versions of the an API (1.4 and 1.5), lets call it the Foo API. And my code that connects to the API and processes the results is substantially duplicated - the only difference is the data types returned from the two APIs. How can I refactor this to remove duplication?

In Foo14Connector.cs (my own class that calls the 1.4 API)

public class Foo14Connector
{
    public void GetAllCustomers() 
    {
        var _foo = new Foo14WebReference.FooService();
        Foo14WebReference.customerEntity[] customers = _foo.getCustomerList;
        foreach (Foo14WebReference.customerEntity customer in customers)
        {
            GetSingleCustomer(customer);
        }
    }
    public void GetSingleCustomer(Foo14WebReference.customerEntity customer)
    {
        var id = customer.foo_id;
        // etc 
    }
}

And in the almost exact duplicate class Foo15Connector.cs (my own class that calls the 1.5 API)

public class Foo15Connector
{
    public void GetAllCustomers() 
    {
        var _foo = new Foo15WebReference.FooService();
        Foo15WebReference.customerEntity[] customers = _foo.getCustomerList;
        foreach (Foo15WebReference.customerEntity customer in customers)
        {
            GetSingleCustomer(customer);
        }
    }
    public void GetSingleCustomer(Foo15WebReference.customerEntity customer)
    {
        var id = customer.foo_id;
        // etc 
    }
}

Note that I have to have two different connectors because one single method call (out of hundreds) on the API has a new parameter in 1.5.

Both classes Foo14WebReference.customerEntity and Foo15WebReference.customerEntity have identical properties.

JK.
  • 21,477
  • 35
  • 135
  • 214
  • 1
    Do `Foo14WebReference.customerEntity` and `Foo15WebReference.customerEntity` share a common interface or base type? – M.Babcock Jan 05 '12 at 03:21
  • 1
    Are both connectors in the same project? – competent_tech Jan 05 '12 at 03:21
  • @M.Babcock no - no common type. Both are created by importing the web reference to the appropriate API. And no they are in two different projects, the Foo14 and Foo15 project. It's not possible to add a web reference to version 1.4 and version 1.5 to the same project (the web references both have the same name) – JK. Jan 05 '12 at 03:25
  • @Sneal I dont know. The API web references return a specific type – JK. Jan 05 '12 at 03:27

3 Answers3

5

If the connectors are in different projects, this is an easy situation to solve:

Add a new class file, call it ConnectorCommon and copy all of the common code, but with the namespaces removed. Make this class a partial class and rename the class (not the file) to something like Connector.

You will need to add a link to this to each project.

Next, remove all of the code from your current connector classes, rename the class (not necessarily the file) to the same as the partial class, and add a using statement that references the namespace.

This should get what you are looking for.

So, when you are done you will have:

File ConnectorCommon:

public partial class Connector
{
    public void GetAllCustomers() 
    {
        var _foo = new FooService();
        customerEntity[] customers = _foo.getCustomerList;
        foreach (customerEntity customer in customers)
        {
            GetSingleCustomer(customer);
        }
    }
    public void GetSingleCustomer(customerEntity customer)
    {
        var id = customer.foo_id;
        // etc 
    }
}

File Magento15Connector

using Foo15WebReference;

partial class Connector
{
}

File Magento14Connector

using Foo14WebReference;

partial class Connector
{
}

Update

This process can be a little confusing at first.

To clarify, you are sharing source code in a common file between two projects.

The actual classes are the specific classes with the namespaces in each project. You use the partial keyword to cause the common file to be combined with the actual project file (i.e. Magneto14) in each project to create the full class within that project at compile time.

The trickiest part is adding the common file to both projects.

To do this, select the Add Existing Item... menu in the second project, navigate to the common file and click the right-arrow next to the Add button.

From the dropdown menu, select Add as link. This will add a reference to the file to the second project. The source code will be included in both projects and any changes to the common file will be automatically available in both projects.

Update 2

I sometimes forget how easy VB makes tasks like this, since that is my ordinary programming environment.

In order to make this work in C#, there is one more trick that has to be employed: Conditional compilation symbols. It makes the start of the common code a little more verbose than I would like, but it still ensures that you can work with a single set of common code.

To employ this trick, add a conditional compilation symbol to each project (ensure that it is set for All Configurations). For example, in the Magento14 project, add Ver14 and in the Magento15 project add Ver15.

Then in the common file, replace the namespace with a structure similar to the following:

#if Ver14
using Magneto14;
namespace Magento14Project

#elif Ver15
using Magneto15;
namespace Magento15Project

#endif

This will ensure that the proper namespace and usings are included based on the project the common code is being compiled into.

Note that all common using statements should be retained in the common file (i.e., enough to get it to compile).

competent_tech
  • 44,465
  • 11
  • 90
  • 113
  • You can do partial classes across project boundaries? Thats nice.. But what does ConnectorCommon reference so that it knows what the customerEntity type is? If it references the 1.4 API then the 15 connector wont work and vice versa. – JK. Jan 05 '12 at 03:41
  • re partial classes: No, you are creating separate classes in each project. The trick is to share the code in ConnectorCommon between these classes and then have the actual class within each project. Because it is a partial class, connectorcommon will pick up the using statements from the actual classes and compile correctly within each project. – competent_tech Jan 05 '12 at 03:56
  • Ok, good luck. We've been doing this for years around this issue and a nasty issue with class reuse in web service clients, so I am confident in the integrity of the approach :). – competent_tech Jan 05 '12 at 04:02
  • I've tried to implement this but I don't quite follow how you did it. I thought you were saying that ConnectorCommon is in a 3rd project, but now that I re-read the answer, I can see that said anywhere. Why do you say to remove namespaces from the common class? What do I do with namespaces in the 1.4 and 1.5 classes? How do you reference the common from the specific class when common has no namespace? Are you sure that it is possible to have partial classes across projects? Jon Skeet says its not possible: http://stackoverflow.com/a/309944 – JK. Jan 07 '12 at 06:46
  • Updated answer with clarification. – competent_tech Jan 07 '12 at 08:26
  • Ah tricky + nice. But something is still not right. The two existing projects both have their own (different) namespaces. I just tried adding Connector and ConnectorCommon (as a link) to my 1.4 project. It only works if ConnectorCommon has the same namespace as Connector – JK. Jan 07 '12 at 09:33
  • Also the using statements in Connector do not seem to flow through to CommonConnector. I removed the namespace from Connector so that I could have both together. But even simple usings in Connector, like `using System` do not carry into ConnectorCommon - it is complaining about Exception not being defined for example. The web reference using is also not carried to Common. – JK. Jan 07 '12 at 09:59
  • Sorry, I forgot that C# can be a little more painful that VB for doing something like this. Nevertheless, there is one more trick that will solve the problem. I have updated the answer with this info. – competent_tech Jan 07 '12 at 19:35
  • Great thanks, I got that to work with a simple example. Will try to transfer all of the connector code to this way now. – JK. Jan 09 '12 at 00:44
1

If the FooConnectors are not sealed and you are in control to create new instances, then you can derive your own connectors and implement interfaces at the same time. In c# you can implement members by simply inheriting them from a base class!

public IFooConnector {
    void GetAllCustomers();
}

public MyFoo14Connector : Foo14Connector, IFooConnector
{
    // No need to put any code in here!
}

and then

IFooConnector connector = new MyFoo14Connector();
connector.GetAllCustomers();
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
0

You should introduce an interface that is common to both of the implementations. If the projects are written in the same language and are in different projects, you can introduce a common project that both projects reference. You are then making a move towards having dependencies only on your interface which should allow you to swap in different implementations behind the scenes somewhere using inversion of control (google, dependency injection or service locator or factory pattern).

Difficulties for you could be:

1) Public static methods in the implementations are not able to be exposed staticly via an interface 2) Potentially have code in one implementation class ie Foo14Connector or Foo15Connector that doesnt make sense to put into a generic interface

Dessus
  • 2,147
  • 1
  • 14
  • 24
  • Possibly if what I am suggesting is not possible due to not having control over both web reference projects, you could try static ducking. – Dessus Jan 05 '12 at 03:30