Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to represent different entities that have identical behavior?

Tags:

c#

I have several different entities in my domain model (animal species, let's say), which have a few properties each. The entities are readonly (they do not change state during the application lifetime) and they have identical behavior (the differ only by the values of properties).

How to implement such entities in code?

Unsuccessful attempts:

Enums

I tried an enum like this:

enum Animals {
    Frog,
    Duck,
    Otter,
    Fish  
}

And other pieces of code would switch on the enum. However, this leads to ugly switching code, scattering the logic around and problems with comboboxes. There's no pretty way to list all possible Animals. Serialization works great though.

Subclasses

I also thought about where each animal type is a subclass of a common base abstract class. The implementation of Swim() is the same for all Animals, though, so it makes little sense and serializability is a big issue now. Since we represent an animal type (species, if you will), there should be one instance of the subclass per application, which is hard and weird 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 plain awful.

Static fields

This blog post gave me and idea for a solution where each option is a statically defined field inside the type, like this:

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 like enums (AnimalType = Animal.Otter), you can easily add a static list of all defined animals, you have a sensible place where to implement Swim(). Immutability can be achieved by making property setters protected. There is a major problem, though: it breaks serializability. A serialized Animal would have to save all its properties and upon deserialization it would create a new instance of Animal, which is something I'd like to avoid.

Is there an easy way to make the third attempt work? Any more suggestions for implementing such a model?

like image 582
Dominik Avatar asked Jun 02 '12 22:06

Dominik


3 Answers

If you have issues with serialization, you can always separate the application-code from the serialization code. That is, place conversion classes that convert to/from your serialized state. The serialized instances can have exposed any empty constructors and properties needed and their only job is to serialize state. Meanwhile, your application logic works with the non-serializable, immutable objects. This way you do not mix your serialization concerns with logical concerns which brings with it a host of disadvantages as you are finding out.

EDIT: Here's some example 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 application logic for serialization might look something like:

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

Just to reiterate, the SerializableAnimal class and usage is ONLY used in the final layer(s) of your application that need to serialize/deserialize. Everything else works against your immutable Animal classes.

EDITx2: Another major benefit of this managed separation is you can deal with legacy changes in your code. For example, you have a Fish type, which is pretty broad. Maybe you split it into Shark and Goldfish later and decide all your old Fish type should be considered Goldfish. With this separation of serialization, you can now place a check for any old Fish and convert them to Goldfish whereas direct serialization would result in an exception because Fish no longer exists.

like image 58
Chris Sinclair Avatar answered Nov 19 '22 16:11

Chris Sinclair


I would implement it with subclasses, but where the instances of the subclasses don't store any data, like this:

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 shouldn't matter that you have multiple "instances", because each instance only contains its type information (no actual data). Overriding Equals and GetHashCode on the base class means that different instances of the same class will be considered equal.

like image 3
Gabe Avatar answered Nov 19 '22 16:11

Gabe


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

It seems like your only problem is serialization. What kind of serialization we're talking about: binary or XML? If it's binary, have you looked at custom serialization? If it's XML, you should either stick with the second option, also use custom serialization or delegate the serialization logic outside of your classes.

I personally think the latter is the most architecturally sound solution. Mixing object creation and serialization is a bad idea.

like image 1
DreamSonic Avatar answered Nov 19 '22 15:11

DreamSonic