Заставить производный класс реализовать / расширить конкретную структуру внутри - PullRequest
0 голосов
/ 23 октября 2018

Я должен расширить приложение, добавив в него больше классов, в качестве своего рода «плагинов».Приложение принимает строковый параметр и пытается создать экземпляр объекта класса с именем, соответствующим этой строке.если это удается, приложение продолжает вызывать различные конкретные функции из этого класса (например, Initialize()) и сериализовать структуру или класс с именем Data* внутри.В идеале один такой класс должен выглядеть следующим образом:

public class SomePlugin
{
    public abstract event ChangesAppliedDelegate ChangesApplied;
    public delegate void ChangesAppliedDelegate();

    public Model Data = new Model();

    public SomePlugin()
    {
        Data.Initialized = false;
    }

    public bool Initialize()
    {
        Data.Initialized = true;
        return true;
    }

    [Serializable]
    public struct Model
    {
        bool Initialized;
    }
}

Согласно настоящему файлу readme, в каждом таком классе ДОЛЖНЫ существовать следующие вещи, потому что фреймворк опирается на них:

  • Событие с именем ChangesApplied, на которое платформа может подписаться.
  • Сериализуемый объект с именем Data, определение и содержание не имеют значения.
  • Метод Initialize(), который будет вызван один разобъект был создан.

Теперь я пытаюсь заменить этот readme базовым классом, который предположительно «поможет» мне или любому следующему разработчику реализовать эти требования так же ясно, каквозможный.Теперь я застрял в определении модели данных.

Я бы предпочел сделать это частичным и пустым, чтобы оно существовало в базовом классе, и фреймворк мог полагаться на него, в то время как все производные классы могутпри желании можно заполнить его содержимым:

public abstract class Base
{
    public abstract Model Data;

    public partial struct Model { }
}

public class Derivate : Base 
{
    public Model Data = new Model();

    public bool Initialize()
    {
        Data.A = 1;
        Data.B = "Hello world.";
        return true;
    }
    public partial struct Model
    {
        int A;
        string B;
    }
}

Но это дает целый ряд ошибок и предупреждений компилятора (например, то, что Derivate.Model скрывает Base.Model (это не то, что я намеревался), или это модификатор 'abstract' недопустим в полях. , предлагая мне использовать свойства, что в любом случае приводит меня к функциям getter и setters).Как лучше всего реализовать мои идеи?Моя идея чем-то хороша или я просто делаю вещи более сложными, чем они есть?

ОБНОВЛЕНИЕ

Пока этот вопрос приостановлен, я 'Мы пришли к выводу, что интерфейс (в отличие от абстрактного класса) лучше подходит для этой цели с двумя основными недостатками:

  • Фреймворковое приложение должно изменить способ доступа к сериализуемым данным.
  • Хотя программисту не нужно реализовывать какую-либо структуру или класс, GetData() и SetData(), в частности обобщенный тип возврата GetData() (object), подразумевают существование некоторого вида объекта Data,Но поскольку среда больше не работает с данными напрямую, вопрос как и где для хранения данных становится неактуальным (поэтому я использую MyModel вместо Model в следующем коде).

Это выглядит следующим образом:

public interface IBase
{
    object GetData();
    void SetData(object newData);
}

public Derivate : IBase
{
    public MyModel Data = new MyModel();

    public bool Initialize()
    {
        Data.A = 1;
        Data.B = "Hello world.";
        return true;
    }

    public object GetData()
    {
        return Data;
    }

    public void SetData(object newData)
    {
        Data = (MyModel) newData;
    }

    [Serializable]
    public struct MyModel
    {
        public int A;
        public string B;
    }
}

* Для тех, кто читает вопрос перед надлежащим повторным редактированием и уточнением: я перепутал значение Model (определение структуры) и Data (экземпляр Model).

1 Ответ

0 голосов
/ 23 октября 2018

Это мой взгляд на эту ситуацию:

Сначала контракт на плагин:

public interface IModel
{
    bool Initialized { get; }
}
public interface IPlugin<TModel> where TModel: struct, IModel
{
    event ChangesAppliedDelegate<TModel> ChangesApplied;
    TModel Model { get; }
    void SetData(TModel model);
}
public delegate void ChangesAppliedDelegate<in TModel>(TModel model) where TModel : struct, IModel;

Затем типичная реализация контракта

public struct MyModel : IModel
{
    /// <summary>
    /// Data constructor. Assumes data is initialized after this is called.
    /// </summary>
    /// <param name="a">The A value</param>
    /// <param name="b">The B value</param>
    public MyModel(int a, string b)
    {
        this.A = a;
        this.B = b;
        this.Initialized = true;
    }
    /// <summary>
    /// Copy construct, but with ability to flip the Initialized property
    /// </summary>
    /// <param name="other">The model to copy data from</param>
    /// <param name="initialized">Set the initialization flag here</param>
    public MyModel(MyModel other, bool initialized = true)
    {
        this = other;
        this.Initialized = initialized;
    }
    public int A { get; }
    public string B { get; }
    public bool Initialized { get; }

}

public class MyModelPlugin: IPlugin<MyModel>
{
    public MyModel Model { get; private set; }

    public event ChangesAppliedDelegate<MyModel> ChangesApplied;

    public void SetData(MyModel model)
    {
        if(model.Initialized)
        {
            this.Model =model;
            ChangesApplied?.Invoke(Model);
        }
        else
        {
            // Handle case where model isn't initialized
        }
    }
}

Наконец, пример использования

static class Program
{
    static void Main(string[] args)
    {
        var plugin = new MyModelPlugin();
        plugin.ChangesApplied += (m) => Debug.WriteLine(m.Initialized ? $"A={m.A}, B={m.B}" : "Uninitialized");

        // Uninitialized model
        var model = new MyModel();
        plugin.SetData(model);

        // Initialized model
        model = new MyModel(1, "Hello SO");
        plugin.SetData(model);

        // Make a copy and check equality
        var copy = plugin.Model;
        Debug.WriteLine($"Copy is identical = { copy == model }");
    }
}

с выводом отладчика

A=1, B=Hello SO
Copy is identical = True

Приложение

Требуемый код для каждой структуры модели, показанной ниже, для обработки инфраструктурыструктуры правильно.Структура должна быть неизменной, и она должна обрабатывать операторы равенства == и != на основе данных.Наконец, GetHashCode() необходимо вернуть уникальное управляемое данными значение для использования в контейнерах типа Dictionary.

[ImmutableObject(true)]
public struct MyModel : IModel, IEquatable<MyModel>
{
    ...
    #region Equality Comparison
    public override bool Equals(object obj)
    {
        if(obj is MyModel model)
        {
            return Equals(model);
        }
        return false;
    }

    public bool Equals(MyModel other)
    {
        return A==other.A&&
               B==other.B;
    }

    public override int GetHashCode()
    {
        var hashCode = -1817952719;
        hashCode=hashCode*-1521134295+A.GetHashCode();
        hashCode=hashCode*-1521134295+B.GetHashCode();
        return hashCode;
    }

    public static bool operator ==(MyModel model1, MyModel model2)
    {
        return model1.Equals(model2);
    }

    public static bool operator !=(MyModel model1, MyModel model2)
    {
        return !(model1==model2);
    }

    #endregion
...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...