Try   HackMD

Basic Concepts of OOP

fcc-eli5

Encapsulation

Say we have a program. It has a few logically different objects which communicate with each other — according to the rules defined in the program.

Encapsulation is achieved when each object keeps its state private, inside a class. Other objects don’t have direct access to this state. Instead, they can only call a list of public functions — called methods.

So, the object manages its own state via methods — and no other class can touch it unless explicitly allowed. If you want to communicate with the object, you should use the methods provided. But (by default), you can’t change the state.

Let’s say we’re building a tiny Sims game. There are people and there is a cat. They communicate with each other. We want to apply encapsulation, so we encapsulate all “cat” logic into a Cat class. It may look like this:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Here the “state” of the cat is the private variables mood, hungry and energy. It also has a private method meow(). It can call it whenever it wants, the other classes can’t tell the cat when to meow.

What they can do is defined in the public methods sleep(), play() and feed(). Each of them modifies the internal state somehow and may invoke meow(). Thus, the binding between the private state and public methods is made.

This is encapsulation.

you can also use the setter method to implement additional validation rules to ensure that your object always has a valid state.

stackify-encapsulation

Abstraction

In object-oriented design, programs are often extremely large. And separate objects communicate with each other a lot. So maintaining a large codebase like this for years — with changes along the way — is difficult.

Abstraction is a concept aiming to ease this problem.

Applying abstraction means that each object should only expose a high-level mechanism for using it. This mechanism should hide internal implementation details. It should only reveal operations relevant for the other objects.

Think — a coffee machine. It does a lot of stuff and makes quirky noises under the hood. But all you have to do is put in coffee and press a button.

Preferably, this mechanism should be easy to use and should rarely change over time. Think of it as a small set of public methods which any other class can call without “knowing” how they work.

Another real-life example of abstraction?
Think about how you use your phone:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

You interact with your phone by using only a few buttons. What’s going on under the hood? You don’t have to know — implementation details are hidden. You only need to know a short set of actions.

Implementation changes — for example, a software update — rarely affect the abstraction you use.

example with a coffee machine

public class CoffeeApp {
    public static void main(String[] args) {
        // create a Map of available coffee beans
        Map<CoffeeSelection, CoffeeBean> beans = new HashMap<CoffeeSelection, CoffeeBean>();
        beans.put(CoffeeSelection.ESPRESSO, 
            new CoffeeBean("My favorite espresso bean", 1000));
        beans.put(CoffeeSelection.FILTER_COFFEE, 
            new CoffeeBean("My favorite filter coffee bean", 1000));

        // get a new CoffeeMachine object
        CoffeeMachine machine = new CoffeeMachine(beans);

        // brew a fresh coffee
        try {
	    Coffee espresso = machine.brewCoffee(CoffeeSelection.ESPRESSO);
	} catch(CoffeeException  e) {
	    e.printStackTrace();
        }
    } // end main
} // end CoffeeApp

The classes Grinder and BrewingUnit provide abstractions on their own. The Grinder abstracts the complexity of grinding the coffee and BrewingUnit hides the details of the brewing process.

public class Grinder {
    public GroundCoffee grind(CoffeeBean coffeeBean, double quantityCoffee) { 
        // ... 
    }
}
public class BrewingUnit {
    public Coffee brew(CoffeeSelection selection, GroundCoffee groundCoffee, double quantity) {
        // ... 
    }
}

That makes the implementation of the CoffeeMachine class a lot easier. You can implement the brewCoffee method without knowing any details about the grinding or brewing process. You just need to know how to instantiate the 2 classes and call the grind and brew methods.

Methods like this can be private too, in this way another internal level of abstraction could be achieved.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

the constructor not only stores the provided Map of available CoffeeBeans in an internal property, it also initializes an internal Map that stores the configuration required to brew the different kinds of coffees and instantiates a Grinder and a BrewingUnit object.

All these steps are not visible to the caller of the constructor method. The developer most likely doesn’t even know that the Grinder or BrewingUnit class exists. That’s another example of the abstraction that the CoffeeMachine class provides.

cs50-abstraction
stackify-abstraction

Inheritance

You create a (child) class by deriving from another (parent) class. This way, we form a hierarchy.

The child class reuses all fields and methods of the parent class (common part) and can implement its own (unique part).

For example:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

A private teacher is a type of teacher. And any teacher is a type of Person.
If our program needs to manage public and private teachers, but also other types of people like students, we can implement this class hierarchy.

This way, each class adds only what is necessary for it while reusing common logic with the parent classes.

stackify-inheritance

Polymorphism

Polymorphism gives a way to use a class exactly like its parent so there’s no confusion with mixing types. But each child class keeps its own methods as they are.
This typically happens by defining a (parent) interface to be reused. It outlines a bunch of common methods. Then, each child class implements its own version of these methods.

For example, in the implementation of geometric figures below, a common interface is reused to calculate surface and perimeter

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Having these three figures inheriting the parent Figure Interface lets you create a list of mixed triangles, circles, and rectangles. And treat them like the same type of object.
Then, if this list attempts to calculate the surface for an element, the correct method is found and executed. If the element is a triangle, triangle’s CalculateSurface() is called. If it’s a circle — then cirlce’s CalculateSurface() is called. And so on.