Penca53
Penca53

Reputation: 80

Best way to handle similar classes and store them?

EDIT: This isn't the best approach for this problem. I managed to change design and use a List/Dictionary which creates a new instance of a class depending on the key selected. In this way you don't need any cast nor Reflection nor switch statement.

I've a question that i tried to solve by myself but i'm not satisfied with my solutions. Let's set up a little example: i have a Farm class, a Mill class and a Bakery class. Those are buildings, and i'd like to store them in a list and obiouvsly i'd like to manage its elements whenever i need it. In this case this is a server-side code, and the list would need to have a list of all building of a player so i can send him that list whenever he requests it, for example when it joins. The player could build a Farm to start farming some wheat, then a Mill to start crushing it to make flour. Then he builds a Bakery to make some bread or pies. This is the example code:

public class Program
{
    public List<> Buildings = new List<>() //Or whatever solution to store them
}

public class Farm
{
    public string Name;
    public int CropType;
    public float Timer;
}

public class Mill
{
    public string Name;
    public float Timer;
}

public class Bakery
{
    public string Name;
    public int ProductionType;
    public bool IsOpen;
    public float Timer;
}

I've tried using Interfaces, abstract classes and normal classes to derive from but i haven't found my solution.

This is my try:

public interface IBuilding
    {
        string ExposedName { get; set; }
        string ExposedDescription { get; set; }
    }

    public class Farm : IBuilding
    {
        public string Name { get { return Name; } set { Name = value; } }
        public string Description { get { return Description; } set { Description = value; } }
        public int Crop;
    }

    public class Mill : IBuilding
    {
        public string Name { get { return Name; } set { Name = value; } }
        public string Description { get { return Description; } set { Description = value; } }
        public float Timer;
    }

I'd like to be able to access to each variable of each element of the List or whatever solution you suggest me without using non-performant code.

Upvotes: 1

Views: 177

Answers (4)

trollingchar
trollingchar

Reputation: 790

Inheriting from a base class is the right way to do this. But when accessing elements from list, you should only use interface of base class. That means, only members of Building are allowed to use. Anything that relies on runtime type checking will work against you when you will add new buildings, because you must remember every place where it is to add a new case in switch.

Extract to base class only those members that can be called on any building, and implement them in derived classes.

You mentioned that you want not only to store them in a list, but also send to client (and probably receive). So I recommend to define Building such as this:

public abstract class Building : ISerializable, IDeserializable {
    // here you define interface, members which every building has
    public abstract string Name { get; }
    public abstract short Id { get; }
    public abstract void Update (); // do staff that this type of building does

    public abstract void ReadMembers (BinaryReader reader);
    public void Write (BinaryWriter writer) {
        writer.Write (Id);
        WriteMembers (writer);
    }
    public abstract void WriteMembers (BinaryWriter writer);
}

So, when you iterate through a list, you call methods from base class:

foreach (var b in buildings) b.Update ();

foreach (var b in buildings) b.Write (writer);

Reading is tricky. It requires writing an extension method for your reader, which should know about ids to create the right type of building.

public static class BinaryReaderExtension {

    private static Dictionary <short, Func <Building>> generators = new Dictionary <short, Func <Building>> ();


    static BinaryReaderExtension () {
        generators [0] = () => new Farm ();
        generators [1] = () => new Mill ();
    }


    public static Building ReadBuilding (this BinaryReader reader) {
        var b = generators [reader.ReadInt16 ()] ();
        b.ReadMembers (reader);
        return b;
    }

}

Important: Id must return unique number for each building, the same as in extension.

There is much room for experimenting.
You can use array instead of dictionary. You can write an extension for writer and remove Id and Write from Building class. You can use static method in Building returning Building instead of reader extension. You can also make such as this in Building class:

private Dictionary <Type, short> codeByType;
private Dictionary <short, Func <Building>> instanceByCode;
private void RegisterBuilding (Type t, short code) {
    // custom error checking for code to be unique, etc
}

and populate dictionaries from Building static initializer via this function.

Derived class should be defined such as:

public class Farm : Building {
    public override string Name => "Farm"; // assuming c# 6.0
    public override short Id => 0;

    private Crop crop;
    // other farm-specific things
    public override void Update {
        // you access a private field,
        // but Update method itself can be called even if you don't know the type
        // every building has its own implementation
        crop.Grow ();

        // other farm-specific things
    }

// override other methods, feel free to access private members of Farm here
}

