DEV Community

Cover image for OOP - Abstraction - Part 2
Sivakumar Natarajan
Sivakumar Natarajan

Posted on • Edited on

OOP - Abstraction - Part 2

What ?

We have already looked at Part1 of the definition here. Now let's dive deep into the part 2.

Part 2: Abstraction is the process of hiding implementation details from users, enabling them to focus on the functionality rather than the internal workings.

note: Users in the definition points to developers.

Why (hide implementation details) ?

Scenario: A new service importing an already existing reusable authentication setup.

A usual setup as part of the authentication process is to hash user passwords before comparing them with stored credentials. However, for a user of this reusable Authentication class, it suffices for them to know if a given credential is valid or not; it's not necessary for them to know about this particular hashing logic, as it's an implementation detail.

In the above scenario, by hiding the implementation detail we gained benefits like,

  • Controlled access (users can't access the hashing setup)
  • Focus on the WHAT rather than the HOW
  • Reduced complexity/cognitive load
  • Reduced maintenance efforts - Because the hashing process is not open for users, if there is a need to change the hashing algorithm it's simple to refactor as no one else is using it.

How

There are several ways to hide implementation detail to achieve abstraction. However, let's focus on using the abstract keyword to maintain consistency with the examples from Part 1 and ensure the explanation remains intuitive.

Scenario:

Designing a coffee machine capable of making a variety of coffee flavours

Ofcourse, there are several ways to achieve this, and that's the beauty of software programming. However, one effective way to implement such a coffee machine is by using abstraction — hiding the implementation details and allowing the user or programmer to focus on selecting the desired coffee flavour.

Solution

The below code snippet uses partial abstract class that's explained in detail on part 1 of this blog post.

type CupSize = "small" | "medium" | "large";

type CoffeeTemperature = "hot" | "warm" | "ice";

type Milk = "almond" | "semi-skimmed" | "full-fat" | "skimmed";

// Partial abstract class with all implementation details hidden
abstract class CoffeeMachine {
  private cupSize: CupSize;
  private coffeeTemperature: CoffeeTemperature;
  private milk: Milk | undefined;
  constructor(
    cupSize: CupSize,
    coffeeTemperature: CoffeeTemperature,
    milk?: Milk
  ) {
    this.cupSize = cupSize;
    this.coffeeTemperature = coffeeTemperature;
    this.milk = milk;
  }
  private setCoffeeCupSize() {
    console.log(`set Coffee cup size to - ${this.cupSize}`);
  }

  private heatWater() {
    console.log(`heating water to default temperature`);
  }

  private addMilk() {
    this.milk
      ? console.log(`milk addition - ${this.milk}`)
      : console.log(`no milk added`);
  }

  private setCoffeeTemperature() {
    console.log(
      `heating coffee to provided temperature - ${this.coffeeTemperature}`
    );
  }

  // the only abstract method
  protected abstract addExtras(): void;

  public makeCoffee() {
    this.setCoffeeCupSize();
    this.heatWater();
    this.addMilk();
    this.setCoffeeTemperature();
    this.addExtras();
  }
}
Enter fullscreen mode Exit fullscreen mode

Introducing Cappuccino flavour:

Now it's straight forward to introduce any new flavour as we have an abstract class with all implementation details that is hidden away from the developer extending the machine to one more flavour.

// Subclass inheriting concrete methods from CoffeeMachine but als extends an abstract method
class Cappuccino extends CoffeeMachine {
  constructor(
    cupSize: CupSize,
    temperaturemilk: CoffeeTemperature,
    milk?: Milk
  ) {
    super(cupSize, temperaturemilk, milk);
  }

  protected addExtras(): void {
    console.log(`adding Cappuccino coffee pod`);
  }
}

Enter fullscreen mode Exit fullscreen mode

Making the coffee

const coffee = new Cappuccino("large", "hot", "almond");
// makeCoffee on Cappuccino object works here because its inherited from CoffeeMachine
coffee.makeCoffee();
Enter fullscreen mode Exit fullscreen mode

Conclusion

In summary, abstraction is a cornerstone of object-oriented programming that simplifies complex systems by hiding unnecessary details and exposing only the essential features. By defining clear blueprints or contracts, abstraction promotes code clarity, consistency, and extensibility. It allows developers to focus on higher-level design while ensuring the underlying implementation remains flexible and reusable.

Through the examples provided, we’ve seen how abstraction helps streamline workflows, encourages adherence to design principles, and makes systems easier to maintain and scale. Whether using abstract classes, interfaces, or encapsulated methods, abstraction is a powerful tool for building robust and efficient software systems.

Top comments (0)