geekelo / dsa_practice

This repo is maintained just for the purpose of daily data structures and algorithm practice and proficiency
1 stars 0 forks source link

Practice designing class hierarchies and relationships. #9

Open geekelo opened 5 months ago

geekelo commented 5 months ago

Certainly! Designing class hierarchies and relationships is an important aspect of object-oriented programming. Let's go through a simple example to illustrate this concept. We'll design a class hierarchy for a zoo simulation.

Zoo Simulation Example:

// Animal class as the base class
class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  makeSound() {
    console.log(`${this.name} says ${this.sound}`);
  }
}

// Mammal class extends Animal
class Mammal extends Animal {
  constructor(name, sound, furColor) {
    super(name, sound);
    this.furColor = furColor;
  }

  walk() {
    console.log(`${this.name} is walking.`);
  }
}

// Bird class extends Animal
class Bird extends Animal {
  constructor(name, sound, featherColor) {
    super(name, sound);
    this.featherColor = featherColor;
  }

  fly() {
    console.log(`${this.name} is flying.`);
  }
}

// Reptile class extends Animal
class Reptile extends Animal {
  constructor(name, sound, scaleType) {
    super(name, sound);
    this.scaleType = scaleType;
  }

  crawl() {
    console.log(`${this.name} is crawling.`);
  }
}

// Zoo class that aggregates different animals
class Zoo {
  constructor() {
    this.animals = [];
  }

  addAnimal(animal) {
    this.animals.push(animal);
  }

  performDailyActivities() {
    console.log("Daily activities at the zoo:");
    this.animals.forEach(animal => {
      if (animal instanceof Mammal) {
        animal.walk();
      } else if (animal instanceof Bird) {
        animal.fly();
      } else if (animal instanceof Reptile) {
        animal.crawl();
      }

      animal.makeSound();
    });
  }
}

// Usage
const lion = new Mammal("Lion", "Roar", "Golden");
const parrot = new Bird("Parrot", "Squawk", "Colorful");
const snake = new Reptile("Snake", "Hiss", "Smooth");

const zoo = new Zoo();
zoo.addAnimal(lion);
zoo.addAnimal(parrot);
zoo.addAnimal(snake);

zoo.performDailyActivities();

In this example:

This example demonstrates class hierarchies, inheritance, and polymorphism. Designing your class hierarchies thoughtfully can lead to more modular and maintainable code.

geekelo commented 5 months ago

You're correct, and I appreciate the opportunity to clarify. Abstraction involves exposing only the essential features of an object while hiding unnecessary details. It focuses on providing a clear and simplified interface to the outside world, while the internal implementation details are hidden.

In the context of programming, abstraction often involves defining interfaces, abstract classes, or protocols that specify the essential behaviors without exposing the underlying implementation. Clients using these abstractions don't need to know the intricacies of how things work internally; they only need to interact with the public interface.

In the zoo simulation example:

So, in summary, abstraction in programming involves exposing the essential features while hiding the implementation details, providing a simplified and understandable interface for users of the code.

geekelo commented 5 months ago

Certainly! Let's review the design decisions made in the zoo simulation example and justify them based on the principles of Object-Oriented Design (OOD).

1. Encapsulation:

2. Inheritance:

3. Abstraction:

4. Polymorphism:

Additional Considerations:

By adhering to these OOD principles, the design of the zoo simulation promotes modularity, maintainability, flexibility, and code reuse, making it a robust and scalable solution.

geekelo commented 5 months ago

Certainly! Let's extend the zoo simulation example by introducing a ZooKeeper class using composition and incorporating interfaces for common behaviors.

Introducing Composition with ZooKeeper:

// ZooKeeper class for interacting with animals
class ZooKeeper {
  feedAnimal(animal) {
    console.log(`${this.name} is feeding ${animal.name}.`);
  }

  cleanEnclosure(animal) {
    console.log(`${this.name} is cleaning ${animal.name}'s enclosure.`);
  }
}

