CodingNic

Object-Oriented Programming

Exercises

Object-Oriented Programming 40 min read

Exercises

Objectives

This chapter introduces no new concepts. It’s a chance to practice everything from this module: the this keyword, classes, constructors, static methods, encapsulation, private fields, inheritance, and polymorphism.

Exercises

  1. Write an object { name: "Jordan", greet() { return Hi, I’m ${this.name}; } }. Call .greet() normally and log it, then assign the method to a separate variable (const g = obj.greet) and call g() inside a try/catch. Check: the direct call logs Hi, I'm Jordan, and the extracted call throws a TypeError because this is undefined.

  2. Write a plain function formatPrice(currency) that returns `${currency}${this.amount}`, and an object { amount: 25 }. Call formatPrice three ways against that object: with .call(), with .apply(), and by creating a bound version with .bind() first. Check: all three produce the same result, $25 if you pass "$" as the currency.

  3. Write a class Book with constructor(title, author, pages) that stores all three as properties. Add an instance method summary() that returns `${title} by ${author} (${pages} pages)`. Create new Book("Dune", "Frank Herbert", 412) and log .summary(). Check: the output must read exactly Dune by Frank Herbert (412 pages).

  4. Add a fourth constructor parameter to Book called available that defaults to true if not passed. Create one book without an available argument and one passing false explicitly. Check: logging .available on each gives true and false.

  5. Add a static method Book.blank() that returns new Book("Untitled", "Unknown", 0). Check: Book.blank().summary() logs Untitled by Unknown (0 pages).

  6. Write a class MathHelper with a static method square(n) that returns n * n, and no instance methods. Check: MathHelper.square(5) returns 25. Then check that new MathHelper().square is undefined, since static methods aren’t reachable from instances.

  7. Write a class Wallet with a private field #cash starting at 0, a method addCash(amount) that increases it, a method spend(amount) that decreases it and returns the new total, or returns the string "Not enough cash" if amount is greater than the current balance, and a method getCash() that returns the current value. Create a wallet, call addCash(50), then spend(20). Check: getCash() returns 30.

  8. Using the Wallet class from exercise 7, try writing myWallet.#cash directly in your file, outside the class body. Run the file with node. Check: instead of returning a value, it throws SyntaxError: Private field '#cash' must be declared in an enclosing class.

  9. Write a class Playlist with a private field #songs (an array), a method addSong(title) that pushes onto it, and a method getSongs() that returns a copy of the array, for example with [...this.#songs], not the original array. Check: calling .push() on the array returned by getSongs() does not change what a second call to getSongs() returns.

  10. Write a base class Employee with constructor(name, salary) and a method describe() that returns `${name} earns ${salary}`. Write a subclass Manager extends Employee with constructor(name, salary, teamSize) that calls super(name, salary) and stores teamSize. Do not override describe() yet. Check: new Manager("Sam", 70000, 4).teamSize is 4, and .describe() still works even though Manager never defined it.

  11. Override describe() in Manager so it calls super.describe() and appends `, managing ${teamSize} people` to the result. Check: new Manager("Sam", 70000, 4).describe() returns exactly Sam earns 70000, managing 4 people.

  12. Write a second subclass Intern extends Employee that overrides describe() to return `${name} is interning`. Put one Employee, one Manager, and one Intern in an array and loop over it, calling .describe() on each. Check: the three lines of output are all different, each matching its own class’s version of describe().

  13. Add a static method Employee.payFrequency() that returns "monthly". Do not add anything to Manager or Intern. Check: Manager.payFrequency() and Intern.payFrequency() both return "monthly", proving static methods are inherited too.

  14. Using the classes from exercise 12, create const erin = new Intern("Erin", 0). Check: erin instanceof Intern and erin instanceof Employee are both true, and erin instanceof Manager is false.

  15. Write a class Account with a private field #balance, a constructor(balance), a deposit(amount) method, and a getBalance() method, all touching #balance the way lesson 4 showed. Write a subclass SavingsAccount extends Account with constructor(balance, rate) that calls super(balance) and stores rate, plus a method applyInterest() that computes this.getBalance() * this.rate and passes it to this.deposit(...). Check: new SavingsAccount(100, 0.1).applyInterest() returns 110.

  16. Write a static factory method Shape.create(type, size) on a base class Shape (with subclasses Circle and Square, each with a describe() override) that returns new Circle(size) when type === "circle" and new Square(size) otherwise. Check: Shape.create("circle", 4).describe() and Shape.create("square", 4).describe() produce two different, class-specific strings.

Recap

You can now define classes with constructors and static methods, protect internal state with private fields, build class hierarchies with extends and super, and write polymorphic code that treats different subclasses the same way while each still behaves like itself.

Next module: error handling, catching problems with try/catch/finally and raising your own errors with throw.