tags:

views:

212

answers:

5

I'm fairly new to all this, so this is probably OOP 101 but I can't get my head around it, assume the following C# code lives in an assembly:

internal interface IDataStore
{
    void Store(string name, object data);
    object Retrieve(string name);
}

internal class DBStore : IDataStore
{
    public DBStore(string connection) { }
    public void Store(string name, object data) { }
    public object Retrieve(string name) { }
}

public class GizmoManager
{
    public GizmoManager(IDataStore dataStore) { }
    // Other stuff
}

public class WidgetManager
{
    public WidgetManager(IDataStore dataStore) { }
    // Other stuff
}

If a second assembly tries to create a GizmoManager and a WidgetManager, it can't because it has no way to get hold of a DBStore (since it is internal not public).

The following don't work AFAICS:

  • Make DBStore and IDataStore public. Bad because the client assembly can then bypass GizmoManager/WidgetManager and access the DB however it likes.
  • Don't pass in an IDataStore to the GizmoManager and WidgetManager constructors. Bad because it reduces testability (you can't easily pass in a MockDataStore).
  • Do something magic with factories. Doesn't seem to solve anything because you need to pass the same IDataStore to both GizmoManager and WidgetManager and thus the client assembly still needs to assign an IDataStore to a variable (which it can't because IDataStore is internal).

This is probably stunningly obvious but I just can't see it. How would one overcome this contradiction?

TIA.

+1  A: 

Make IDataStore and DBStore public but make the DBStore constructor internal. Then assemebly 1 has complete control over when DBStores gets instantiated, but assembly 2 is able to access and pass in the DBStore.

I imagine you will then want a factory in assembly 1 that limits the number or configuration of the DBStore instances that get created.

Paul Ruane
+2  A: 

Just make the interface IDataStore public. That way, clients can instantiate your WidgetManager and GizmoManager classes with mock objects they create themselves.

Implement on of the Factory class of design patterns to allow creation of GiszmoManager and WidgetManager objects by client code. That way, client code can never create a DBStore object, so they can never circumvent your managers.

BTW: Why bother fussing with encapsulation? This might make sense if you are in the proprietary frameworks business, but otherwise, YAGNI.

Daren Thomas
A: 

You can make the interface and classes public, but make the methods to access the database directly internal. This way, you instantiate an instance of your DBStore, without allowing any access on methods that other assemblies are not allowed to.

internal methods are only visible to classes in the same assembly.

I would rather check if there is no way to assemble your objects without the need of instantiating a DBStore every time. Perhaps a factory pattern or dependency injection? Then you can make your interface public, and let a factory inside the first assembly instantiate them. This does not impact testability, but does reduce the object instances you create. Less new XYZ() statements is better...

Kamiel Wanrooij
A: 

Make DBStore public, butmake all members that you don't want to expose private or internal. Then... Option 1: Remove all members from IDataStore that you don't want to be accessible outside of the assembly, and make IDataStore public. Option 2: Rename IDataStore to IDataStoreInternal, and make it inherit from a new public IDataStore interface that has no members (or only members that you want to expose externally).

BlueMonkMN
A: 

I like both of these:

  • Don't pass in an IDataStore to the GizmoManager and WidgetManager constructors. Bad because it reduces testability (you can't easily pass in a MockDataStore).
  • Do something magic with factories. Doesn't seem to solve anything because you need to pass the same IDataStore to both GizmoManager and WidgetManager and thus the client assembly still needs to assign an IDataStore to a variable (which it can't because IDataStore is internal).

To solve testability but hide implementation, the public interface should only offer the option of whether the stores should be live, testable, pre-prod etc. So you could delegate responsibility for correct IDataStore construction to another party that is inside the assembly. E.g. a factory concept that only allows you to set "testing", "deploy", "debug", etc. Have default constructors (or singletons or whatever) that refer internally to the factory for the correct storage given the "mode" setting.

Richard Watson