// Modify Animal class to include a reference to ZooKeeper
class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
    this.zooKeeper = null; // Reference to ZooKeeper
  }

  makeSound() {
    console.log(`${this.name} says ${this.sound}`);
  }

  setZooKeeper(zooKeeper) {
    this.zooKeeper = zooKeeper;
  }
}

// Extend the Zoo class to include a ZooKeeper
class Zoo {
  constructor(zooKeeper) {
    this.animals = [];
    this.zooKeeper = zooKeeper;
  }

  addAnimal(animal) {
    this.animals.push(animal);
    // Set the ZooKeeper for the animal
    animal.setZooKeeper(this.zooKeeper);
  }

  performDailyActivities() {
    console.log("Daily activities at the zoo:");
    this.animals.forEach(animal => {
      // Polymorphic behavior
      if (animal instanceof Mammal) {
        animal.walk();
      } else if (animal instanceof Bird) {
        animal.fly();
      } else if (animal instanceof Reptile) {
        animal.crawl();
      }

      animal.makeSound();
      // ZooKeeper interactions using composition
      this.zooKeeper.feedAnimal(animal);
      this.zooKeeper.cleanEnclosure(animal);
    });
  }
}

// Usage
const zooKeeper = new ZooKeeper();
const zoo = new Zoo(zooKeeper);

const lion = new Mammal("Lion", "Roar", "Golden");
const parrot = new Bird("Parrot", "Squawk", "Colorful");
const snake = new Reptile("Snake", "Hiss", "Smooth");

zoo.addAnimal(lion);
zoo.addAnimal(parrot);
zoo.addAnimal(snake);

zoo.performDailyActivities();

Introducing Interfaces:

While JavaScript doesn't have native support for interfaces, we can emulate them by defining methods in a separate object and ensuring that classes implement those methods.

// Define an interface for animals
const AnimalInterface = {
  makeSound: function () {},
};

// Modify Animal class to implement the AnimalInterface
class Animal {
  // ... (unchanged code)

  // Implement the makeSound method from the interface
  makeSound() {
    console.log(`${this.name} says ${this.sound}`);
  }
}

// ... (other classes remain unchanged)

// Usage
const lion = new Mammal("Lion", "Roar", "Golden");
const parrot = new Bird("Parrot", "Squawk", "Colorful");
const snake = new Reptile("Snake", "Hiss", "Smooth");

// Ensure the objects adhere to the interface
Object.assign(lion, AnimalInterface);
Object.assign(parrot, AnimalInterface);
Object.assign(snake, AnimalInterface);

lion.makeSound();
parrot.makeSound();
snake.makeSound();

In this updated example, ZooKeeper is introduced through composition, and an "interface" is emulated using a shared object of methods. These additions demonstrate how OOD principles can be extended and adapted to enhance the flexibility and maintainability of the code.

geekelo commented 5 months ago

Certainly! Let's review the zoo simulation example in the context of the SOLID principles:

1. Single Responsibility Principle (SRP):

2. Open/Closed Principle (OCP):

3. Liskov Substitution Principle (LSP):

4. Interface Segregation Principle (ISP):

5. Dependency Inversion Principle (DIP):

By considering the SOLID principles, the zoo simulation example is designed to be modular, extensible, and maintainable. Each principle contributes to the overall robustness of the design, making it easier to adapt to changes and new requirements.

geekelo commented 5 months ago

The SOLID principles are a set of design principles in object-oriented programming that, when followed, lead to more maintainable, flexible, and scalable software. SOLID is an acronym representing five key principles:

1. Single Responsibility Principle (SRP):

2. Open/Closed Principle (OCP):

3. Liskov Substitution Principle (LSP):

4. Interface Segregation Principle (ISP):

5. Dependency Inversion Principle (DIP):

Following the SOLID principles contributes to the development of maintainable, modular, and scalable software systems. These principles guide developers in creating code that is easier to understand, extend, and modify, reducing the risk of introducing errors when making changes or adding new features.