Menu iconMenu iconJavaScript from Zero to Superhero
JavaScript from Zero to Superhero

Chapter 6: Object-Oriented JavaScript

6.5 Practical Exercises

This set of exercises is designed to reinforce the concepts discussed in Chapter 6, focusing on object-oriented programming principles such as encapsulation, abstraction, inheritance, and the use of ES6 classes in JavaScript. Each exercise includes a challenge and a solution to help you apply what you've learned in practical coding scenarios.

Exercise 1: Implementing a Class with Encapsulation

Create a Person class that encapsulates an individual's name and age and provides methods to get and set each attribute. Ensure that the age cannot be set to a negative number.

Solution:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getName() {
        return this.name;
    }

    setName(name) {
        this.name = name;
    }

    getAge() {
        return this.age;
    }

    setAge(age) {
        if (age < 0) {
            throw new Error("Age cannot be negative");
        }
        this.age = age;
    }
}

const person = new Person("John", 30);
console.log(person.getName()); // Outputs: John
console.log(person.getAge()); // Outputs: 30

person.setAge(25);
console.log(person.getAge()); // Outputs: 25

// Attempting to set a negative age
try {
    person.setAge(-5);
} catch (e) {
    console.log(e.message); // Outputs: Age cannot be negative
}

Exercise 2: Implementing Inheritance and Method Overriding

Extend the Person class to create a new class called Employee that includes everything from Person and adds employeeId and a method to display all details of the employee.

Solution:

