views:

573

answers:

4

I am re-designing part of our internal ORM tool, and I want to expose Field (a class that represents a field in the database, like CustomerFirstName) directly to the end-developer.

So that was easy enough to accomplish, however the API got a little ugly because this Field class was previously used internally and is too open. For instance, and this is just one small example: the IsDirty property was not read-only, and that's something end-developers shouldn't be able to tamper with.

I thought about maybe creating two interfaces, IPublicField and IPrivateField, and trying to get the field class to implement both of them. However, continuing with the IsDirty example, I didn't want something like this:

Public ReadOnly Property PrivateIsDirty Implements IPrivateField.IsDirty
 ...
End  Property

Public Property IsDirty Implements IPublicField.IsDirty
 ...
End  Property

...It's just a little ugly, plus you could still cast back to the Field class and get into the non-readonly method. I also didn't want to introduce a seperate setter method because it would be another breaking change that I don't want to think about, and it also would create an inconsistency with other parts of the API.

I ended up renaming the Field class to InnerField, and creating a facade/wrapper style structure around it like this:

Public Class Field
    Implements BusinessObjects.IField

    Private InnerField As BusinessObjects.IInnerField

    Public Sub New(ByVal field As IInnerField)
        InnerField = field
    End Sub

    ...

     Public ReadOnly Property IsDirty() As Boolean Implements BusinessObjects.IField.IsDirty
        Get
            Return InnerField.IsDirty
        End Get
    End Property

    ...
End Class

This seems to be working out pretty well. Internally, the InnerField is suitably open and we have the freedom to make it more open in the future without impacting end-developers, and externally the Field class provides the simplified, locked down tool that end-developers need.

So, assuming that was coherant, I was wondering how you might have proceeded in this situation, and whether my solution seems reasonable from the outside.

Thanks!

+1  A: 

You could try using CAS to your advantage. Here is an example:

    public bool IsDirty
    {
        get;
        [StrongNameIdentityPermissionAttribute(SecurityAction.LinkDemand, Name = "<assembly name>")]
        set;
    }

The attribute is in System.Security.

You might want to change LinkDemand to Demand if you are worried that your developers might call it through reflection: but you could also 'replace' those developers instead of the SecurityAction :).

The only catch is that now you have the checks at runtime. This probably isn't what you want, but I can't see it working any other way without major restructuring efforts.

Jonathan C Dickinson
+1  A: 

If you don't object to using a get and set method instead of a property, trying having IPublicField implement IPrivateField.

Public Interface IPrivateField
    Function GetDirty() As Boolean
End Interface

Public Interface IPublicField
    Inherits IPrivateField

    Sub SetDirty(ByVal dirty As Boolean)
End Interface

Public Class Whatever
    Implements IPublicField

    Public Function GetDirty() As Boolean Implements IPrivateField.GetDirty
        ' logic here
    End Function

    Public Sub SetDirty(ByVal dirty As Boolean) Implements IPublicField.SetIsDirty
        ' logic here
    End Sub
End Class

Alternately, if you can ditch VB and switch to C#, this works just fine:

public interface IPrivateField
{
    bool IsDirty { get;}
}

public interface IPublicField : IPrivateField
{
    new bool IsDirty { get; set; }
}

public class Whatever : IPublicField
{
    public bool IsDirty 
    {
        get
        {
            // logic here
        }
        set
        {
            // logic here
        }
    }
}
Michael Meadows
+2  A: 

In the specific case of the isDirty property, you could just restrict the scope of the setters:

public bool IsDirty
     {
        get
        {
            // logic here that the end-devs can use safely.
        }
        private set
        {
            // logic here that is only exposed to other members of your private
            // ORM assembly.
        }
    }
GWLlosa
Wow, I have to admit that I have *never* seen that before, but it works great! That looks extremely useful.
Brian MacKay
+2  A: 

In the context of VB.NET, you can give the setter of the IsDirty property scope. If you want this property "set-able" by other classes in the assembly, just make it scoped as Friend:

Public Property IsDirty() As Boolean
    Get
        Return _isDirty
    End Get
    Friend Set(ByVal trueFalse As Boolean)
        _isDirty = trueFalse
    End Set
End  Property

Now, any outside assembly can check the IsDirty property, but only classes within the same assembly can set it.

HardCode
This seems to work great, except when I try to use the class containing the IsDirty property from an interface. In that case, it completely ignores the Friend scope and does the set! Ever run into that?
Brian MacKay
I opened another thread about this.
Brian MacKay