Abstraction and Encapsulation

 There are 2 main paradigms for programming languages now: imperative and object oriented. While these are vastly different paradigms, they do the exact same thing: changing and moving around some numbers. Your "Player" object doesn't really exists. Its numeric representation is just somewhere in the RAM and you just can only access it if you have to reference(pointer) to that object. But humans don't think this way. Real world doesn't work this way. So we use abstraction to make somewhat believable object that represents real world things. And this is the core concept of object oriented languages(imperative languages also have abstraction, but it's not their core concept).

Abstraction is reducing real things down to concepts. Real things are complex. They are made of atoms(and even more small particles if you go further) and even the small things you can see in daily life consist of billions and trillions of atoms. But computers can't represent that. They only understand numbers. So we do what we must and abstract those atoms into some numbers.

If you were to make a sword in a game, what would it need? Most people would think graphic, damage, animation, hit sound or even durability. But real swords don't have any of that. You can't "deal 10 damage" with your sword in real life. It just cuts things. The damage that we gave to the sword is abstraction. We see that when a real sword is used to attack things, they take some form of physical damage. And each sword swing does roughly about the same damage. If we extend this little further, various things can take various amount of damage until it loses its core features. So we make weapons "deal damage" and make enemies "have health" and "die" in games. So weapon's code should look something like this:

public class Weapon{
    public float damage;
    public void Update(){
        Enemy enemy = // detect collision
        if(enemy != null){
            enemy.health -= damage;
            if(enemy.health <= 0){
                enemy.Die();
            }
        }
    }
}

And it works fine. Player can damage enemies with the weapon. And this could be the only thing needed for a small game.

But say, you want to add an enemy with defense. Now things become complex. You have to find every line that says enemy.health -= damage; or similar and change all of them into something like this:

enemy.health -= damage;
// change above to
damage -= enemy.defense;
enemy.health -= damage;

And you might miss some and create a very hard to find bug. After the agony of fixing those bugs, you also find out some enemies need special interaction when they get damaged. There's got to be a easier way to do this. And there is. It's encapsulation.

Encapsulation is a subtype of abstraction. All encapsulations are abstraction, but not all abstractions are encapsulation. Let's see the new weapon code with encapsulation

public class Weapon{
    private float damage;
    public void OnCollision(Enemy enemy){
        enemy.Damage(damage);
    }
}

Firstly, the damage field is now private. In real world, Other things can't really tell if a sword is going to do how much damage. The damage field is just a abstraction that nothing other than the sword itself should know. This is called implementation detail.
Secondly, rather than updating the weapon every frame, we added OnCollision function so that the weapon has less responsibility. Real swords don't "check collision". They just cut things that they collide with. The universe checks collision and not the sword. So like the universe, physics engine checks collision and "tells" the weapon to do something.
Lastly, the damage process is simply replaced by a function call. Real swords don't care if it's hitting a soft balloon or a hard robot. So the weapon just "tells" the enemy that it should take damage, preferably this amount.
Now if we want to add defense, on damage effect etc. we can do so in the Enemy's Damage function. This contains potential bugs only to that function and any bugs will be way easier to find.


I've seen so many "tutorials" that calls making boilerplate for setters and getters "encapsulation". While it's better than just directly manipulating the variable, setters and getters still expose implementation details, making it very hard to change things without breaking something.
Good objects should be a very complex black box. If you click your mouse, the button's spring is compressed and its 2 metal parts meet, conducting electricity. Then the mouse forwards this to the computer and the OS interprets this as a mouse click and tells the current program to act accordingly. But your mouse could be a laser switch(checks if the constant laser beam is blocked rather than if the metal parts meet), your mouse's button could be remapped, the program might just ignore the click because you clicked on a blank spot. But each part of this interaction doesn't care about that. For the OS, the mouse click is mouse click no matter how that signal was made. And it doesn't care if the program doesn't do anything about the input. Each part is a complete black box that only shares notifications with some information about what it wants.
"Getting" the enemy health and "setting" enemy health by "encapsulation" would be like OS trying to detect mouse click by directly asking if the mouse's physical left button's metal components are meeting. It would work, but it will break as soon as something changes and the whole mouse code will be a mess. The OS should just ask the driver if the expected mouse's left button equivalent is pressed.

Comments

Popular posts from this blog

Uncomfortable Truth about Coding Tutorial Videos

The Humble Beginning