class Employee extends Person {
    constructor(name, age, employeeId) {
        super(name, age); // Calls the constructor of the base class
        this.employeeId = employeeId;
    }

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Employee ID: ${this.employeeId}`);
    }
}

const employee = new Employee("Alice", 28, "E12345");
employee.display(); // Outputs: Name: Alice, Age: 28, Employee ID: E12345

Exercise 3: Using Static Methods

Create a class Calculator with static methods for basic arithmetic operations (add, subtract, multiply, divide) that take two numbers and return the result.

Solution:

class Calculator {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }

    static multiply(a, b) {
        return a * b;
    }

    static divide(a, b) {
        if (b === 0) {
            throw new Error("Division by zero");
        }
        return a / b;
    }
}

console.log(Calculator.add(10, 5)); // Outputs: 15
console.log(Calculator.subtract(10, 5)); // Outputs: 5
console.log(Calculator.multiply(10, 5)); // Outputs: 50
console.log(Calculator.divide(10, 5)); // Outputs: 2

Exercise 4: Abstraction with Proxies

Create a proxy for a settings object that validates changes to properties. Specifically, ensure that volume is between 0 and 100.

Solution:

let settings = {
    volume: 30
};

let settingsProxy = new Proxy(settings, {
    set(target, prop, value) {
        if (prop === 'volume') {
            if (value < 0 || value > 100) {
                throw new Error("Volume must be between 0 and 100");
            }
        }
        target[prop] = value;
        return true;
    }
});

settingsProxy.volume = 90; // Works fine
console.log(settings.volume); // Outputs: 90

// Attempting to set volume out of range
try {
    settingsProxy.volume = 101;
} catch (e) {
    console.log(e.message); // Outputs: Volume must be between 0 and 100
}

These exercises provide practical applications of the object-oriented programming features discussed in Chapter 6, helping you solidify your understanding of these concepts through coding challenges that reflect real-world scenarios.

Chapter Summary

In Chapter 6, we embarked on an exploratory journey through the principles of object-oriented programming (OOP) as implemented in JavaScript. This chapter aimed to demystify the concepts of encapsulation, abstraction, inheritance, and polymorphism, which are pivotal for developing scalable and maintainable applications. By diving into these core principles, we've equipped you with the knowledge to leverage JavaScript's capabilities to create robust and efficient web applications.

Encapsulation and Abstraction

We began by addressing encapsulation, a fundamental OOP concept that involves bundling the data (variables) and the methods (functions) that act on the data into single units called classes. Encapsulation protects an object's internal state from unwanted external interference and misuse, which enhances data integrity and security. We saw how JavaScript uses function scopes and closures to maintain private states within objects, a practice crucial in preserving the integrity and security of applications.

Abstraction was another focus area, simplifying complex systems by hiding the irrelevant details from the users and exposing only the necessary parts of the objects. This helps in reducing programming complexity and increasing efficiency. JavaScript's support for abstraction comes through its ability to create objects that expose only selected attributes and methods to the outside world, allowing developers to change and refactor an object's internal workings without altering how other code interacts with it.

Inheritance and Polymorphism

Inheritance in JavaScript, traditionally achieved through prototypes and more recently through class syntax introduced in ES6, allows objects to inherit properties and methods from other objects. We explored how to create class hierarchies that reflect real-world relationships, enabling you to write less code while increasing functionality. By using the extends keyword for class inheritance and the super constructor for initializing the parent's constructor, JavaScript simplifies the prototype chain management, making it more accessible to developers familiar with traditional OOP languages.

Polymorphism was discussed as a mechanism allowing objects to be treated as instances of their parent class, with the ability to override methods to perform different functionalities. This aspect of OOP in JavaScript enables you to call the same method on different objects, each responding in a way appropriate to the object's type, thus enhancing code flexibility and reusability.

Practical Applications and Best Practices

Throughout the chapter, practical examples demonstrated how to implement these OOP principles in real-world scenarios. From creating classes and managing inheritance trees to applying encapsulation and abstraction for better data management and system design, the exercises provided a hands-on approach to solidifying your understanding of these concepts.

Conclusion

This chapter has laid a solid foundation for understanding and applying object-oriented principles in JavaScript. By mastering these concepts, you are now better equipped to tackle complex development challenges, create highly reusable code, and design your applications with better structure and maintainability. As you continue to delve deeper into JavaScript and its object-oriented features, remember that these principles are not just theoretical but are essential tools that can significantly enhance the functionality and quality of your software projects.

6.5 Practical Exercises

This set of exercises is designed to reinforce the concepts discussed in Chapter 6, focusing on object-oriented programming principles such as encapsulation, abstraction, inheritance, and the use of ES6 classes in JavaScript. Each exercise includes a challenge and a solution to help you apply what you've learned in practical coding scenarios.

Exercise 1: Implementing a Class with Encapsulation

Create a Person class that encapsulates an individual's name and age and provides methods to get and set each attribute. Ensure that the age cannot be set to a negative number.

Solution:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getName() {
        return this.name;
    }

    setName(name) {
        this.name = name;
    }

    getAge() {
        return this.age;
    }

    setAge(age) {
        if (age < 0) {
            throw new Error("Age cannot be negative");
        }
        this.age = age;
    }
}

const person = new Person("John", 30);
console.log(person.getName()); // Outputs: John
console.log(person.getAge()); // Outputs: 30

person.setAge(25);
console.log(person.getAge()); // Outputs: 25

// Attempting to set a negative age
try {
    person.setAge(-5);
} catch (e) {
    console.log(e.message); // Outputs: Age cannot be negative
}

Exercise 2: Implementing Inheritance and Method Overriding

Extend the Person class to create a new class called Employee that includes everything from Person and adds employeeId and a method to display all details of the employee.

Solution:

class Employee extends Person {
    constructor(name, age, employeeId) {
        super(name, age); // Calls the constructor of the base class
        this.employeeId = employeeId;
    }

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Employee ID: ${this.employeeId}`);
    }
}

const employee = new Employee("Alice", 28, "E12345");
employee.display(); // Outputs: Name: Alice, Age: 28, Employee ID: E12345

Exercise 3: Using Static Methods

Create a class Calculator with static methods for basic arithmetic operations (add, subtract, multiply, divide) that take two numbers and return the result.

