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:
- The animal knows permanently about its barn. (An animal has a field that is a reference to its barn, and we set this field, e.g., when the animal is constructed.) The barn has a method that provides food. The animal can then get food from its barn.
- The animal knows about the barn only at feeding time: When the barn calls the animal, the barn gives itself as an argument, so that the animal can get food from there.
- At feeding time, the barn asks the animal how much food it wants. The barn then deducts this much food from itself and passes that food to the animal.
- The barn contains not only the animals, but also a food source, an object of a new class to design. At feeding time, the barn points each animal to the food source, and lets the animal interact with the food source. The animals need not now about barns, but only about food sources.
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: 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.
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. 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