Recall a farming simulator game called Stardew Valley from the lecture. You started with the implementation of an Apple and an Apple tree as class Apple and class Tree using an class AppleFactory. The idea of decomposition Tree and AppleFactory was to separate two possibly difficult algorithms (although now they are quite simple):
creation of an apple
when and how many fruits should grow on the tree
This is captured by this code. Following on that, during the lecture, you created a special GoldenApple by deriving from Apple and overriding now virtual method Eat to provide additional feature. In the same manner, the creation of an Apple (responsibility of AppleFactory) was extended to sometimes produce GoldenApples. That is done by these modifications. Note that we didn’t have to modify Tree at all as the logic of a tree being a tree hasn’t changed. This is where the example ended during the lecture.
Now, for yourself, think how would you implement Peach as a fruit, growing on some Tree. Start by thinking how to represent Peach as a fruit in the code. Answer
Note that in our (game) world a definition of Fruit is different than botanical definition of Fruit. In our case, Fruits are all things growing on trees and being harvestable. Whichever approach you prefer more, try to answer the following questions:
What methods/properties/data should be present in the Fruit class/IFruit interface?
How would you implement a Walnut? (Is it a Fruit? Can it be eaten?)
How would you implement an Acorn? (Is it a Fruit? Can it be eaten?)
How would you implement a Cheese (Is it a Fruit? Can it be eaten?)
On the side note, lookup the semantic difference between eatable and edible. In the same manner, try to think for a good word that would describe something that can be given to animals for eating (e.g. acorns).
Answer
Now we have an implementation of Peach. How can we make peaches be able to grow on trees?
Hint,Answer 1/2Answer 2/2
We just extended the Factory design pattern into an Abstract Factory design pattern. Factory allowed us to separate two (possibly hard to implement) features. Abstract factory allows one of the parts to have various implementations.
Here I would ask a question if you know what is Frňákovník (my best translation attempt would be Proboscis tree). This is a term from a czech fairy tale. Please watch this video to understand what the Proboscis tree is (make sure to select best available quality and english subtitles).
So what is Proboscis tree? What fruit does it yield? How does it look like? What is it like?
Answer 1/2,Answer 2/2
This looks all good, but what if a tree that always grows NoseGrowingApples is very rare and more often in our game we would find a tree that mostly grows regular Apples and only occasionally a NoseGrowingApple (e.g. similarly to GoldenApples being grown occasionally on regular apple tree). Can we somehow reuse the idea of AppleFactory in our OccasionalNoseGrowingAppleFactory? Recall that copy-pasting a code is almost never a good idea while programming. This is very difficult to invent, so don’t worry if you can’t get it right.
Hint,Answer 1/3,Answer 2/3,Answer 3/3
This approach is usually referred to as Strategy design pattern.
Now would be a good time to write unit tests for every class and method we have. But is there even any code that can be tested? By good decomposition, we managed to develop these ideas in very small and isolated components that barely have any code to be tested (except the PeriodicSpecialFruitDispensingStrategy).
The link to runnable projects in all variants is present on the previous page.
namespace AppleFactory
{
// Library
class Apple
{
public int Energy { get; init; }
public void Eat()
{
Console.WriteLine($"Apple eaten, player got {Energy} units of energy.");
}
}
class AppleFactory
{
private int _appleEnergy;
public AppleFactory(int appleEnergy)
{
_appleEnergy = appleEnergy;
}
public Apple Create()
{
return new Apple { Energy = _appleEnergy };
}
}
class Tree
{
private AppleFactory _factory;
public Tree(AppleFactory factory)
{
_factory = factory;
}
public List<Apple> Harvest()
{
return new List<Apple> {
_factory.Create(),
_factory.Create(),
_factory.Create()
};
}
}
// Application
internal class Program
{
static void Main(string[] args)
{
var goodAppleFactory = new AppleFactory(10);
var goodTree = new Tree(goodAppleFactory);
HarvestAndEatAllApples(goodTree);
var badAppleFactory = new AppleFactory(2);
var badTree = new Tree(badAppleFactory);
HarvestAndEatAllApples(badTree);
}
static void HarvestAndEatAllApples(Tree tree)
{
var apples = tree.Harvest();
foreach (Apple apple in apples)
{
apple.Eat();
}
Console.WriteLine();
}
}
}
class NoseGrowingApple : Apple
{
public override void Eat()
{
base.Eat();
Console.WriteLine("Player's nose got 100-times longer.");
}
}
And how can we grow these fruits on a tree? See other half of the answer.
class AppleFactory : IFruitFactory
{
private int _applesDispensed = 0;
private int _appleEnergy;
private bool _dispenseFrnakovnikApples;
public AppleFactory(int appleEnergy, bool dispenseFrnakovnikApples)
{
_appleEnergy = appleEnergy;
_dispenseFrnakovnikApples = dispenseFrnakovnikApples;
}
public IFruit Create()
{
if (_dispenseFrnakovnikApples)
{
return new NoseGrowingApple { Energy = _appleEnergy };
}
else
{
if ((_applesDispensed++) % 2 == 0)
{
return new GoldenApple { Energy = _appleEnergy };
}
else
{
return new Apple { Energy = _appleEnergy };
}
}
}
}
class NoseGrowingAppleFactory : IFruitFactory
{
public IFruit Create() => new NoseGrowingApple { Energy = 7 };
}
There are various ways to do it. If we check AppleFactory, we’ll see that it already produces two different kinds of apples, so we can put the NoseGrowingApple in there as well (
See the code
). But this very much complicates the code and it’s not very extensible. Same approach as we used with Peach would be better, i.e. NoseGrowingAppleFactory (
See the code
).
Let’s take a look at what we have currently:
Apple tree
Peach tree
100% Proboscis tree
Occasional Proboscis tree
Golden apple
Peach
Nose growing apple
Nose growing apple
Apple
Peach
Nose growing apple
Apple
Golden apple
Peach
Nose growing apple
Apple
Apple
Peach
Nose growing apple
Nose growing apple
Golden apple
Peach
Nose growing apple
Apple
Apple
Peach
Nose growing apple
Apple
Do you see any patterns? Any groups of trees that behave similarly?
Both peach tree and 100% proboscis tree always produce the same fruit. On the other hand apple tree and occasional proboscis tree always grow two regular fruit with special fruit appearing periodically. Let’s call these different strategies of dispensing fruit. Let the first one be SingleFruit and second one be PeriodicSpecialFruit. How would we represent these different strategies in C# language? See other part of the answer.
As class SingleFruitDispensingStrategy and class PeriodicSpecialFruitDispensingStrategy.
An instance of an IFruitFactory in our case is an object that is capable of producing fruit. How can say that this factory is to be used by the SingleFruitDispensingStrategy as a configuration of which specific fruit to produce? How to do this in PeriodicSpecialFruitDispensingStrategy? How many inputs does this require? See the last part of the answer.
Also, who should use this strategy? And how?
See the last part of the answer.
First we create an interface for all strategie that can dispense fruits:
interface IFruitDispensingStrategy
{
public IFruit DispenseFruit();
}
Now the implementations that take the configuration via arguments of constructor:
class SingleFruitDispensingStrategy : IFruitDispensingStrategy
{
private IFruitFactory _factory;
public SingleFruitDispensingStrategy(IFruitFactory factory)
{
_factory = factory;
}
public IFruit DispenseFruit()
{
return _factory.Create();
}
}
class PeriodicSpecialFruitDispensingStrategy : IFruitDispensingStrategy
{
private IFruitFactory _regularFruitFactory;
private IFruitFactory _specialFruitFactory;
private int _specialFruitModulo;
private int _fruitsDispensed = 0;
public PeriodicSpecialFruitDispensingStrategy(IFruitFactory regularFruitFactory, IFruitFactory specialFruitFactory, int regularFruitCountPerOneSpecial)
{
if (regularFruitCountPerOneSpecial <= 0)
{
throw new ArgumentOutOfRangeException(nameof(regularFruitCountPerOneSpecial), "Must be greater or equal to 1.");
}
_specialFruitModulo = regularFruitCountPerOneSpecial + 1;
_regularFruitFactory = regularFruitFactory;
_specialFruitFactory = specialFruitFactory;
}
public IFruit DispenseFruit()
{
if ((_fruitsDispensed++) % _specialFruitModulo == 0)
{
return _specialFruitFactory.Create();
}
else
{
return _regularFruitFactory.Create();
}
}
}
And this strategies are passed to an implementation of the tree:
class Tree
{
private IFruitDispensingStrategy _dispensingStrategy;
public Tree(IFruitDispensingStrategy dispensingStrategy)
{
_dispensingStrategy = dispensingStrategy;
}
public List<IFruit> Harvest()
{
return new List<IFruit> {
_dispensingStrategy.DispenseFruit(),
_dispensingStrategy.DispenseFruit(),
_dispensingStrategy.DispenseFruit()
};
}
}
class Apple
{
public int Energy { get; init; }
public virtual void Eat()
{
Console.WriteLine($"Apple eaten, player got {Energy} units of energy.");
}
}
class GoldenApple : Apple
{
public override void Eat()
{
base.Eat();
Console.WriteLine("Golden apple increased player's luck.");
}
}
class AppleFactory
{
private int _applesDispensed = 0;
private int _appleEnergy;
public AppleFactory(int appleEnergy)
{
_appleEnergy = appleEnergy;
}
public Apple Create()
{
if ((_applesDispensed++) % 2 == 0)
{
return new GoldenApple { Energy = _appleEnergy };
}
else
{
return new Apple { Energy = _appleEnergy };
}
}
}
class Peach makes sense. We can implement it however we need. Does it make sense to capture the concept of a fruit in our application as well and explicitly make Apple : Fruit and Peach : Fruit? Think about class Fruit, abstract class Fruit and interface IFruit. Try to come up with pros and cons of different approaches.
Walnuts growing on trees typically can’t be eaten directly, but rather need to be cracked open for it’s kernel — WalnutKernel. Is that a fruit? Can that be eaten?
Is it possible to capture all these requirements (non-edible fruit, edible non-fruit, edible fruit, non-edible non-fruit, non-edible crack-able fruit, …) using abstract classes?
You should realize that implementing wider range of fruits and other entities in the game will be very inconvenient using abstract classes, since some Fruits can be eaten, but others can’t and not every eatable thing grows on trees. The approach using interfaces offers finer options for grouping various classes together. Also recall that property defined in interface isn’t an automatically implemented property with backing field (but rather just a definition of a contract) and also property in interface doesn’t force the class into a specific implementation of such property, but rather allows the implementator to choose (i.e. automatically implemented property, mutable/immutable, property with just getter, etc.)
Here is the implementation
interface IEdible
{
public int Energy { get; }
public void Eat();
}
interface IFruit { }
class Apple : IFruit, IEdible
{
public int Energy { get; init; }
public virtual void Eat()
{
Console.WriteLine($"Apple eaten, player got {Energy} units of energy.");
}
}
class GoldenApple : Apple
{
public override void Eat()
{
base.Eat();
Console.WriteLine("Golden apple increased player's luck.");
}
}
class Peach : IFruit, IEdible
{
public int Energy => 8;
public void Eat()
{
Console.WriteLine($"Peach eaten, player got {Energy} units of energy.");
}
}
Also note that while Apple uses automatically implemented properties to satisfy Energy requirement from the interface, Peach uses get-only property that doesn’t consume any memory at runtime. This would not be possible if the automatically implemented property was forced by the abstract class.
If you’ve seen class Acorn : IFodder for the first time, would you have any idea what does it mean? Maybe interface IFeedable is not exactly correct in English, but it is more verbose and easier to understand for non native english speakers (e.g. colleagues in your future company). And the mutual understanding is what matter! If you need to look up every other word to understand the concepts, it will be very troublesome to follow the code.
Sadly the days of Shakespeare’s English is over and no longer you will see
What devil art thou, that dost torment me thus?
or
O Romeo, Romeo, wherefore art thou Romeo?
Deny thy father and refuse thy name.
Or if thou wilt not, be but sworn my love
And I’ll no longer be a Capulet.
in the documentation.
Surely we could modify AppleFactory to be able to instantiate Peach as well, but we already have kind of complicated code that does multiple things at once. Can we put the implementation elsewhere?
As a standalone PeachFactory!
class PeachFactory
{
public Peach Create() => new Peach();
}
The problem here is Tree can only work with AppleFactory. How can we make it work with PeachFactory as well? See other half of the answer.
Make Tree accept any IFruitFactory interface and have AppleFactory and PeachFactory implement this interface. Before checking the code below, think about the signature of the method for creating the fruit (it’s name, arguments and return type).
class AppleFactory : IFruitFactory
{
private int _applesDispensed = 0;
private int _appleEnergy;
public AppleFactory(int appleEnergy)
{
_appleEnergy = appleEnergy;
}
public IFruit Create()
{
if ((_applesDispensed++) % 2 == 0)
{
return new GoldenApple { Energy = _appleEnergy };
}
else
{
return new Apple { Energy = _appleEnergy };
}
}
}
class PeachFactory : IFruitFactory
{
public IFruit Create() => new Peach();
}
interface IFruitFactory
{
public IFruit Create();
}
class Tree
{
private IFruitFactory _factory;
public Tree(IFruitFactory factory)
{
_factory = factory;
}
public List<IFruit> Harvest()
{
return new List<IFruit> {
_factory.Create(),
_factory.Create(),
_factory.Create()
};
}
}