Solution:

class Calculator {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }

    static multiply(a, b) {
        return a * b;
    }

    static divide(a, b) {
        if (b === 0) {
            throw new Error("Division by zero");
        }
        return a / b;
    }
}

console.log(Calculator.add(10, 5)); // Outputs: 15
console.log(Calculator.subtract(10, 5)); // Outputs: 5
console.log(Calculator.multiply(10, 5)); // Outputs: 50
console.log(Calculator.divide(10, 5)); // Outputs: 2

Exercise 4: Abstraction with Proxies

Create a proxy for a settings object that validates changes to properties. Specifically, ensure that volume is between 0 and 100.

Solution:

let settings = {
    volume: 30
};

let settingsProxy = new Proxy(settings, {
    set(target, prop, value) {
        if (prop === 'volume') {
            if (value < 0 || value > 100) {
                throw new Error("Volume must be between 0 and 100");
            }
        }
        target[prop] = value;
        return true;
    }
});

settingsProxy.volume = 90; // Works fine
console.log(settings.volume); // Outputs: 90

// Attempting to set volume out of range
try {
    settingsProxy.volume = 101;
} catch (e) {
    console.log(e.message); // Outputs: Volume must be between 0 and 100
}

These exercises provide practical applications of the object-oriented programming features discussed in Chapter 6, helping you solidify your understanding of these concepts through coding challenges that reflect real-world scenarios.

Chapter Summary

In Chapter 6, we embarked on an exploratory journey through the principles of object-oriented programming (OOP) as implemented in JavaScript. This chapter aimed to demystify the concepts of encapsulation, abstraction, inheritance, and polymorphism, which are pivotal for developing scalable and maintainable applications. By diving into these core principles, we've equipped you with the knowledge to leverage JavaScript's capabilities to create robust and efficient web applications.

Encapsulation and Abstraction

We began by addressing encapsulation, a fundamental OOP concept that involves bundling the data (variables) and the methods (functions) that act on the data into single units called classes. Encapsulation protects an object's internal state from unwanted external interference and misuse, which enhances data integrity and security. We saw how JavaScript uses function scopes and closures to maintain private states within objects, a practice crucial in preserving the integrity and security of applications.

Abstraction was another focus area, simplifying complex systems by hiding the irrelevant details from the users and exposing only the necessary parts of the objects. This helps in reducing programming complexity and increasing efficiency. JavaScript's support for abstraction comes through its ability to create objects that expose only selected attributes and methods to the outside world, allowing developers to change and refactor an object's internal workings without altering how other code interacts with it.

Inheritance and Polymorphism

Inheritance in JavaScript, traditionally achieved through prototypes and more recently through class syntax introduced in ES6, allows objects to inherit properties and methods from other objects. We explored how to create class hierarchies that reflect real-world relationships, enabling you to write less code while increasing functionality. By using the extends keyword for class inheritance and the super constructor for initializing the parent's constructor, JavaScript simplifies the prototype chain management, making it more accessible to developers familiar with traditional OOP languages.

Polymorphism was discussed as a mechanism allowing objects to be treated as instances of their parent class, with the ability to override methods to perform different functionalities. This aspect of OOP in JavaScript enables you to call the same method on different objects, each responding in a way appropriate to the object's type, thus enhancing code flexibility and reusability.

Practical Applications and Best Practices

Throughout the chapter, practical examples demonstrated how to implement these OOP principles in real-world scenarios. From creating classes and managing inheritance trees to applying encapsulation and abstraction for better data management and system design, the exercises provided a hands-on approach to solidifying your understanding of these concepts.

Conclusion

This chapter has laid a solid foundation for understanding and applying object-oriented principles in JavaScript. By mastering these concepts, you are now better equipped to tackle complex development challenges, create highly reusable code, and design your applications with better structure and maintainability. As you continue to delve deeper into JavaScript and its object-oriented features, remember that these principles are not just theoretical but are essential tools that can significantly enhance the functionality and quality of your software projects.

