Wrapping my head around classes

Rhodan

Active member
Joined
Apr 7, 2015
Messages
41
Programming Experience
10+
I'm just starting to work on a fairly simple game just for programming practice/fun and I'm trying to start doing things in a "proper" and efficient fashion rather than my usual "do whatever is most convenient at the time" method that ends up in a confusing mess.

One thing I'm really unsure about is how to use classes. By that I mean should a class be completely self-sufficient or is it OK for it to rely on external classes? Here's an example of what I'm talking about:

I have a map which has locations that have lots of information I need to access such as terrain. There are a finite number of terrain types while the number of locations is variable and unknown until the program runs.

Remembering the idea that having multiple copies of the same data is really bad (you have to find and change every occurance) I've usually done something like this (pseudo code done badly for simplicity)

C#:
Public List<TerrainClass> terrains = new List<TerrainClass>();
Public List<LocationClass> terrains = new List<LocationClass>();

Class TerrainClass()
{
    int _id = 0;
    string _name = "";
    int _moveCost = 1;
    <and more stuff>;
    Public String TerrainName()
    {
        return _name;
    }
}

Class LocationClass ()
{
    int _id = 0;
    int _terrainID = 0;
    <and more stuff>
    
    Public Int TerrainID ()
    {
        return _terrainID;
    }
}

Then when I create a location I figure out which terrain it is and assign that terrain's id to the location and later reference it like

C#:
terrainName = terrains(thisLocation(index).TerrainID()).TerrainName()

Though this seems kind of long winded and prone to typos

I considered just having all the terrain data be part of the location class but there are so many repeats of the same information that it would waste a lot of memory (ok, not really significant compared to modern amount of RAM) and if I wanted to change something about the terrain I'd have to change it in a large number of places.

I also thought about encapsulating the terrain class inside the location class but that doesn't seem very different.

At the same time, when terrain is external it seems like Location isn't stand alone

What's a good way of handing this kind of situation? I'm open to entirely different ideas from this - although I've been programming for decades I've never really be "expert" in any way. Its entirely likely I'm barking up the wrong tree.

Thanks for any help or insights
 
Two things:

First I kind of missed one of my big questions about classes and that is: Should a class be aware of and do things with another class? Say I create a new location and I need to assign a terrain. In the new method for Location is it considered good form for it to use the terrain list to set its own terrain or should that be handled externally? Instinctively it feels wrong for location to do this and I should assign the terrain externally, with the location class only having the information to find the terrain

Second, I just realized I can probably create a reference to an existing terrain inside the location class rather than using an ID or completely copying it. Something like

C#:
Class LocationClass ()
{
    int _id = 0;
    TerrainClass _terrainObject; 
    <and more stuff>

    Public void SetTerrain(TerrainClass TerrainObject)
    {
        _terrainObject = TerrainObject;
    }

    Public TerrainClass GetTerrain()
    {
        return _terrainObject;
    }

    
}

<elsewhere>
locationList(index).SetTerrain(terrainList(terrainIndex);

<Later on>
thisTerrain = LocationList(index).GetTerrain()
terrainName = thisTerrain.Name;
terrainMoveCost = thisTerrain.MoveCost;

That sort of thing. Sound reasonable?
 
I haven't read through both your posts but I'm going to answer in a general sense to begin with. When designing your classes, you should always consider the Single Responsibility Principle, i.e. each thing should only be responsible for doing one thing. Exactly what constitutes doing one thing is highly dependent on context but get better at knowing it when you see it. The DataSet class is a good example. A DataSet class is basically an in-memory representation of a database. That's it's single responsibility. To do that, it need to coordinate one or more tables and the relations between them. To do that, it has a Tables property of type DataTableCollection and a Relations property of type DataRelationCollection. A DataTableCollection is a collection of DataTables. That is its single responsibility. Each DataTable represents a database table. That is its single responsibility. To do that, it has a Columns property of type DataColumnCollection and a Rows property of type DataRowCollection. Etc, etc. Hopefully that shows how the overall task is performed by multiple types that each have specific responsibilities within the whole and how types can interact, i.e. those with more general responsibilities know about those with more specific responsibilities and coordinate them.
 
I didn't quite understand your answer at first but after doing lots of experimenting I think I'm starting to clue in and have come up with what I think is a reasonable philosophy (not using the word "proper" since there seems to be very few correct or proper ways to do anything).

See if this sounds reasonable

A class should do just one thing (as you said above)

If a class needs to do more than one thing then you probably need another class to do the second thing, though that doesn't mean class1 can't be involved.

A class should only ever deal with or modify its own fields/data and never start poking around in the innards of another class

If I needed something like a Location which contains a terrain and a city I could make a location class that incorporates fields to describe everything about locations, terrains, and cities but that would probably get quite large and its violating the "do one thing" philosophy. Instead I'd create three classes

C#:
    class TerrainClass
    {
        public string Name { get; set; }
        public int MoveCost { get; set; }

        public TerrainClass(string name, int movecost)
        {
            Name = name;
            MoveCost = movecost;
        }

        public void DoTerrainishThing(int Command)
        {
            // Whatever you'd do to a terrain
        }
    }

    class CityClass
    {
        public string Name { get; set; }
        public int Population { get; set; }

        public CityClass(string name, int population)
        {
            Name = name;
            Population = population;
        }

        public int GetGrowth()
        {
            int newGrowth = 10;
            return newGrowth;
        }

        public void ModifyPopulation(int GrowthModAmount)
        {
            Population += GrowthModAmount;
        }
    }

    class LocationClass
    {
        public string Name { get; set; }
        public TerrainClass Terrain { get; set; }
        public CityClass City { get; set; }

        public LocationClass(string name)
        {
            Name = name;
        }

        public void SetTerrain(TerrainClass newTerrain)
        {
            Terrain = newTerrain;
        }

        // Or you could create a city instance externally and pass that instead
        public void CreateCity(string name, int population)
        {
            City = new CityClass(name, population);
        }
    }
Many locations could have the same terrain so I'd make an external collection of terrains (I like generic lists myself, but it could be an array or whatever people like) then pass a refernce to the appropriate terrain type to the location instance.
A city would be unique to a location so either have Location create a new instance itself or pass a new instance to it
A location would have a reference to its terrain type and an instance of city

A Location would have no knowledge of what is inside its child classes though you might want to do something like change the type of terrain due to city development. In that case you could have Location assign a new TerrainClass reference to itself.

If something needs to be modified in a terrain or a city you'd use the city or terrain instance's properties or methods to do so, not the location itself. e.g.

Location.City.SetPopulation(200000)
or
Location.Terrain.DoSomethingTerrainish(86)

So I think this fits all the requirements of the Class handling philosophy outlined above.

In hindsight just using .NET to program makes this philosophy pretty obvious as that's pretty much how its doing things.
 
Back
Top Bottom