.NET Forum / Languages / C# / November 2006
Separating implementation and interface: HOW?
|
|
Thread rating:  |
Luc Kumps - 19 Nov 2006 15:13 GMT (Sorry about the previous post, it got transmitted before it was complete)
We try to separate implementation and interface defintions, but we run into a problem. I hope the guru's can solve this, as we seem to lack only a single 'step' to have "full separation"...
We have a first project, namespace Ninterface, that contains the interface definitions in class1_interface.cs, like this: namespace Ninterface { public interface IClass1{ void method1(); } }
In a second project, namespace Nimplementation, we have an implementation of class1 in class1.cs: namespace Nimplementation { public class Class1: IClass1 { void method1() { /** implementation of Class1.method1 **/ } }
In any project using Class1, we include the first project in the references and we add a "Using Ninterface". All our instance references use "IClass1", as "Class1" is completely invisible here.
But here's the problem: how do we create a new instance of a Class1 object, without referring to the implementation in Nimplementation? We don't want do refer to Nimplementation, only to Ninterface. So we can't use "new Class1();". Moreover, we want to be able to have multiple parallel implementations of Class1, being able to construct a specific one at runtime.
Of course, we could make a third project that contains class factories able to generate instances of the different implementations. But then this project would need to have the 'implementation' projects in the references and we want to exclude any 'reference' between 'class user' and 'class implementation'.
We would like to have all the implementations 'register' their factory in a 'central 'factory'. In C++, this is easy to accomplish: define static instances of a factory in each file, and in the instance constructor call the central static "RegisterClass" method. But C# doesn't initialize a static field of a class (and therefore doesn't call the constructor) unless you USE the class for the first time. But that's exactly what we're trying to prevent: "using" the implementation directly!
Is there any way to execute code within a number of classes "automatically" when the program starts up, without needing to USE all these classes? Just like the constructors of all static objects in C++ are being called (in random order, but we don't care about order) upon program startup? Or should we stop dreaming because this is impossible?
Thanks in advance,
Luc Kumps
Andreas Mueller - 19 Nov 2006 15:40 GMT > (Sorry about the previous post, it got transmitted before it was complete) > [quoted text clipped - 53 lines] > > Luc Kumps Hi Luc,
you can implement some kind of PlugIn Framework yourself or use an Dependency Injection Container. Here are some links:
Spring: http://www.springframework.net
Here is a good example how to use it: http://www.springframework.net/doc/reference/html/quickstarts.html
Castle/Windsor: http://www.castleproject.org/container/index.html
Another one is pico/nano container: http://www.picocontainer.org/
If you want to implement it yourself, you basically have to load your assemblies dynamically and call a static entry point method or instantiate an entry point object via reflection. In your entry point you register your class factory as you have described it in your post.
HTH, Andy
 Signature You can email me directly by removing the NOSPAm below xmenNOSPAm40@gmxNOSPAm.netNOSPAm
Luc Kumps - 19 Nov 2006 17:11 GMT > If you want to implement it yourself, you basically have to load your > assemblies dynamically and call a static entry point method or > instantiate an entry point object via reflection. In your entry point > you register your class factory as you have described it in your post. Thanks Andy! I'll simply use a number of IClassRegistration cr = (IClassRegistration)Activator.CreateInstance(Type.GetType("Namespace.Object, Assembly"))
calls and call a static entry point method (defined in IClassRegistration) in the resulting instance. We only have a handfull of classes we want to create a factory for, so this will work fine and I won't need a reference from the calling code!
I was just hoping that it was possible to call a static entry point method in a number of classes automagically upon program startup (just like static C++ objects in different files). But it's not too hard to do it this way!
Thanks!
Luc K
William Stacey [C# MVP] - 19 Nov 2006 17:21 GMT | I'll simply use a number of | IClassRegistration cr = (IClassRegistration)Activator.CreateInstance(Type.GetType("Namespace.Object,
| Assembly")) That seems like an easy way to do it :-) Using this method, does "Namespace.Object" need to be unique for each one? Check that. The assembly name makes them unique even with same namespace - right?
 Signature William Stacey [C# MVP]
Luc Kumps - 19 Nov 2006 21:03 GMT >> I'll simply use a number of >> IClassRegistration cr = (IClassRegistration)Activator.CreateInstance(Type.GetType("Namespace.Object,
>> Assembly")) > > That seems like an easy way to do it :-) Using this method, does > "Namespace.Object" need to be unique for each one? Check that. The > assembly name makes them unique even with same namespace - right? Now there's an idea, William! I simply use the same class name in each namespace to 'register' the factory (or factories) and I use Reflection to find all the namespaces that have this class! Here's the code for the central 'factory repository', the main program should only call FactoryDepository.Init(): //******************************************* public interface IFactory { object getInstance(Dictionary<string, string> settings); }
public interface IFactoryRegistry { void registerFactory(string name, IFactory factory); }
public class FactoryDepository: IFactoryRegistry { static Dictionary<string, IFactory> _allFactories = new Dictionary<string, IFactory>();
static public void Init() { FactoryDepository f = new FactoryDepository(); // Get a list of all our factories System.Reflection.Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies(); foreach (System.Reflection.Assembly a in assems) { string[] parts = a.FullName.Split(new char[] { ',' }); if (parts.Length > 0) { Type tFact=null; try { tFact = Type.GetType(parts[0] + ".NamespaceFactory, " + parts[0]); } catch (Exception) { } if (tFact != null) { Activator.CreateInstance(tFact, new object[] { f }); } } } } public void registerFactory(string name, IFactory factory) { _allFactories[name] = factory; } }
//*******************************************
And here's the part in an "implementation namespace":
//*******************************************
namespace Nimplementation { public class NamespaceFactory : IFactory { public object getInstance(Dictionary<string, string> settings) { return new BlaBla(); //This is were we need to instantiate the correct implementation, based upon the "settings" } public NamespaceFactory(IFactoryRegistry registry) { registry.registerFactory("interfacename", this); } }
} //*******************************************
Whenever a factory is needed , you simply use _allFactories["interfacename].getInstance(...).
Luc K
Dave Sexton - 19 Nov 2006 21:30 GMT Hi Luc,
Are you expecting to register any assemblies through configuration after deployment or will third-parties be able to register assemblies through configuration after deployment?
 Signature Dave Sexton
>>> I'll simply use a number of >>> IClassRegistration cr = [quoted text clipped - 87 lines] > > Luc K Luc Kumps - 19 Nov 2006 22:07 GMT > Are you expecting to register any assemblies through configuration > after deployment or will > third-parties be able to register assemblies through configuration > after deployment? Neither! This is a development issue only. We simply want to separate implementation and interface definitions as good as possible... Our Solution has several (implementation) Projects and we want the Projects to refer *only* to the interfaces definitions (which we keep in a separate file for each namespace, but all interface definitions reside in a single namespace) and *never* to each other (i.e. to the actual implementations). This keeps the projects (and the developers) well 'separated' and prevents any problems with 'circular references'...
Of course, there are some drawbacks to this approach as well. E.g. an interface can't define static methods or accessors...
Luc K
Dave Sexton - 19 Nov 2006 22:17 GMT Hi Luc,
> Neither! > This is a development issue only. > We simply want to separate implementation and interface definitions as good > as possible... That's what I thought at first, but then I started doubting myself when you wouldn't except my very simple solution.
If you're sure that a developmental-plugin approach is what you need then by all means just ignore me :)
 Signature Dave Sexton
>> Are you expecting to register any assemblies through configuration >> after deployment or will [quoted text clipped - 16 lines] > > Luc K Dave Sexton - 19 Nov 2006 22:26 GMT Hi Luc,
Correction: accept, not except :)
 Signature Dave Sexton
> Hi Luc, > [quoted text clipped - 29 lines] >> >> Luc K Luc Kumps - 20 Nov 2006 07:26 GMT > Hi Luc, > [quoted text clipped - 6 lines] > when you wouldn't except my very > simple solution. Your simple solution was too simple for me to understand the first time around :-)
Here was our actual problem (simplified): 1. We had a "Main" project and projects B and C 2. Our "Main" project contained quite some code, including constructing an instance of a class implemented in B 3. The implementation of project B needed constructing an instance of a class implemented in C
We now understand that your simple solution means that "Main" in C# should *only*: 1. Create all class factories the other Projects will ever need. In the above example, a (interface!) reference to the factories for classes C and B would need to be constructed in the "Main" project. Whenever a project needs a factory for a class in another project, code needs to be added to the "Main" project to create an interface reference to this factory. 2. All the non-factory-constructing code of "Main" should be moved to a separate project A. 3. After instantiating the class factories needed by the other projects, "Main" should start Project A, containing the actual "Main" code.
Given our C, C++ and Delphi background, this approach was new: we weren't used to the need to separate [the construction of the class factory] and [the actual use of the factory] in separate projects (in the above example this means modifying the "Main" implementation every time we need a class factory in some other class implementation in another project). But we can certainly live with that minor quirk, if we get full interface/implementation separation in return!
Thanks again for your patience!
Luc K
Dave Sexton - 20 Nov 2006 14:37 GMT Hi Luc,
> Here was our actual problem (simplified): > 1. We had a "Main" project and projects B and C > 2. Our "Main" project contained quite some code, including constructing an > instance of a class implemented in B > 3. The implementation of project B needed constructing an instance of a > class implemented in C I thought you had a circular dependency problem, whereby classes in project B reference classes in project C and classes in project C also reference classes in project B. This circular dependency doesn't work using project references in Visual Studio (as I'm sure you're aware :). If you define interfaces in a third assembly, "Project I", then you won't need to have any circular project dependencies. All classes in project B that reference classes in project C should have interfaces for project C classes defined in project I, and all classes in project C that have references to classes in project B should have interfaces for project B classes defined in project I. Class factories for classes in project B and project C should be located in their own project (A, to use the name you've given already).
Architecting a solution that doesn't require the use of reflection to construct known classes and relationships, as described above, seems like it will be a much better approach. The plug-in architecture is a complex solution (but if you do see value in it then it might be what you need :).
If it's not clear to you why I'm trying to avoid the plug-in approach, it's because you claim this to be a development issue where you know what all of the interface implementations and their relationships will be at design-time, and at runtime they will not change. Therefore, there is absolutely no need for reflection. Also, the plug-in approach requires assembly registration, which may be a security risk in your solution since it allows third-party developers to plug-in any code they want that will run with the privileges of your application. To secure against this you must sign each of your assemblies using the same strong-name and add the StrongNameIdentityPermissionAttribute to each as well. Then make sure your private strong-name key is stored in a very safe place. This approach doesn't prevent tampering, however. Anybody can remove registered assemblies from the configuration causing your application to break without a trace.
If you need to chain the construction of objects, as in your example, while accounting for circular dependencies and you'd like to use class factories to do so then here is a solution that might work for you. Let me know if this is still too primitive for your needs (the names here relate to the project names used in our examples):
{Project I: Interfaces}
interface IB { } interface IC { }
{Project B; References: I}
public class B1 : IB { public B1(IC c) { Console.WriteLine("B1 constructed with: " + c.GetType().ToString()); } }
public class B2 : IB { public B2(IC c) { Console.WriteLine("B2 constructed with: " + c.GetType().ToString()); } }
{Project C; References: I}
public class C1 : IC { } public class C2 : IC { }
{Project A: Factories; References: I, B, C}
// static classes can be used in C# 2.0 only public static class BFactory { public static IB Create(SomeState state) { // Based on SomeState choose the appropriate implementation of IB // and the appropriate implementation for IC. // Here, I'm simply checking for null return (state == null) ? (IB) new B1(CFactory.Create(state)) : new B2(CFactory.Create(state)); } }
// static classes can be used in C# 2.0 only public static class CFactory { public static IC Create(SomeState state) { // Based on SomeState choose the appropriate implementation of IC. // Here, I'm simply checking for null. return (state == null) ? (IC) new C1() : new C2(); } }
{Project Main; References: I, A}
static void Main(string[] args) { IB b = A.BFactory.Create(null); IC c = A.CFactory.Create(null);
Console.WriteLine("BFactory created: " + b.GetType().ToString()); Console.WriteLine("CFactory created: " + c.GetType().ToString());
Console.ReadLine(); }
Don't forget that you'll have to add "using" directives as well, which I removed here for the sake of simplicity. I created some skeleton projects in VS 2005 with the appropriate references and added all of the above code. Here was my output:
B1 constructed with: C.C1 BFactory created: B.B1 CFactory created: C.C1
The key here is that the BFactory knew to call into the CFactory, and that each factory knows how to construct the appropriate type based on some state supplied by the caller (the usual purpose of the class factory pattern). The factories are hard-coded here, but if you needed more flexibility then you could use abstract factories instead of static ones. No reflection required, however.
I admit that your needs are still not perfectly clear to me so please don't assume that I know this to be the best solution for you. This solution might not be enough, or might not work exactly to meet your requirements, but I feel that it's worth checking out before jumping into reflection, losing type-safety at compile-time and introducing potential security risks.
 Signature Dave Sexton
>> Hi Luc, >> [quoted text clipped - 40 lines] > > Luc K Luc Kumps - 20 Nov 2006 18:21 GMT "Dave Sexton" <dave@jwa[remove.this]online.com> wrote in message news:%> {Project Main; References: I, A}
> static void Main(string[] args) > { > IB b = A.BFactory.Create(null); > IC c = A.CFactory.Create(null); Yes, this is the solution we ended up implementing: creating (in Main) the interface references for all the factories that will ever be needed by one of the projects. Initially, I was looking for a way to create "b" from Project B and "c" from project C automatically at startup of the program: I was trying to make each implementation resposible for creating a reference to its factory... But if that isn't possible in C#, then the above work-around will do just fine!
Thanks for your assistance!
Luc K
Luc Kumps - 20 Nov 2006 18:21 GMT "Dave Sexton" <dave@jwa[remove.this]online.com> wrote in message news:%> {Project Main; References: I, A}
> static void Main(string[] args) > { > IB b = A.BFactory.Create(null); > IC c = A.CFactory.Create(null); Yes, this is the solution we ended up implementing: creating (in Main) the interface references for all the factories that will ever be needed by one of the projects. Initially, I was looking for a way to create "b" from Project B and "c" from project C automatically at startup of the program: I was trying to make each implementation resposible for creating a reference to its factory... But if that isn't possible in C#, then the above work-around will do just fine!
Thanks for your assistance!
Luc K
Dave Sexton - 19 Nov 2006 17:28 GMT Hi Luc,
If you know all the concrete implementations there will be at design-time, then just use them directly. Create a factory object and call a method named CreateIClass1, for example, supplying any variable data required for the class factory to choose the appropriate implementation of IClass1 to be constructed and returned.
Class registration and a plug-in framework may be major overkill here. I don't see any value in using such a convoluted approach over coding a simple factory that can instantiate and return any one of the predefined types in your application.
 Signature Dave Sexton
>> If you want to implement it yourself, you basically have to load your >> assemblies dynamically and call a static entry point method or [quoted text clipped - 19 lines] > > Luc K Luc Kumps - 19 Nov 2006 19:14 GMT > Class registration and a plug-in framework may be major overkill > here. I don't see any value in > using such a convoluted approach over coding a simple factory that > can instantiate and return any > one of the predefined types in your application. I think the simple "Activator.createInstance" approach in combination with a simple automatic factory registrator is a good compromise. We like keeping interfaces and implementations as separated as possible. This makes the structure clear, forces one to think about the interactions between the different parts of the application and it prevents running into the "circular reference" problems, as classes are never referring to each others implementations, only to each others interfaces!
Luc K
Dave Sexton - 19 Nov 2006 19:24 GMT Hi Luc,
Well I'm not really sure what you mean, but I wasn't suggesting to return a concrete implementation from the factory method. I was suggesting that you return your IClass1 interface. This preserves separation while using a very simple design.
 Signature Dave Sexton
>> Class registration and a plug-in framework may be major overkill >> here. I don't see any value in [quoted text clipped - 11 lines] > > Luc K Luc Kumps - 19 Nov 2006 20:30 GMT To return the IClass1 interface, I need to be able to instantiate a Class1 implementation. To be able to do that, I need to add a "Reference" to the Class1 implementation. And I don't want ANY 'reference' from the "user" part of IClass1 to the "implementation" part...
Luc K
> Hi Luc, > [quoted text clipped - 21 lines] >> >> Luc K Dave Sexton - 19 Nov 2006 20:37 GMT Hi Luc,
I'm not sure why adding a project reference is such a bad thing, but if you really want to avoid it you can create a shim project that contains the factory. The project with the factory will reference both the project containing the interface and the project containing the implementations. The interface project won't have to reference anything. The implementations project would reference the factory project.
Very strange, I must say.
 Signature Dave Sexton
> To return the IClass1 interface, I need to be able to instantiate a Class1 > implementation. To be able to do that, I need to add a "Reference" to the [quoted text clipped - 28 lines] >>> >>> Luc K Dave Sexton - 19 Nov 2006 20:46 GMT Hi Luc,
Actually, I messed up the references but the original concept still works (I assumed that your implementations project was the main assembly, but I think that's incorrect based on what you've said)
- Factory references both implementations and interface projects - Interface project requires no references - Implementations project requires no references - Main Assembly references interface and factory projects
If you know what all of the classes will be ahead of time then I don't see any value in this approach.
 Signature Dave Sexton
> Hi Luc, > [quoted text clipped - 38 lines] >>>> >>>> Luc K Luc Kumps - 19 Nov 2006 22:04 GMT > - Factory references both implementations and interface projects > - Interface project requires no references > - Implementations project requires no references > - Main Assembly references interface and factory projects The implementation projects need to interface to each other now and then, and we don't want to have ANY references between implementation projects. So projects should refer to each other through interfaces.
Luc K
Jeff Louie - 20 Nov 2006 06:11 GMT Luc.. It sound like you are coming from C++. To be clear, in C# if you have two objects with circular references to each other, and both objects as a _set_ become unreachable, the two objects become eligible for garbage collection.
If you want to create concrete objects from a fixed set of related classes you can use a static class factory. If you want to load related classes discovered at runtime you can use a type based class factory or an interface based class factory. Both of these approaches create an instance of the concrete class but return a reference of the related type.
http://www.geocities.com/jeff_louie/OOP/oop13.htm http://www.geocities.com/jeff_louie/OOP/oop18.htm
Regards, Jeff
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|