开发者

Return an opaque object to the caller without violating type-safety

开发者 https://www.devze.com 2023-01-01 11:39 出处:网络
I have a method which should return a snapshot of the current state, and another method which restores that state.

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

public class MachineModel
{
    public Sn开发者_Go百科apshot 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.


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.


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.


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.


This is an old question, but i was looking for something very similar and I ended up here and between the information reported here and some other I came up with this solution, maybe is a little overkill, but this way the state object is fully opaque, even at the assembly level

class Program
{
    static void Main(string[] args)
    {
        DoSomething l_Class = new DoSomething();

        Console.WriteLine("Seed: {0}", l_Class.Seed);

        Console.WriteLine("Saving State");

        DoSomething.SomeState l_State = l_Class.Save_State();

        l_Class.Regen_Seed();

        Console.WriteLine("Regenerated Seed: {0}", l_Class.Seed);

        Console.WriteLine("Restoring State");

        l_Class.Restore_State(l_State);

        Console.WriteLine("Restored Seed: {0}", l_Class.Seed);

        Console.ReadKey();
    }
}

class DoSomething
{
    static Func<DoSomething, SomeState> g_SomeState_Ctor;

    static DoSomething()
    {
        Type type = typeof(SomeState);
        System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
    }

    Random c_Rand = new Random();

    public DoSomething()
    {
        Seed = c_Rand.Next();
    }

    public SomeState Save_State()
    {
        return g_SomeState_Ctor(this);
    }

    public void Restore_State(SomeState f_State)
    {
        ((ISomeState)f_State).Restore_State(this);
    }

    public void Regen_Seed()
    {
        Seed = c_Rand.Next();
    }

    public int Seed { get; private set; }

    public class SomeState : ISomeState
    {
        static SomeState()
        {
            g_SomeState_Ctor = (DoSomething f_Source) => { return new SomeState(f_Source); };
        }

        private SomeState(DoSomething f_Source) { Seed = f_Source.Seed; }

        void ISomeState.Restore_State(DoSomething f_Source)
        {
            f_Source.Seed = Seed;
        }

        int Seed { get; set; }
    }

    private interface ISomeState
    {
        void Restore_State(DoSomething f_Source);
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消