Upvotes: 0

Icculus018
Icculus018

Reputation: 1066

This is how i would set it up. You need to inherit from building, like others have mentioned. When running through the loop, just check if the building is a farm or bakery.

public abstract class Building
{
    public string Name { get; set; }
    public float Timer { get; set; }


}
public class Farm : Building
{
    public int CropType { get; set; }
    public Farm()
    {
    }
    public Farm(string name, int cropType, float timer)
    {
        Name = name;
        Timer = timer;
        CropType = cropType;
    }
}
public class Bakery : Building
{
    public int ProductionType { get; set; }
    public bool IsOpen { get; set; }
    public Bakery()
    {
    }
    public Bakery(string name, float timer, int prodType, bool isOpen)
    {
        Name = name;
        Timer = timer;
        ProductionType = prodType;
        IsOpen = isOpen;
    }
}

public class Example
{
    public static void myMethod()
    {
        Bakery bakery1 = new Bakery("Bake1", 123, 1, true);
        Farm farm1 = new Farm("Farm1", 2, 321);

        List<Building> buildings = new List<Building>();
        buildings.Add(bakery1);
        buildings.Add(farm1);

        foreach(Building building in buildings)
        {
            if(building is Farm)
            {
                Farm f1 = (Farm)building;
                int cropType = f1.CropType;
            }
            else if(building is Bakery)
            {
                Bakery b1 = (Bakery)building;
                int prodType = b1.ProductionType;
            }
        }
    }
}

Upvotes: -2

Scott Hannen
Scott Hannen

Reputation: 29222

In this description:

The player could build a Farm to start farming some wheat, then a Mill to start crushing it to make flour. Then he builds a Bakery to make some bread or pies.

...you describe three different uses for three different classes.

In order to store a Farm, a Mill, and a Bakery in a list, they would need to have some common type. But that's really beside the point. They are three different classes that do different things, so if you're using them to farm, mill, and bake then there's no benefit whatsoever to putting them in a list with each other.

An interface or common class is only useful for describing what objects have or do in common, and using them in that context.

If you send a client a list of Building, the client only knows that they have descriptions and names. They can't know about the unique behaviors of different, more specific types of Building. They shouldn't know. That way if Building had a method like:

someBuilding.CloseDoors()

Then it would be possible to call that method on a building without knowing if it's a bakery, mill, etc. We don't care - we just want to close the doors. But if we want to Bake then we don't want a Building. We want a Bakery.

If you want to give the client objects that do distinctly different things then a list of one type of thing won't accomplish that.

The specifics of how to provide this functionality depend on your application. But here are some alternatives:

  • Don't provide a list of something common. Provide instances or lists of something specific. Do you want to bake? Here are list of your bakeries. (The common Name and Description properties might make it easier to display a list of buildings in a reusable way.) Pick one and use it.
  • Does the client actually need a list of bakeries, or just one? Maybe all they need is a Bake API method, and the available bakeries are managed behind the scenes.

Upvotes: 2

SomeRandomGuy
SomeRandomGuy

Reputation: 579

Based on your try, but with an abstract class :

public abstract class Building
{
    string Name { get; set; }
    string Description { get; set; }
}

public class Farm : Building
{
    public int Crop { get; set; }
}

public class Mill : Building
{
    public float Timer { get; set; }
}

Another part of the solution could be the IEnumerable possibilities (like OfType) to access to your properties of the items in your list in a loop. (Without "manual" casting nor ulgy "isXXX" code)

class MyCollection
{
    List<Building> buildings;

    public MyCollection()
    {
        buildings = new List<Building>();

        buildings.Add(new Farm() { Crop = 4 });
        buildings.Add(new Mill() { Timer = 4.5f });
        buildings.Add(new Farm() { Crop = 5 });
        buildings.Add(new Farm() { Crop = 6 });
        buildings.Add(new Mill() { Timer = 42 });
        buildings.Add(new Farm() { Crop = 55 });
    }

    public void Print()
    {
        foreach (Farm f in buildings.OfType<Farm>())
        {
            Console.WriteLine(f.Crop);
        }

        foreach (Mill m in buildings.OfType<Mill>())
        {
            Console.WriteLine(m.Timer);
        }
    }
}

I Don't know if it's what you mean by

"I'd like to be able to access to each variable of each element of the List or whatever solution you suggest me without using non-performant code."

Upvotes: 2

Related Questions