如何表示具有相同行为的不同实体?

Dom*_*nik 7 c#

我的域模型中有几个不同的实体(动物物种,比方说),每个实体都有一些属性.实体是只读的(它们在应用程序生命周期内不会改变状态)并且它们具有相同的行为(仅由属性值不同).

如何在代码中实现这样的实体?

不成功的尝试:

枚举

我尝试了这样的枚举:

enum Animals {
    Frog,
    Duck,
    Otter,
    Fish  
}
Run Code Online (Sandbox Code Playgroud)

其他代码片段将打开枚举.然而,这导致丑陋的切换代码,分散逻辑和组合框的问题.列出所有可能的动物没有很好的方法.序列化虽然很好.

子类

我还想过每个动物类型在哪里是公共基础抽象类的子类.但是,对于所有动物来说,Swim()的实现是相同的,因此它没有多大意义,可串行化现在是一个大问题.由于我们代表一种动物类型(种类,如果你愿意),每个应用程序应该有一个子类的实例,当我们使用序列化时,这很难维护.

public abstract class AnimalBase {
    string Name { get; set; } // user-readable
    double Weight { get; set; }
    Habitat Habitat { get; set; }
    public void Swim(); { /* swim implementation; the same for all animals but depends                  uses the value of Weight */ }
}

public class Otter: AnimalBase{
    public Otter() {
        Name = "Otter";
        Weight = 10;
        Habitat = "North America";
    }
}

// ... and so on
Run Code Online (Sandbox Code Playgroud)

简直太糟糕了.

静态字段

这篇博文给了我一个解决方案的想法,其中每个选项都是类型中静态定义的字段,如下所示:

public class Animal {
   public static readonly Animal Otter = 
       new Animal 
       { Name="Otter", Weight = 10, Habitat = "North America"}
   // the rest of the animals...

   public string Name { get; set; } // user-readable
   public double Weight { get; set; }
   public Habitat Habitat { get; set; }

   public void Swim();
Run Code Online (Sandbox Code Playgroud)

}

这将是很棒的:你可以像enums(AnimalType = Animal.Otter)一样使用它,你可以轻松添加所有已定义动物的静态列表,你有一个明智的地方可以实现Swim().通过使财产制定者受到保护可以实现不变性.但是存在一个主要问题:它破坏了可串行性.序列化的Animal必须保存它的所有属性,并且在反序列化时它会创建一个Animal的新实例,这是我想要避免的.

是否有一种简单的方法可以使第三次尝试起作用?有关实施此类模型的更多建议吗?

Chr*_*air 3

如果您遇到序列化问题,您始终可以将应用程序代码与序列化代码分开。也就是说,放置与序列化状态之间进行转换的转换类。序列化的实例可以公开所需的任何空构造函数和属性,并且它们唯一的工作是序列化状态。同时,您的应用程序逻辑与不可序列化、不可变的对象一起工作。这样,您就不会将序列化问题与逻辑问题混合在一起,正如您发现的那样,这会带来许多缺点。

编辑:这是一些示例代码:

public class Animal 
{
    public string Name { get; private set; }
    public double Weight { get; private set; }
    public Habitat Habitat { get; private set; }

    internal Animal(string name, double weight, Habitat habitat)
    {
        this.Name = name;
        this.Weight = weight;
        this.Habitat = habitat;
    }

    public void Swim();
}

public class SerializableAnimal
{
    public string Name { get; set; }
    public double Weight { get; set; }
    public SerializableHabitat Habitat { get; set; } //assuming the "Habitat" class is also immutable
}

public static class AnimalSerializer
{
    public static SerializableAnimal CreateSerializable(Animal animal)
    {
        return new SerializableAnimal {Name=animal.Name, Weight=animal.Weight, Habitat=HabitatSerializer.CreateSerializable(animal.Habitat)};
    }

    public static Animal CreateFromSerialized(SerializableAnimal serialized)
    {
        return new Animal(serialized.Name, serialized.Weight, HabitatSerializer.CreateFromSerialized(serialized.Habitat));
    }

    //or if you're using your "Static fields" design, you can switch/case on the name
    public static Animal CreateFromSerialized(SerializableAnimal serialized)
    {
        switch (serialized.Name)
        {
            case "Otter" :
                return Animal.Otter
        }

        return null; //or throw exception
    }
}
Run Code Online (Sandbox Code Playgroud)

那么您的序列化应用程序逻辑可能如下所示:

Animal myAnimal = new Animal("Otter", 10, "North America");
Animal myOtherAnimal = Animal.Duck; //static fields example

SerializableAnimal serializable = AnimalSerializer.CreateSerializable(myAnimal);
string xml = XmlSerialize(serializable);
SerializableAnimal deserialized = XmlDeserializer<SerializableAnimal>(xml);

Animal myAnimal = AnimalSerializer.CreateFromSerialized(deserialized);
Run Code Online (Sandbox Code Playgroud)

重申一下,SerializedAnimal 类和用法在需要序列化/反序列化的应用程序的最后一层中使用。 其他一切都对你不可变的 Animal 类起作用。

EDITx2:这种托管分离的另一个主要好处是您可以处理代码中的遗留更改。例如,您有一个Fish非常广泛的类型。也许您将其分为Shark和 ,Goldfish然后决定Fish应考虑所有旧类型Goldfish。通过序列化的这种分离,您现在可以检查任何旧的 Fish 并将其转换为 Goldfish,而直接序列化将导致异常,因为 Fish 不再存在。