6.5 Practical Exercises

This set of exercises is designed to reinforce the concepts discussed in Chapter 6, focusing on object-oriented programming principles such as encapsulation, abstraction, inheritance, and the use of ES6 classes in JavaScript. Each exercise includes a challenge and a solution to help you apply what you've learned in practical coding scenarios.

Exercise 1: Implementing a Class with Encapsulation

Create a Person class that encapsulates an individual's name and age and provides methods to get and set each attribute. Ensure that the age cannot be set to a negative number.

Solution:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getName() {
        return this.name;
    }

    setName(name) {
        this.name = name;
    }

    getAge() {
        return this.age;
    }

    setAge(age) {
        if (age < 0) {
            throw new Error("Age cannot be negative");
        }
        this.age = age;
    }
}

const person = new Person("John", 30);
console.log(person.getName()); // Outputs: John
console.log(person.getAge()); // Outputs: 30

person.setAge(25);
console.log(person.getAge()); // Outputs: 25

// Attempting to set a negative age
try {
    person.setAge(-5);
} catch (e) {
    console.log(e.message); // Outputs: Age cannot be negative
}

Exercise 2: Implementing Inheritance and Method Overriding

Extend the Person class to create a new class called Employee that includes everything from Person and adds employeeId and a method to display all details of the employee.

Solution:

