0

Let's talk about three projects. I have a Cinema project, a Cinema.Engine project, and a Cinema.Client1 project.

In the Cinema project, I have an interface ICinema and a factory class CinemaFactory.

In the Cinema.Engine project, I have a class that implements ICinema... we'll call it CinemaEngine : ICinema. This project is exposed as a WCF service referencing the Cinema project.

This allows my Cinema.Client1 project to only reference the Cinema project. The Cinema.Client1 can call the CinemaFactory and obtain a reference to the ICinema provided by the WCF service. All is well and good....... Now to get icky.

Let's add a fourth project called Cinema.Client2 which has references to both the Cinema and Cinema.Engine projects. Because I have a reference to Cinema.Engine, I want to be able to call my factory with a different set of parameters and have the factory instantiate the engine locally instead of calling the WCF service.

Important note: the Cinema project does not have any references to any other projects.

So, if I only have a reference to the Cinema project, then the factory should look like this:

public class CinemaFactory {
    public ICinema GetCinema (Uri RemoteCinema) {}
}

But if I have a reference to Cinema.Engine then the factory should look like this:

public class CinameFactory {
    public ICinema GetCinema (string CinemaParameters) {}
}

To put it another way: The client (residing in one project) needs to obtain an instance of the engine (residing in a second project). That instance will either be a proxy to a WCF service or it will be instantiated locally. This instance will be obtained from a factory (residing in a third project). The first project (containing the client) and the third project (containing the engine) both reference the second project (containing the factory).

If the client is obtaining the proxy, it shouldn't need a reference to the second project. If the client has a reference to the second project, only then should the factory provide the option of instantiating the engine locally.

How can I get the factory (in the third project) to instantiate the engine locally (from the second project) without having a reference to the second project (which causes a circular reference)?

Things I've looked into but that don't work:

  • Partial classes don't work. Partial classes are syntactic sugar and cannot span projects.
  • Same class, different namespace (for the factory): but how would I know which namespace to pull the class from?
  • new keyword: only applies to members, not to entire classes.
  • 2
    Can you be more specific about what isn't working here? – Robert Harvey Aug 5 '16 at 16:10
  • Ok, your factory needs two entry points, one with an Uri and one with a string as a parameter. Where is the problem? – Doc Brown Aug 5 '16 at 16:13
  • If the problem boils down to having two different signatures, why don't you use the string signature, pass a string in the first example, and convert it to a Uri when it arrives? Uri has a constructor overload that accepts a string. Your other alternative is to have two method signatures in your Interface. – Robert Harvey Aug 5 '16 at 16:13
  • The problem is that if I add and implement the second entry point to Cinema.CinemaFactory, then I must also have a reference to the Cinema.Engine project. Not only does this cause a circular reference, but it means deploying the Cinema.Engine.dll file along with my client, which I'm trying to avoid. – J Fenter Aug 5 '16 at 16:14
  • 1
    Why does adding the second entry point require a new reference to Cinema.Engine in the client? Can't you just alter the existing ICinema interface, or add a new interface in the same location? – Robert Harvey Aug 5 '16 at 16:23
3

This is exactly what extension methods are for. I can define the factory in the Cinema project with just the one method signature. I can define an extension method in Cinema.Engine.

Sample code below, showing how to accomplish this using extension methods. The four namespaces are meant to reside in four different projects.

namespace Cinema {

    // Required for WCF
    public interface ICinema {
    }

    public sealed class CinemaFactory {
        // Singleton pattern to support extension methods. (Can't add extension methods to static classes.)
        private static readonly Lazy<CinemaFactory> LazyInstance = new Lazy<CinemaFactory> (() => new CinemaFactory());

        public static CinemaFactory Instance {
        get {
                return LazyInstance.Value;
            }
        }

        private CinemaFactory () {
        }

        public ICinema  GetCinema (EndpointAddress10 RemoteCinemaUri) {
            ICinema CinemaEngineInstance;

            // Get a reference from the WCF service...

            return CinemaEngineInstance;
        }
    }
}

