What ?
Definition 1: Abstraction is the process of hiding implementation details from users, enabling them to focus on the functionality rather than the internal workings.
Eg: A commonly used analogy is the TV remote control: users interact with buttons to operate the TV without needing to know how the internal mechanics work.
Definition 2: Abstraction provides a blueprint or plan for designing classes with a defined set of rules. It ensures consistency and enforces that certain behaviors are implemented across derived classes.
I find Definition 2 to be more intuitive and relatable and this post explains in detail from that standpoint.
Why ?
To understand the need for abstraction, let’s first explore a problem.
Problem:
Imagine creating classes to draw pictures of animals. Each animal has its own unique features, but certain traits—such as having four legs and a tail—are shared among them. Without a structured approach, developers might forget to include these common traits, leading to incomplete or inaccurate depictions.
class DogPicture {
drawATail() {}
drawFourLegs() {}
drawFloppyEars() {}
}
class CatPicture {
drawATail() {}
drawFourLegs() {}
drawConeShapedEars() {}
}
class HorsePicture {
drawATail() {}
drawFourLegs() {}
drawManeHair() {}
}
class GoatPicture {
drawATail() {}
drawFourLegs() {}
drawHorns() {}
}
class ZebraPicture {
drawATail() {}
drawFourLegs() {}
drawStripesOnBody() {}
}
Solution:
Abstraction can solve this problem by defining a shared plan or rules that every animal picture class must follow. This can be achieved using an abstract class or interface in TypeScript.
abstract class AnimalPicture {
abstract drawFourLegs(): void;
abstract drawATail(): void;
}
Now, any class that implements AnimalPicture must define these methods. Failing to do so will result in an error.
Incorrect implementation
class DogPicture implements AnimalPicture { // this will throw error as `drawFourLegs` & `drawATail` are not implemented.
drawFloppyEars() {}
}
Error:
Class 'DogPicture' incorrectly implements class 'AnimalPicture'. Did you mean to extend 'AnimalPicture' and inherit its members as a subclass?
Type 'DogPicture' is missing the following properties from type 'AnimalPicture': drawFourLegs, drawATailts(2720)
Correct implementation
class DogPicture implements AnimalPicture {
drawFourLegs(): void {
console.log("Drawing four legs for a dog.");
}
drawATail(): void {
console.log("Drawing four legs for a dog.");
}
drawFloppyEars() {}
}
class CatPicture implements AnimalPicture {
drawFourLegs(): void {
console.log("Drawing four legs for a cat.");
}
drawATail(): void {
console.log("Drawing four legs for a cat.");
}
drawConeShapedEars() {}
}
class HorsePicture implements AnimalPicture {
drawFourLegs(): void {
console.log("Drawing four legs for a horse.");
}
drawATail(): void {
console.log("Drawing four legs for a horse.");
}
drawManeHair() {}
}
class GoatPicture implements AnimalPicture {
drawFourLegs(): void {
console.log("Drawing four legs for a goat.");
}
drawATail(): void {
console.log("Drawing four legs for a goat.");
}
drawHorns() {}
}
class ZebraPicture implements AnimalPicture {
drawFourLegs(): void {
console.log("Drawing four legs for a zebra.");
}
drawATail(): void {
console.log("Drawing four legs for a zebra.");
}
drawStripesOnBody() {}
}
This ensures that all animal picture classes implement the shared rules (drawFourLegs and drawATail), while allowing flexibility for unique features like floppy ears or stripes.
Conclusion
Abstraction in OOP helps enforce a consistent structure across related classes while allowing flexibility for unique implementations. It ensures that essential behaviors are not overlooked, making code more predictable and maintainable.
SOCIAL SHARE CARD GENERATOR