Lemmings Forums

Lemmings Boards => Help & Guides => Topic started by: Dullstar on February 18, 2020, 02:23:53 AM

Title: Barns, animals, feeding: Software class design
Post by: Dullstar on February 18, 2020, 02:23:53 AM
We seem to have several reasonably experienced programmers on these forums, so I thought I might put this question here.

I'm still trying to learn how classes work and when to use them. From what I've read about them, they sound like they'd help me with organization on larger projects. I might just be having some sort of misconception about how to use classes, but I've come up with the following illustrative example (the actual project has nothing to do with barnyard animals, but I think it's a simple way to illustrate why I want to organize things this way that I suspect the language doesn't want me organizing them):

Suppose we have barns filled with animals and food. In this example, the food supplies in each barn are shared between animals in the same barn, but not between individual animals. Each animal has a daily routine that involves eating if there is enough food in the barn (else, the animal starves). So I'd like to organize things like this:

Note that this isn't intended to be partial code in any language. It's just pseudocode.
Code: [Select]
class Barn:
    contains animals and food

    method have_animals_do_stuff:
        for each animal in animals:

My first instinct would be to try nesting classes, which the interpreter does seem to allow, but it seems like this doesn't actually accomplish anything since from what I could find it sounds like Python doesn't let inner classes access outer class variables.
Title: Re: Barns, animals, feeding: Software class design
Post by: Simon on February 18, 2020, 06:19:11 AM
Visibility of objects. This is the heart of the design problem: An animal in the barn shall access the food in the same barn, but the animal shall not see the food in other animals or anywhere else.

There are several ways to have an animal access the food:
None of these is best in all cases. It's up to taste, and maybe we will reorganize this in a couple months when the system expands.

Asking the animal for the exact amount of food, then giving it that much food, violates Tell, don't ask (https://www.martinfowler.com/bliki/TellDontAsk.html): Hunger is a decision of the animal, not of the barn, therefore we should put the code in the animal, not into the barn. The barn shall tell the animal to get food, not ask the animal about food.

Decoupling the food source from the barn is the most extra code to write, but is good once we herd animals outside a barn. The developer of the system can tell best how much decoupling/generalization is appropriate. :lix-grin:

It also depends on how much other work the barn is doing. If the barn has tractors, hay, ..., that's a reason to delegate the animal-keeping/feeding to another part of the program: separation of concerns (https://en.wikipedia.org/wiki/Separation_of_concerns). If the barn became empty after we removed its keeping/feeding, concerns were probably separated well in the original design already.



Modularity. (Inner classes, moving types to different files.) This example is small enough such that every type may still see every other type, and everything can go in the same source file.

Nonetheless, I'll go into detail here to show why inner classes will not help solve our problem.

In Python, classes are Python objects because everything is a Python object. A class Y can be a member of another object x of class X. This doesn't gain much from the classic OO viewpoint: An object y instantiated from that inner class Y has no special relationship anymore with x. Any association must be set up as normal, e.g., when y is constructed, x might pass itself as a parameter to Y's constructor.

In Java, classes can be inner classes and access the outer classes' private members. I don't believe they are commonly used. If some classes only make sense within a part of the Java project, it's natural to express this via the package system (code in same file, or at least in a file in same directory).

In many languages, you would move classes, types, functions, ..., that loosely belong to each other into different files (modules) in the same directory (package), and import modules to use the types. Whether a single file becomes too big is again a matter of taste and convention.



Data hiding, invariants. This is typically the first mantra that one learns about OO, that one shall hide the raw data. It's merely not the heart of our design problem here; in a way, the objects themselves were hidden too well already, and we're trying to find the best associations between the objects.

For encapsulation, a class hides its data and instead exposes methods that enforce invariants. E.g., the barn or the food source tracks an amount of food, and allows the animals access to the food via methods. These methods enforce that animals cannot take more food than the barn has, and that animals cannot add food. The animals have no direct access to any integer that tracks food, they cannot set it negative.

The benefit of this is that if we ever have a bug with negative food, the encapsulation tells us that the bug must be within the barn.

-- Simon
Title: Re: Barns, animals, feeding: Software class design
Post by: Simon on February 18, 2020, 08:16:08 PM
Restored this thread from Recycling in accordance with Dullstar. Enjoy!

-- Simon