Reputation: 80
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
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
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
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:
Name
and Description
properties might make it easier to display a list of buildings in a reusable way.) Pick one and use it.Bake
API method, and the available bakeries are managed behind the scenes.Upvotes: 2
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