Welcome back! Previously, you explored polymorphism and how it empowers you to create flexible code structures using inheritance. In this session, we will take a step further into a crucial aspect of object-oriented programming: Abstraction.
While JavaScript does not natively support abstract classes like some other programming languages, modern JavaScript provides mechanisms to simulate this behavior. You can create a structure that enforces method implementations in derived classes, thereby achieving abstraction.
To simulate abstract classes in JavaScript, we can utilize ES6 class syntax and create methods that throw errors when not overridden:
JavaScript1// Create a base class Shape that simulates an abstract class 2class Shape { 3 constructor(color) { 4 this.color = color; 5 } 6 7 // Simulate abstract methods by throwing an error if they are not implemented 8 area() { 9 throw new Error('Method "area()" must be implemented'); 10 } 11 12 perimeter() { 13 throw new Error('Method "perimeter()" must be implemented'); 14 } 15 16 // Concrete method to get the color 17 getColor() { 18 return this.color; 19 } 20}
In this JavaScript class Shape
, we define a constructor and a method getColor
. The area
and perimeter
functions throw errors if not overridden, simulating the behavior of abstract methods. This approach enforces that any subclass must provide implementations for these methods.
Now, let’s create concrete classes that extend the base class Shape
:
JavaScript1// Define a Circle class that extends Shape 2class Circle extends Shape { 3 constructor(radius, color) { 4 super(color); 5 this.radius = radius; 6 } 7 8 // Implement the area and perimeter methods 9 area() { 10 return Math.PI * this.radius * this.radius; 11 } 12 13 perimeter() { 14 return 2 * Math.PI * this.radius; 15 } 16}
The Circle
class inherits from Shape
and implements the area
and perimeter
methods. We also provide a constructor to initialize the circle's radius and color, passing the color to the parent class constructor using super
.
JavaScript1// Define a Rectangle class that extends Shape 2class Rectangle extends Shape { 3 constructor(width, height, color) { 4 super(color); 5 this.width = width; 6 this.height = height; 7 } 8 9 // Implement the area and perimeter methods 10 area() { 11 return this.width * this.height; 12 } 13 14 perimeter() { 15 return 2 * (this.width + this.height); 16 } 17}
Similarly, the Rectangle
class extends Shape
, implementing the area
and perimeter
methods and initializing attributes through its constructor.
Let's demonstrate how you can utilize these classes:
JavaScript1// Example usage of the Circle and Rectangle classes 2const circle = new Circle(5, "Red"); 3const rectangle = new Rectangle(4, 6, "Blue"); 4 5console.log(`Circle Area: ${circle.area()}, Perimeter: ${circle.perimeter()}, Color: ${circle.getColor()}`); 6console.log(`Rectangle Area: ${rectangle.area()}, Perimeter: ${rectangle.perimeter()}, Color: ${rectangle.getColor()}`); 7 8// Using a Shape reference to a Circle object 9const shape = new Circle(3, "Green"); 10console.log(`Shape Area: ${shape.area()}, Perimeter: ${shape.perimeter()}, Color: ${shape.getColor()}`); 11 12// Uncommenting the following line will cause an error as 'Shape' class should not be instantiated directly 13// const invalidShape = new Shape("Yellow");
By running this code, you can see how the Circle
and Rectangle
classes implement the area
and perimeter
methods instead of throwing an error, which would happen if these methods weren't implemented. This demonstrates both inheritance and polymorphism in JavaScript, where a Shape
reference can access a Circle
object.
Simulating abstraction in JavaScript enables you to enforce patterns and rules within your codebase. It ensures that derived classes implement critical functionality, maintaining uniformity across implementations.
By mastering these techniques:
- Create more organized and readable code: Clear structures enforce method behaviors.
- Encourage code reusability: Common functionality resides in base classes, preventing redundant code.
- Enhance flexibility: Easily extend functionality without altering existing code, adhering to fundamental software design principles.
Intrigued? Let's move on to the practice section and solidify these concepts together. You're on your way to becoming proficient in building sophisticated and maintainable systems with JavaScript!