namespace Cinema.Engine {
    using Cinema;

    public class CinemaEngine : ICinema {
    }

    // Add polymorphism to the class defined in a different project.
    public static class CinemaFactoryHelper {
        public static ICinema GetCinema (this CinemaFactory _This) {
            return new CinemaEngine ();
        }
    }
}

namespace Cinema.Client1 {
    using Cinema;

    public class Consumer1 {
        // Intellisense shows that there are no overloads to the GetCinema() method. Yay!
        private ICinema ref = CinemaFactory.Instance.GetCinema (EndpointAddress10.FromEndpointAddress (new EndpointAddress ("http://localhost"));
    }
}

namespace Cinema.Client2 {
    using Cinema;
    using Cinema.Engine; // Add visibility to the extension method that provides a local instance of the engine.

    public class Consumer2 {
        // Intellisense shows the method +1 overload. Yay!
        private ICinema ref = CinemaFactory.Instance.GetCinema ();
    }
}
  • So your ICinema interface is now empty? O_o – Robert Harvey Aug 5 '16 at 21:08
  • :P The contents of the ICinema interface are not relevant to the functionality of the factory which was the original subject of the question. – J Fenter Aug 5 '16 at 21:10
  • The CinemaEngine is exposed as a WCF service. You can't create a WCF service without an interface. It's precisely the interface that gets exposed. Thus, I need the interface. – J Fenter Aug 5 '16 at 21:12
  • Well, +1 for creativity. I love extension methods. – Robert Harvey Aug 5 '16 at 21:13
  • Let's look at it this way: The Cinema project defines what the client is working with and how to gain access to it. The Cinema.Engine project handles all of the implementation. The two client projects simply consume it. And since the implementation might be hidden behind a WCF service, the client might not need a reference to the implementation. – J Fenter Aug 5 '16 at 21:14
1

So, you have something like this:

public interface IWidget {}

public class RemoteWidget : IWidget {}
public class LocalWidget : IWidget {}

public class WidgetFactory
{
  public IWidget Create(Configuration configuration)
  {
      return new LocalWidget(configuration);
  }

  public IWidget Create(Uri origin)
  {
      return new RemoteWidget(origin);
  }
}

And, for some reason, you now want to distribute WidgetFactory separately from LocalWidget. How do you create a WidgetFactory that doesn't know at compile-time which IWdidget implementations are available or where to find them?

You've got a few options. For example:

  • Scan an extensions, modules, or plugins directory for DLL's and load them dynamically.
  • Use a DI framework that can do some of that dynamic library loading for you.
  • Use the Managed Extensibility Framework.
  • Extension methods ... maybe. I just get the sense this might work; I can't think of how offhand.
  • Let the client application configure WidgetFactory or register the available Widget implementations. This is probably the path of least assumption: The client can choose to use DI, MEF, extensions methods, direct reflection, etc. You're not imposing much in terms of architecture this way.
  • Similarly, make your WidgetFactory.Create accept a Type parameter (potentially as a Generic). In your case, your client seems to need to know which type it needs built anyway. So, this may be acceptable ...

OR, just change your design: Don't treat the "Widgets" project as your distributable; include it in your distributable. In that case, you wouldn't put a WidgetFactory in the "Widgets" project, you'd put an IWidgetFactory in.

Then, either ask the client to provide the concrete factory, or create a few distro's based on your pricing model (I'm assuming that's what's driving the need for separation). E.g., FreeDistro, StandardDistro, EnterpriseDistro ... Each of those distros would just include hard references to the concrete types (and factories) available at that "support level."

Lots of options... Not all mutually exclusive options either!

  • While I appreciate your answer and the effort you put into it, I had an epiphany at lunch and beat you to the answer by about a half hour. I pasted code on PasteBin that hopefully explains how to do it with extension methods. The four namespaces are meant to reside in four different projects. – J Fenter Aug 5 '16 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.