views:

65

answers:

3

I have a method which should return a snapshot of the current state, and another method which restores that state.

public class MachineModel
{
    public Snapshot CurrentSnapshot { get; }
    public void RestoreSnapshot (Snapshot saved) { /* etc */ };
}

The state Snapshot class should be completely opaque to the caller--no visible methods or properties--but its properties have to be visible within the MachineModel class. I could obviously do this by downcasting, i.e. have CurrentSnapshot return an object, and have RestoreSnapshot accept an object argument which it casts back to a Snapshot.

But forced casting like that makes me feel dirty. What's the best alternate design that allows me to be both type-safe and opaque?

Update with solution:

I wound up doing a combination of the accepted answer and the suggestion about interfaces. The Snapshot class was made a public abstract class, with a private implementation inside MachineModel:

public class MachineModel
{
    public abstract class Snapshot
    {
        protected internal Snapshot() {}
        abstract internal void Restore(MachineModel model);
    }

    private class SnapshotImpl : Snapshot
    {
        /* etc */
    }

    public void Restore(Snapshot state)
    {
        state.Restore(this);
    }
}

Because the constructor and methods of Snapshot are internal, callers from outside the assembly see it as a completely opaque and cannot inherit from it. Callers within the assembly could call Snapshot.Restore rather than MachineModel.Restore, but that's not a big problem. Furthermore, in practice you could never implement Snapshot.Restore without access to MachineModel's private members, which should dissuade people from trying to do so.

+2  A: 

Can MachineModel and Snapshot be in the same assembly, and callers in a different assembly? If so, Snapshot could be a public class but with entirely internal members.

Jon Skeet
`MachineModel` will have callers both from within the same assembly and from outside assemblies.
JSBangs
+1  A: 

I could obviously do this by downcasting, i.e. have CurrentSnapshot return an object, and have RestoreSnapshot accept an object argument which it casts back to a Snapshot.

The problem is that somebody could then pass an instance of an object which is not Snapshot.

If you introduce an interface ISnapshot which exposes no methods, and only one implementation exists, you can almost ensure type-safety at the price of a downcast.

I say almost, because you can not completely prevent somebody from creating another implementation of ISnapshot and pass it, which would break. But I feel like that should provide the desired level of information hiding.

ewernli
+1  A: 

You could reverse the dependency and make Snapshot a child (nested class) of MachineModel. Then Snapshot only has a public (or internal) Restore() method which takes as a parameter an instance of MachineModel. Because Snapshot is defined as a child of MachineModel, it can see MachineModel's private fields.

To restore the state, you have two options in the example below. You can call Snapshot.RestoreState(MachineModel) or MachineModel.Restore(Snapshot)*.

public class MachineModel
{
    public class Snapshot
    {
        int _mmPrivateField;

        public Snapshot(MachineModel mm) 
        { 
            // get mm's state
            _mmPrivateField = mm._privateField;
        }

        public void RestoreState(MachineModel mm) 
        { 
            // restore mm's state
            mm._privateField = _mmPrivateField;
        }
    }

    int _privateField;

    public Snapshot CurrentSnapshot
    {
        get { return new Snapshot(this); }
    }

    public void RestoreState(Snapshot ss)
    {
        ss.Restore(this);
    }
}

Example:

    MachineModel mm1 = new MachineModel();
    MachineModel.Snapshot ss = mm1.CurrentSnapshot;
    MachineModel mm2 = new MachineModel();
    mm2.RestoreState(ss);

* It would be neater to have Snapshot.RestoreState() as internal and put all callers outside the assembly, so the only way to do a restore is via MachineModel.RestoreState(). But you mentioned on Jon's answer that there will be callers inside the same assembly, so there isn't much point.

Charles