class Employee extends Person {
    constructor(name, age, employeeId) {
        super(name, age); // Calls the constructor of the base class
        this.employeeId = employeeId;
    }

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Employee ID: ${this.employeeId}`);
    }
}

const employee = new Employee("Alice", 28, "E12345");
employee.display(); // Outputs: Name: Alice, Age: 28, Employee ID: E12345

Exercise 3: Using Static Methods

Create a class Calculator with static methods for basic arithmetic operations (add, subtract, multiply, divide) that take two numbers and return the result.

Solution:

class Calculator {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }

    static multiply(a, b) {
        return a * b;
    }

    static divide(a, b) {
        if (b === 0) {
            throw new Error("Division by zero");
        }
        return a / b;
    }
}

console.log(Calculator.add(10, 5)); // Outputs: 15
console.log(Calculator.subtract(10, 5)); // Outputs: 5
console.log(Calculator.multiply(10, 5)); // Outputs: 50
console.log(Calculator.divide(10, 5)); // Outputs: 2

Exercise 4: Abstraction with Proxies

Create a proxy for a settings object that validates changes to properties. Specifically, ensure that volume is between 0 and 100.

Solution:

let settings = {
    volume: 30
};

let settingsProxy = new Proxy(settings, {
    set(target, prop, value) {
        if (prop === 'volume') {
            if (value < 0 || value > 100) {
                throw new Error("Volume must be between 0 and 100");
            }
        }
        target[prop] = value;
        return true;
    }
});

settingsProxy.volume = 90; // Works fine
console.log(settings.volume); // Outputs: 90

// Attempting to set volume out of range
try {
    settingsProxy.volume = 101;
} catch (e) {
    console.log(e.message); // Outputs: Volume must be between 0 and 100
}

These exercises provide practical applications of the object-oriented programming features discussed in Chapter 6, helping you solidify your understanding of these concepts through coding challenges that reflect real-world scenarios.

Chapter Summary

In Chapter 6, we embarked on an exploratory journey through the principles of object-oriented programming (OOP) as implemented in JavaScript. This chapter aimed to demystify the concepts of encapsulation, abstraction, inheritance, and polymorphism, which are pivotal for developing scalable and maintainable applications. By diving into these core principles, we've equipped you with the knowledge to leverage JavaScript's capabilities to create robust and efficient web applications.

Encapsulation and Abstraction

We began by addressing encapsulation, a fundamental OOP concept that involves bundling the data (variables) and the methods (functions) that act on the data into single units called classes. Encapsulation protects an object's internal state from unwanted external interference and misuse, which enhances data integrity and security. We saw how JavaScript uses function scopes and closures to maintain private states within objects, a practice crucial in preserving the integrity and security of applications.

Abstraction was another focus area, simplifying complex systems by hiding the irrelevant details from the users and exposing only the necessary parts of the objects. This helps in reducing programming complexity and increasing efficiency. JavaScript's support for abstraction comes through its ability to create objects that expose only selected attributes and methods to the outside world, allowing developers to change and refactor an object's internal workings without altering how other code interacts with it.

Inheritance and Polymorphism

Inheritance in JavaScript, traditionally achieved through prototypes and more recently through class syntax introduced in ES6, allows objects to inherit properties and methods from other objects. We explored how to create class hierarchies that reflect real-world relationships, enabling you to write less code while increasing functionality. By using the extends keyword for class inheritance and the super constructor for initializing the parent's constructor, JavaScript simplifies the prototype chain management, making it more accessible to developers familiar with traditional OOP languages.

Polymorphism was discussed as a mechanism allowing objects to be treated as instances of their parent class, with the ability to override methods to perform different functionalities. This aspect of OOP in JavaScript enables you to call the same method on different objects, each responding in a way appropriate to the object's type, thus enhancing code flexibility and reusability.

Practical Applications and Best Practices

Throughout the chapter, practical examples demonstrated how to implement these OOP principles in real-world scenarios. From creating classes and managing inheritance trees to applying encapsulation and abstraction for better data management and system design, the exercises provided a hands-on approach to solidifying your understanding of these concepts.

Conclusion

This chapter has laid a solid foundation for understanding and applying object-oriented principles in JavaScript. By mastering these concepts, you are now better equipped to tackle complex development challenges, create highly reusable code, and design your applications with better structure and maintainability. As you continue to delve deeper into JavaScript and its object-oriented features, remember that these principles are not just theoretical but are essential tools that can significantly enhance the functionality and quality of your software projects.

6.5 Practical Exercises

This set of exercises is designed to reinforce the concepts discussed in Chapter 6, focusing on object-oriented programming principles such as encapsulation, abstraction, inheritance, and the use of ES6 classes in JavaScript. Each exercise includes a challenge and a solution to help you apply what you've learned in practical coding scenarios.

Exercise 1: Implementing a Class with Encapsulation

Create a Person class that encapsulates an individual's name and age and provides methods to get and set each attribute. Ensure that the age cannot be set to a negative number.

Solution:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getName() {
        return this.name;
    }

    setName(name) {
        this.name = name;
    }

    getAge() {
        return this.age;
    }

    setAge(age) {
        if (age < 0) {
            throw new Error("Age cannot be negative");
        }
        this.age = age;
    }
}

const person = new Person("John", 30);
console.log(person.getName()); // Outputs: John
console.log(person.getAge()); // Outputs: 30

person.setAge(25);
console.log(person.getAge()); // Outputs: 25

// Attempting to set a negative age
try {
    person.setAge(-5);
} catch (e) {
    console.log(e.message); // Outputs: Age cannot be negative
}

Exercise 2: Implementing Inheritance and Method Overriding

Extend the Person class to create a new class called Employee that includes everything from Person and adds employeeId and a method to display all details of the employee.

Solution:

class Employee extends Person {
    constructor(name, age, employeeId) {
        super(name, age); // Calls the constructor of the base class
        this.employeeId = employeeId;
    }

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Employee ID: ${this.employeeId}`);
    }
}

const employee = new Employee("Alice", 28, "E12345");
employee.display(); // Outputs: Name: Alice, Age: 28, Employee ID: E12345

Exercise 3: Using Static Methods

Create a class Calculator with static methods for basic arithmetic operations (add, subtract, multiply, divide) that take two numbers and return the result.

Solution:

