Intro to Programming Systems Design
By: John Chang
Tags: Programming, Beginner, Systems Design

Intro to Programming Systems Design

Basic Systems & Hierarchies

As an example, Cows and Birds are Animals. Parrot is a type of bird.

The categorization decision here is possibly trivial, however, how do we decide on the following?

Let’s say the customer wants Goblins and Elves to be derived from Humans , Elves derived from Goblins . All of them are also Entity at the same time.

On paper, it sounds fine, but let’s say we wanted to know if an Elf is a Goblin .

Elf will ask Entity if it’s a goblin, it’ll return False

Elf.Entity.is_goblin == False

Elf will ask Goblin if it’s a goblin, it’ll return True

Elf.Goblin.is_goblin == False

Elf will ask Human if it’s a goblin, it doesn’t know.

So it asks Entity, it’ll then return False

Elf.Human.Entity.is_goblin == False

We get different results here. While it is possible to fix this by just adding an algorithm within Elf , ideally, we want to make it as simple as possible from the get-go.

Repeating Dependencies

As shown above, if we don’t think through the links, we run into problems.

As a software designer, try to keep it as simple as possible. As above, if we called is_goblin , we would’ve stopped at Goblin and returned “True”.

The rule of thumb is to avoid repeated dependencies unless you’re very certain.

Spotting Repeated Dependencies

In (1), we can find the dependency of Goblin as

Goblin. Entity

Goblin.Human.Entity

We can see that it’s repeated because both dependencies have Goblin as the start point and Entity as the end point. This is simple to resolve using either (2), (3) or (4).

I would recommend thinking of this like another puzzle game to solve, where you just have to meet the only one criterion.

A.B.C… D

A.E.F…D

Regardless of how many nodes you travel through, if your initial point (A) and destination (D) are the same for different dependencies, you’re going to run into problems.

As long as you keep the dependencies clean and well thought out, you won’t run into any problems.

However, what if you really wanted to break the rules?

Mutual Dependencies

Werewolves can’t exist because of circular dependencies

Generally, most programming languages won’t allow you to do this easily. This is because they will try to compile Human , notices it depends on Werewolf , notices it depends on Human then gives up.

If you start with compiling Werewolf instead, you’ll get the same issue, so who’s dependending on who?

Do you actually need it?

From my 1,000 hour StackOverflow experience, 90% of the time, you can resolve it without forcing the mutual dependency.

Implementation Classes

We can split all the things in Human and Werewolf into separate files.

_HumanImpl and _WerewolfImpl are implementation classes, quite common in practice.

Let’s say we also want both Werewolf and Human as characters you can play, we can do the following

Take note that this doesn’t have any multiple dependencies even though it may look like it.

HumanWerewolf. Playable

HumanWerewolf. _HumanImpl

HumanWerewolf._WerewolfImpl

More Examples

Getting a visual on what’s happening is usually good to resolve these problems.

Okay, so we want an NPC, Damien, Half-Human, Half-Spider, Humongous and also likes carrots.

Damien.Human. Entity

Damien.Spider. Entity

Damien.Huge.EntityHuge. Entity

Damien.Likes_Carrots.EntityLikeCarrot.Entity

The obvious problem is that every single attribute is going to start from Damien and end at Entity, and that’s no good.

We have to inherit from Human and Spider , so the obvious solution is to use implementation files. However, do we really need EntityHuge and EntityLikeCarrot as separate classes?

Let’s take the obvious one first

Damien. Entity

Damien. _HumanImpl

Damien._SpiderImpl

Attributes

Taking a slight tangent. Notice that we never really defined some of these classes in depth, almost all miniscule metadata are hidden within them.

Take for example, Entity, it will have attributes defined. Sometimes they have default values (e.g. size=0 ) to go with.

Let’s say we we create Human from this.

Here, we can see that we override some attributes by just dropping and replacing the values. This way of creation is easy because we don’t have to repeatedly define the Size , Color ,… All of the attributes come from Entity like a template.

If we were to rename Color to Colour , most code editors can automatically detect the connections and rename them properly!

We can also utilize the default value Health=100 provided in Entity to initialize Health , in case we want that property to be always reliant on Entity .

We can also introduce a new attribute on Human . This makes it so you don’t have to have a separate _HumanAttack Class.

Resolving Damien

Finally, we come back to the previous example. We understood how to use Implementation Classes for some classes, now we just add the rest of the information in attributes.

Here, we assume Entity has defined Size but not Like .

Hence, we just override Size and add our own attribute Like with the default value as "Carrot" .

Conclusion?

There’s definitely many more system design principles you can follow, this is one of my fundamental ones to fallback to most of the time.

I would recommend thinking of this like another puzzle game to solve, where you just have to meet the only one criterion.

A.B.C… D

A.E.F…D

Regardless of how many nodes you travel through, if your initial point and destinations are the same, you’re going to run into problems.

That’s all, remember to think out of the box!