Вернуть непрозрачный объект вызывающей стороне, не нарушая безопасность типов - PullRequest
6 голосов
/ 27 мая 2010

У меня есть метод, который должен возвращать снимок текущего состояния, и другой метод, который восстанавливает это состояние.

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

Класс состояния Snapshot должен быть полностью непрозрачным для вызывающей стороны - никаких видимых методов или свойств - но его свойства должны быть видимы в классе MachineModel. Я мог бы, очевидно, сделать это путем понижения, то есть CurrentSnapshot вернуть object и RestoreSnapshot принять аргумент object, который приведен к Snapshot.

Но принудительное литье так заставляет меня чувствовать себя грязным. Каков наилучший альтернативный дизайн, который позволяет мне быть безопасным и непрозрачным?

Обновление с решением :

Я сделал комбинацию принятого ответа и предложения об интерфейсах. Класс Snapshot стал публичным абстрактным классом с закрытой реализацией внутри 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);
    }
}

Поскольку конструктор и методы Snapshot равны internal, вызывающие извне сборки видят его как полностью непрозрачный и не могут наследовать от него. Вызывающие в сборке могут звонить Snapshot.Restore, а не MachineModel.Restore, но это не большая проблема. Более того, на практике вы никогда не сможете внедрить Snapshot.Restore без доступа к частным членам MachineModel, что должно отговорить людей от попыток сделать это.

Ответы [ 4 ]

3 голосов
/ 27 мая 2010

Я мог бы сделать это, даункастинг, т.е. есть CurrentSnapshot вернуть объект и иметь RestoreSnapshot принимает объект аргумент, который он возвращает к Snapshot.

Проблема в том, что кто-то может затем передать экземпляр объекта, который не является Snapshot.

Если вы вводите интерфейс ISnapshot, который не предоставляет методов, и существует только одна реализация, вы можете почти гарантировать безопасность типов по цене снижения.

Я говорю почти, потому что вы не можете полностью помешать кому-либо создать другую реализацию ISnapshot и пропустить ее, что сломается. Но я чувствую, что это должно обеспечить желаемый уровень сокрытия информации.

3 голосов
/ 27 мая 2010

Могут ли MachineModel и Snapshot находиться в одной сборке, а вызывающие - в другой сборке? Если это так, Snapshot может быть публичным классом, но с полностью внутренними членами.

2 голосов
/ 27 мая 2010

Вы можете изменить зависимость и сделать Snapshot дочерним (вложенным классом) MachineModel. Тогда Snapshot имеет только открытый (или внутренний) метод Restore(), который принимает в качестве параметра экземпляр MachineModel. Поскольку Snapshot определен как дочерний элемент MachineModel, он может видеть личные поля MachineModel.

Чтобы восстановить состояние, у вас есть два варианта в примере ниже. Вы можете вызвать Snapshot.RestoreState (MachineModel) или 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);
    }
}

Пример:

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

* Было бы лучше иметь Snapshot.RestoreState () как internal и поместить все вызывающие объекты вне сборки, поэтому единственный способ сделать восстановление - через MachineModel.RestoreState (). Но вы упомянули в ответе Джона, что внутри той же сборки будут звонящие, так что нет особого смысла.

0 голосов
/ 03 декабря 2018

Это старый вопрос, но я искал что-то очень похожее, и я оказался здесь и между информацией, представленной здесь, и какой-то другой, я придумал это решение, возможно, это немного излишне, но в этом случае объект состоянияполностью непрозрачный, даже на уровне сборки

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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...