class Calculator {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }

    static multiply(a, b) {
        return a * b;
    }

    static divide(a, b) {
        if (b === 0) {
            throw new Error("Division by zero");
        }
        return a / b;
    }
}

console.log(Calculator.add(10, 5)); // Outputs: 15
console.log(Calculator.subtract(10, 5)); // Outputs: 5
console.log(Calculator.multiply(10, 5)); // Outputs: 50
console.log(Calculator.divide(10, 5)); // Outputs: 2

Exercise 4: Abstraction with Proxies

Create a proxy for a settings object that validates changes to properties. Specifically, ensure that volume is between 0 and 100.

Solution:

let settings = {
    volume: 30
};

let settingsProxy = new Proxy(settings, {
    set(target, prop, value) {
        if (prop === 'volume') {
            if (value < 0 || value > 100) {
                throw new Error("Volume must be between 0 and 100");
            }
        }
        target[prop] = value;
        return true;
    }
});

settingsProxy.volume = 90; // Works fine
console.log(settings.volume); // Outputs: 90

// Attempting to set volume out of range
try {
    settingsProxy.volume = 101;
} catch (e) {
    console.log(e.message); // Outputs: Volume must be between 0 and 100
}

These exercises provide practical applications of the object-oriented programming features discussed in Chapter 6, helping you solidify your understanding of these concepts through coding challenges that reflect real-world scenarios.

Chapter Summary

In Chapter 6, we embarked on an exploratory journey through the principles of object-oriented programming (OOP) as implemented in JavaScript. This chapter aimed to demystify the concepts of encapsulation, abstraction, inheritance, and polymorphism, which are pivotal for developing scalable and maintainable applications. By diving into these core principles, we've equipped you with the knowledge to leverage JavaScript's capabilities to create robust and efficient web applications.

Encapsulation and Abstraction

We began by addressing encapsulation, a fundamental OOP concept that involves bundling the data (variables) and the methods (functions) that act on the data into single units called classes. Encapsulation protects an object's internal state from unwanted external interference and misuse, which enhances data integrity and security. We saw how JavaScript uses function scopes and closures to maintain private states within objects, a practice crucial in preserving the integrity and security of applications.

Abstraction was another focus area, simplifying complex systems by hiding the irrelevant details from the users and exposing only the necessary parts of the objects. This helps in reducing programming complexity and increasing efficiency. JavaScript's support for abstraction comes through its ability to create objects that expose only selected attributes and methods to the outside world, allowing developers to change and refactor an object's internal workings without altering how other code interacts with it.

Inheritance and Polymorphism

Inheritance in JavaScript, traditionally achieved through prototypes and more recently through class syntax introduced in ES6, allows objects to inherit properties and methods from other objects. We explored how to create class hierarchies that reflect real-world relationships, enabling you to write less code while increasing functionality. By using the extends keyword for class inheritance and the super constructor for initializing the parent's constructor, JavaScript simplifies the prototype chain management, making it more accessible to developers familiar with traditional OOP languages.

Polymorphism was discussed as a mechanism allowing objects to be treated as instances of their parent class, with the ability to override methods to perform different functionalities. This aspect of OOP in JavaScript enables you to call the same method on different objects, each responding in a way appropriate to the object's type, thus enhancing code flexibility and reusability.

Practical Applications and Best Practices

Throughout the chapter, practical examples demonstrated how to implement these OOP principles in real-world scenarios. From creating classes and managing inheritance trees to applying encapsulation and abstraction for better data management and system design, the exercises provided a hands-on approach to solidifying your understanding of these concepts.

Conclusion

This chapter has laid a solid foundation for understanding and applying object-oriented principles in JavaScript. By mastering these concepts, you are now better equipped to tackle complex development challenges, create highly reusable code, and design your applications with better structure and maintainability. As you continue to delve deeper into JavaScript and its object-oriented features, remember that these principles are not just theoretical but are essential tools that can significantly enhance the functionality and quality of your software projects.