【问题标题】:Serializing a custom object using SOLID principles使用 SOLID 原则序列化自定义对象
【发布时间】:2016-04-26 21:46:23
【问题描述】:

我想序列化包含纯数据的模型对象(来自 WPF MVVM)。这听起来很简单,但我不想使用 .NET 框架中提供的序列化属性和东西。我只是想用我自己的方式序列化它。
所以这是我的一门课程的简化版本。

public class EntryKeyValuePair 
{
    public EntryKeyValuePair(string key, string value, bool isMultiline = false, bool isMandatory = true, bool isProtected = false)
    {
        Key = key;
        Value = value;
        IsMultiline = isMultiline;
        IsMandatory = isMandatory;
        IsProtected = isProtected;
    }

    public string Key { get; set; }
    public string Value { get; set; }
    public bool IsMultiline { get; set; }
    public bool IsMandatory { get; set; }
    public bool IsProtected { get; set; }

    public static EntryKeyValuePair FromXML(XElement element, ICipher cipher)
    {
        string key = cipher.Decrypt(element.Element(nameof(Key)).Value);
        string value = cipher.Decrypt(element.Element(nameof(Value)).Value);
        bool isMultiline = bool.Parse(element.Element(nameof(IsMultiline)).Value);
        bool isMandatory = bool.Parse(element.Element(nameof(IsMandatory)).Value);
        bool isProtected = bool.Parse(element.Element(nameof(IsProtected)).Value);
        return new EntryKeyValuePair(key, value, isMultiline, isMandatory, isProtected);
    }

    public XElement ToXML(ICipher cipher)
    {
        return new XElement(nameof(EntryKeyValuePair),
                                new XElement(nameof(Key),cipher.Encrypt(Key)),
                                new XElement(nameof(Value), cipher.Encrypt(Value)),
                                new XElement(nameof(IsMultiline), IsMultiline), new XElement(nameof(IsMandatory), IsMandatory),
                                new XElement(nameof(IsProtected), IsProtected));
    }
}

这很好用。但这违反了单一责任原则,也可能违反了其他原则。这也很难维护和扩展。
所以我想另辟蹊径。这里是:

首先我定义了一个IStringFormatter 接口,它可以将数据格式化为任何字符串数据格式,如XML 和JSON。 (不确定)

interface IStringFormatter
{
    string Name { get; set; }
    Dictionary<string, string> FieldDictionary { get; }
    string Format();
}

下面是 XMLStringFormatter 的样子:

class XmlStringFormatter : IStringFormatter
{
    public XmlStringFormatter()
    {
        FieldDictionary = new Dictionary<string, string>();
    }

    public string Name { get; set; }
    public Dictionary<string, string> FieldDictionary { get; }

    public string Format()
    {
        var xElement = new XElement(Name, FieldDictionary.Keys.Select(key => new XElement(key, FieldDictionary[key])));
        return xElement.ToString();
    }
}

然后我定义了一个ISerializer 来将我的数据对象序列化(或者说保存)到IStringFormatter

interface ISerializer<T>
{
    T DeSerialize(IStringFormatter stringFormatter);
    void Serialize(T obj, IStringFormatter stringFormatter);
}

这就是我如何通过实现这个来“序列化”EntryKeyValurPair

