How to represent different objects that have the same behavior?

I have several different entities in my domain model (say, animal species) that have several properties. Objects are readonly (they do not change state during the life of the application), and they have the same behavior (differ only in property values).

How to implement such objects in code?

Failed Attempts:

Transfers

I tried the listing as follows:

enum Animals { Frog, Duck, Otter, Fish } 

And other code snippets included an enumeration. However, this leads to an ugly commutation code, dissipation of logic, and problems with comboboxes . There is no beautiful way to list all possible animals. Serialization works great.

Subclasses

I also thought about where each type of animal is a subclass of the abstract class of the common base. The implementation of Swim () is the same for all animals , although this makes little sense, and serializability is a big problem right now. Since we represent the type of animals (species, if you want), there must be one instance of a subclass for each application, which is difficult and strange to maintain when we use serialization.

 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 

Just awful.

Static fields

This blog post gave me an idea for a solution, where each parameter is a statically defined field inside the type, for example:

 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(); 

}

That would be great: you can use it as enums ( AnimalType = Animal.Otter ), you can easily add a static list of all defined animals, you have a reasonable place where you can implement Swim() . Immutability can be achieved by protecting real estate. However, there is a big problem: it violates serializability. A serialized animal would have to retain all of its properties, and upon deserialization it would create a new instance of Animal, which I would like to avoid.

Is there an easy way to make a third attempt? Are there any other suggestions for implementing such a model?

+7
source share
4 answers

If you have problems with serialization, you can always separate the application code from the serialization code. That is, move the conversion classes that convert to / from your serialized state. Serialized instances can open any empty constructors and necessary properties, and their only task is to serialize the state. Meanwhile, your application logic works with non-serializable, immutable objects. Thus, you do not confuse your serialization problems with logical problems, which bring with them many shortcomings that you will learn.

EDIT: Here is a sample code:

 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 } } 

Then your serialization application logic might look something like this:

 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); 

To repeat, the SerializableAnimal class and its use are used ONLY in the final layer (s) of your application that you need to serialize / deserialize. Everything else works against your immutable animal classes.

EDITx2: Another major benefit of this managed separation is that you can deal with obsolete changes in your code. For example, you have a type Fish , which is quite wide. Perhaps you split it into Shark and Goldfish later and decide that all of your old type of Fish should be considered Goldfish . With this separation of serialization, you can now put a check for any old fish and convert them to Goldfish, while direct serialization will throw an exception because Fish no longer exists.

+3
source

I would implement it with subclasses, but where instances of the subclasses do not store any data, for example:

 public abstract class AnimalBase { public abstract string Name { get; } // user-readable public abstract double Weight { get; } public abstract Habitat Habitat { get; } public void Swim(); { /* swim implementation; the same for all animals but uses the value of Weight */ } // ensure that two instances of the same type are equal public override bool Equals(object o) { return o != null && o.GetType() == this.GetType(); } public override int GetHashCode() { return this.GetType().GetHashCode(); } } // subclasses store no data; they differ only in what their properties return public class Otter : AnimalBase { public override string Name { return "Otter"; } public override double Weight { return 10; } // here we use a private static member to hold an instance of a class // that we only want to create once private static readonly Habitat habitat = new Habitat("North America"); public override Habitat Habitat { return habitat; } } 

Now it does not matter that you have several “instances”, because each instance contains only type information (no actual data). Overriding Equals and GetHashCode in the base class means that different instances of the same class will be considered equal.

+3
source

As I see it, you are looking for the right creation template to suit your needs. Your first option is similar to the factory method. The second option looks like a type hierarchy with an optional abstract factory . The third is singleton .

It seems your only problem is serialization. What serialization are we talking about: binary or XML? If it's binary, have you watched custom serialization ? If this is XML, you must either stick to the second option, or use custom serialization, or delegate serialization logic outside of your classes.

I personally believe that the latter is the most architectural solution. Creating and serializing mixing objects is a bad idea.

+1
source

I would go with the third option (objects!), But with a slight twist.

Point: you have a set of objects with a specific scheme ...

 public class Animal { public string Name { get; set; } // user-readable public double Weight { get; set; } public Habitat Habitat { get; set; } public void Swim(); } 

but you want them to be predetermined. Trick: If you serialize such an object, you do not want its fields to be serialized . Field initialization is the responsibility of the application, and the only thing you really want in your serialized version is the "type" of the animal. This will allow you to change Otter to Sea Otter and keep the data consistent.

Therefore, you need some idea of ​​the "type of animal" - and this is the only thing you want to serialize. . When deserializing, you want to read the type identifier and initialize all fields based on it.

Oh, and another attempt at deserialization, you don’t want to create a new object! You want to read the ID (and only the ID) and get one of the predefined objects (corresponding to this ID).


The code might look like this:

 public class Animal { public static Animal Otter; public static Animal Narwhal; // returns one of the static objects public static Animal GetAnimalById(int id) {...} // this is here only for serialization, // also it the only thing that needs to be serialized public int ID { get; set; } public string Name { get; set; } public double Weight { get; set; } public Habitat Habitat { get; set; } public void Swim(); } 

So far so good. If there are dependencies that prevent you from making static instances, you can add lazy initialization to all Animal objects.

The Animal class begins to look like a "pair of loners in one place."

Now how to connect it to the .NET serialization engine (BinarySerializer or DataContractSerializer). We want the serializer to use GetAnimalById instead of the constructor when deserializing and to keep only the identifier when serializing.

Depending on your serialization API, you can do this using ISerializationSurrogate or IDataContractSurrogate. This is an example:

 class Surrogate : IDataContractSurrogate { public Type GetDataContractType(Type type) { if (typeof(Animal).IsAssignableFrom(type)) return typeof(int); return type; } public object GetObjectToSerialize(object obj, Type targetType) { // map any animal to its ID if (obj is Animal) return ((Animal)obj).ID; return obj; } public object GetDeserializedObject(object obj, Type targetType) { // use the static accessor instead of a constructor! if (targetType == typeof(Animal)) return Animal.GetAnimalById((int)obj); } } 

BTW: DataContacts seems to have an error (or is it a function?) That makes them act weird when the lookup type is the base type. I had such a problem when serializing objective objects like strings - the GetDeserializedObject method never started when deserializing. If you encounter this behavior, use a wrapper class or structure around this single int field in the surrogate.

0
source

All Articles