我的域模型中有几个不同的实体(动物物种,比方说),每个实体都有一些属性.实体是只读的(它们在应用程序生命周期内不会改变状态)并且它们具有相同的行为(仅由属性值不同).
如何在代码中实现这样的实体?
我尝试了这样的枚举:
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的新实例,这是我想要避免的.
是否有一种简单的方法可以使第三次尝试起作用?有关实施此类模型的更多建议吗?
如果您遇到序列化问题,您始终可以将应用程序代码与序列化代码分开。也就是说,放置与序列化状态之间进行转换的转换类。序列化的实例可以公开所需的任何空构造函数和属性,并且它们唯一的工作是序列化状态。同时,您的应用程序逻辑与不可序列化、不可变的对象一起工作。这样,您就不会将序列化问题与逻辑问题混合在一起,正如您发现的那样,这会带来许多缺点。
编辑:这是一些示例代码:
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 不再存在。