internal class EntryKeyValurPairSerializer : ISerializer<EntryKeyValuePair>
{
    public EntryKeyValuePair DeSerialize(IStringFormatter stringFormatter)
    {
        Dictionary<string, string> fieldDictionary = stringFormatter.FieldDictionary;

        try {
            string key = fieldDictionary[nameof(EntryKeyValuePair.Key)];
            string value = fieldDictionary[nameof(EntryKeyValuePair.Value)];
            bool isMandatory = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsMandatory)]);
            bool isProtected = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsProtected)]);
            bool isMultiline = bool.Parse(fieldDictionary[nameof(EntryKeyValuePair.IsMultiline)]);
            return new EntryKeyValuePair(key, value, isMultiline, isMandatory, isProtected);
        }
        catch (KeyNotFoundException ex) {
            throw new SerializationException(ex);
        }
        catch (FormatException ex) {
            throw new SerializationException(ex);
        }
    }

    public void Serialize(EntryKeyValuePair obj, IStringFormatter stringFormatter)
    {
        stringFormatter.Name = nameof(EntryKeyValuePair);
        Dictionary<string, string> fieldDictionary = stringFormatter.FieldDictionary;

        fieldDictionary.Add(nameof(EntryKeyValuePair.Key), obj.Key);
        fieldDictionary.Add(nameof(EntryKeyValuePair.Value), obj.Value);
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsMandatory), obj.IsMandatory.ToString());
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsProtected), obj.IsProtected.ToString());
        fieldDictionary.Add(nameof(EntryKeyValuePair.IsMultiline), obj.IsMultiline.ToString());
    }
}

现在这工作正常。但问题是,当我的数据类中有 List&lt;Entry&gt;(其中 Entry 是另一个数据类)之类的复杂类型时。
由于IStringFormatter 包含Dictionary&lt;string, string&gt;,我不能只将List&lt;Entry&gt; 转换为字符串,因为我不知道在ISerializer 的上下文中它想要什么样的IStringFormatter。我怎样才能解决这个问题?我还想知道我的解决方案是否遵循 SOLID 原则。如果您能提出更好的解决方案(不是典型的 .NET 序列化),我将不胜感激。

【问题讨论】:

    标签: c# oop serialization solid-principles


    【解决方案1】:

    编写自己的序列化程序可能是一项有趣的任务,但我怀疑这是个好主意。

    据我了解,您希望保持模型干净,没有任何序列化特定属性。我猜“典型的 .NET 序列化”是指 .Net 框架中包含的方法。

    为简单起见,我们将使用这些简单的类作为示例:

    class Customer
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public List<Order> Orders { get; set; }     
    }
    
    class Order 
    {
        public int Id { get; set; }
        public string Details { get; set; }
    }
    

    一个简单的选择是使用 Json.NET:

    var customer = new Customer 
    { 
        Name = "Darth Vader", 
        Age = 45,
        Orders = new List<Order> 
        {
            new Order { Id = 1, Details = "Order1" },
            new Order { Id = 2, Details = "Order2" }
        }
    };
    string json = JsonConvert.SerializeObject(customer);
    

    如您所见,您不需要向Customer 类添加任何自定义属性。在您想要序列化所有公共属性之前,它将一直有效。

    生成的 JSON 将是:

    {
      "Name": "Darth Vader",
      "Age": 45,
      "Orders": [
        {
          "Id": 1,
          "Details": "Order1"
        },
        {
          "Id": 2,
          "Details": "Order2"
        }
      ]
    }
    

    之后你可以随时反序列化它:

    var customer = JsonConvert.DeserializeObject<Customer>(json);
    

    假设您不希望包含Age 属性。在这种情况下,我建议创建一个仅用于序列化的不同类:

    class CostomerSerializationContract
    {
        public string Name { get; set; }
        public List<Order> Orders { get; set; }
    }
    

    如果这种方法的主要优点是您有单独的序列化类,并且您可以在那里添加任何自定义属性,如果您选择使用其他序列化程序,而不会违反 SOLID 原则。主要缺点是您需要手动保持两个对象同步。

    在从源类创建序列化合约时,您可以使用AutoMapper 来减少手动工作。

    【讨论】:

    • 这也适用于 List 吗?它还具有公共获取、设置属性
    • 是的,它会的。我在答案中添加了一个示例。
    • 如果我有一个BitmapSource(不能序列化)属性怎么办? .我可以将其转换为 base64,但我如何告诉序列化程序这样做呢?
    • Nvm,读入(这个)[stackoverflow.com/questions/18841690/…answer
    • 您可以手动操作,也可以使用 AutoMapper。因此,要从 Customer 类创建序列化合约:var contract = Mapper.Map&lt;CostomerSerializationContract&gt;(customer); 您可以在他们的getting started guide 中获得更多信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多