OOPS Flashcards

1
Q

Principles of OOP

A

The following are the essential concepts of object-oriented programming:

  • Attributes
  • Methods
  • Classes
  • Objects

Principles of OOP

The following are the four principles of object-oriented programming:

  • Encapsulation
  • Abstraction
  • Inheritance
  • Polymorphism

Encapsulation

Encapsulation is the bundling of data and methods that operate on the data within one unit, such as a class in JavaScript. It restricts direct access to some of an object’s components, which can prevent the accidental modification of data.

```jsx

class Car {
constructor(brand, model) {
this._brand = brand; // _ indicates private variables by convention
this._model = model;
}

// Getter for brand
get brand() {
    return this._brand;
}

// Setter for brand
set brand(newBrand) {
    this._brand = newBrand;
}

displayDetails() {
    console.log(`Brand: ${this._brand}, Model: ${this._model}`);
} }

const myCar = new Car(‘Toyota’, ‘Corolla’);
myCar.displayDetails(); // Brand: Toyota, Model: Corolla
myCar.brand = ‘Honda’; // using setter
myCar.displayDetails(); // Brand: Honda, Model: Corolla

Abstraction

Abstraction means hiding the complex implementation details and showing only the necessary features of an object. This can be achieved using classes and interfaces.

```jsx

class Animal {
    constructor(name) {
        this.name = name;
    }

    makeSound() {
        // This is an abstract method
        throw new Error('You have to implement the method makeSound!');
    }
}

class Dog extends Animal {
    makeSound() {
        console.log('Woof Woof!');
    }
}

const myDog = new Dog('Buddy');
myDog.makeSound(); // Woof Woof!

Inheritance

Inheritance is a mechanism where a new class (child class) inherits the properties and behaviors of another class (parent class).

```jsx

class Vehicle {
constructor(wheels) {
this.wheels = wheels;
}

describe() {
    console.log(`This vehicle has ${this.wheels} wheels.`);
} }

class Bike extends Vehicle {
constructor() {
super(2); // Call the parent class constructor
}

ride() {
    console.log('Riding the bike');
} }

const myBike = new Bike();
myBike.describe(); // This vehicle has 2 wheels.
myBike.ride(); // Riding the bike

Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common super class. It is often used when a parent class reference is used to refer to a child class object.

```jsx

class Bird {
    fly() {
        console.log('The bird is flying.');
    }
}

class Sparrow extends Bird {
    fly() {
        console.log('The sparrow is flying.');
    }
}

class Eagle extends Bird {
    fly() {
        console.log('The eagle is soaring high.');
    }
}

function makeItFly(bird) {
    bird.fly();
}

const mySparrow = new Sparrow();
const myEagle = new Eagle();

makeItFly(mySparrow); // The sparrow is flying.
makeItFly(myEagle);   // The eagle is soaring high.

These concepts help in creating a structured and organized codebase, making it easier to manage and scale.

Static Polymorphism

Polymorphism that is resolved during compile-time is known as static polymorphism.
Method overloading is used in static polymorphism.

class Sum {
    addition(a, b, c = 0){
        return a + b + c;
    }
}

const sum = new Sum;
console.log(sum.addition(14, 35));
console.log(sum.addition(31, 34, 43));

Dymamic

Polymorphism that is resolved during runtime is known as dynamic polymorphism.
Method overriding is used in dynamic polymorphism.

class Animal {
    printAnimal() {
        console.log("I am from the Animal class")
    }
    printAnimalTwo() {
        console.log("I am from the Animal class")
    }
}

class Lion extends Animal {
    printAnimal(){  // method overriding
        console.log("I am from the Lion class")
    }
}

const animal = new Lion
animal.printAnimal()
animal.printAnimalTwo()

Static Polymorphism | Dynamic Polymorphism |

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

UML

A

Unified Modeling Language (UML) is a standardized modeling language used to visualize, specify, construct, and document the artifacts of a software system. UML provides a set of graphical notation techniques to create abstract models of specific systems, which is particularly useful in object-oriented programming. Here are the main components of UML:

UML Diagrams

UML diagrams are categorized into two main types: structural diagrams and behavioral diagrams.

Structural Diagrams

  1. Class Diagram: Shows the static structure of a system, including classes, attributes, methods, and relationships between classes.
  2. Object Diagram: Displays instances of classes at a particular moment, showing the structure of a system at a specific time.
  3. Component Diagram: Represents the organization and dependencies among software components.
  4. Deployment Diagram: Illustrates the physical deployment of artifacts on nodes.
  5. Package Diagram: Organizes elements of a system into related groups to minimize dependencies.
  6. Composite Structure Diagram: Depicts the internal structure of a class and the collaborations that this structure makes possible.

Behavioral Diagrams

  1. Use Case Diagram: Shows the interactions between users (actors) and the system, representing the functionality of a system.
  2. Sequence Diagram: Displays how objects interact in a particular sequence of time.
  3. Activity Diagram: Represents workflows of stepwise activities and actions with support for choice, iteration, and concurrency.
  4. State Diagram: Describes the states and transitions of an object throughout its lifecycle.
  5. Collaboration Diagram: Shows interactions between objects and their relationships.
  6. Timing Diagram: Depicts the change in state or condition of a class or object over time.
  7. Interaction Overview Diagram: Combines aspects of activity and sequence diagrams to show control flow.

Basic Elements of UML

  • Class: Represented by a rectangle divided into three parts: the class name, attributes, and methods.
  • Association: A line connecting classes to show relationships. It can have multiplicities to show the number of instances involved.
  • Inheritance: Represented by a solid line with a hollow arrow pointing from the subclass to the superclass.
  • Aggregation/Composition: Represented by a line with a diamond at the aggregate (whole) end, indicating a “part-whole” relationship. Composition (filled diamond) indicates a stronger relationship than aggregation (hollow diamond).
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

SOLID principles

A

Let’s look at the possible issues below that may occur in the code if we don’t adhere to the SOLID principles.

  • The code may become tightly coupled with several components, which makes it difficult to integrate new features or bug fixes and sometimes leads to unidentified problems.
  • The code will be untestable, which effectively means that every change will need end-to-end testing.
  • The code may have a lot of duplication.
  • Fixing one issue results in additional errors.

However, if we adhere to the SOLID principles, we are able to do the following:

  • Reduce the tight coupling of the code, which reduces errors.
  • Reduce the code’s complexity for future use.
  • Produce more extensible, maintainable, and understandable software code.
  • Produce the code that is modular, feature specific, and is extremely testable.

Design principles

Let’s look at the definition of the five design principles.

  • In theSingle Responsibility Principle (SRP), each class should be responsible for a single part or functionality of the system.
  • In theOpen Closed principle (OCP), software components should be open for extension but closed for modification.
  • In theLiskov Substitution Principle (LSP), objects of a superclass should be replaceable with objects of its subclasses without breaking the system.
  • TheInterface Segregation Principle (ISP)makes fine-grained interfaces that are client specific.
  • TheDependency Inversion Principle (DIP), ensures that the high-level modules are not dependent on low-level modules. In other words, one should depend upon abstraction and not concretion.

Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should have only one job or responsibility. This principle helps to keep classes focused and manageable.

```jsx
javascriptCopy code
// Bad example
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}

getUserInfo() {
    // Responsibility 1: User data management
    return `Name: ${this.name}, Email: ${this.email}`;
}

saveToDatabase() {
    // Responsibility 2: Database management
    console.log('Saving user to the database...');
} }

// Good example
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}

getUserInfo() {
    return `Name: ${this.name}, Email: ${this.email}`;
} }

class UserRepository {
save(user) {
console.log(‘Saving user to the database…’);
}
}

2. Open/Closed Principle (OCP)

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means you should be able to add new functionality without changing the existing code.

```jsx
javascriptCopy code
// Bad example
class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    area() {
        return this.width * this.height;
    }
}

class AreaCalculator {
    calculate(shape) {
        if (shape instanceof Rectangle) {
            return shape.area();
        }
        // If a new shape is added, this method needs to be modified.
    }
}

// Good example
class Shape {
    area() {
        throw new Error('This method should be overridden!');
    }
}

class Rectangle extends Shape {
    constructor(width, height) {
        super();
        this.width = width;
        this.height = height;
    }

    area() {
        return this.width * this.height;
    }
}

class Circle extends Shape {
    constructor(radius) {
        super();
        this.radius = radius;
    }

    area() {
        return Math.PI * this.radius * this.radius;
    }
}

class AreaCalculator {
    calculate(shape) {
        return shape.area();
    }
}
  1. Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. This principle ensures that a subclass can stand in for its superclass.

```jsx
javascriptCopy code
// Bad example
class Bird {
fly() {
console.log(‘Flying’);
}
}

class Ostrich extends Bird {
fly() {
throw new Error(‘Ostriches can’t fly!’);
}
}

// Good example
class Bird {
move() {
console.log(‘Moving’);
}
}

class Sparrow extends Bird {
move() {
console.log(‘Flying’);
}
}

class Ostrich extends Bird {
move() {
console.log(‘Running’);
}
}

function moveBird(bird) {
bird.move();
}

const sparrow = new Sparrow();
const ostrich = new Ostrich();

moveBird(sparrow); // Flying
moveBird(ostrich); // Running

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on methods they do not use. This principle encourages creating specific interfaces rather than a single, general-purpose interface.

```jsx
javascriptCopy code
// Bad example
class Animal {
    eat() {}
    fly() {}
    swim() {}
}

class Dog extends Animal {
    eat() {
        console.log('Dog is eating');
    }

    fly() {
        throw new Error('Dog can’t fly');
    }

    swim() {
        console.log('Dog is swimming');
    }
}

// Good example
class Eater {
    eat() {
        console.log('Eating');
    }
}

class Swimmer {
    swim() {
        console.log('Swimming');
    }
}

class Dog extends Eater {
    swim() {
        console.log('Dog is swimming');
    }
}
  1. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

```jsx
javascriptCopy code
// Bad example
class MySQLDatabase {
connect() {
console.log(‘Connecting to MySQL’);
}
}

class UserRepository {
constructor() {
this.db = new MySQLDatabase();
}

addUser(user) {
    this.db.connect();
    console.log('Adding user');
} }

// Good example
class Database {
connect() {
throw new Error(‘This method should be overridden!’);
}
}

class MySQLDatabase extends Database {
connect() {
console.log(‘Connecting to MySQL’);
}
}

class UserRepository {
constructor(db) {
this.db = db;
}

addUser(user) {
    this.db.connect();
    console.log('Adding user');
} }

const mySQLDatabase = new MySQLDatabase();
const userRepository = new UserRepository(mySQLDatabase);
userRepository.addUser({ name: ‘John Doe’ });

~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

design patterns

A

They provide correct and efficient solutions since they have been derived and optimized by various experienced programmers over time.

They are generic templates that can be modified and used for solving different problems.

They can provide a clean and elegant solution to a large problem by avoiding repetition in the code.

They provide a template on which the developers can build upon. This allows developers to spend less time on code structure and more on the overall quality of the solution.

  1. Module Pattern
  • Purpose: Encapsulate related code into a single unit of code (module), which can be exported and reused.
  • Example:```jsx
    javascriptCopy code
    const Module = (function() {
    let privateVar = ‘I am private’;
    function privateMethod() {
      console.log(privateVar);
    }
    
    return {
      publicMethod: function() {
        privateMethod();
      }
    };   })();
    Module.publicMethod(); // I am private```
  1. Observer Pattern
  • Purpose: Define a subscription mechanism to allow multiple objects to listen to events or changes in another object.
  • Example:```jsx
    javascriptCopy code
    class EventObserver {
    constructor() {
    this.observers = [];
    }
    subscribe(fn) {
      this.observers.push(fn);
    }
    
    unsubscribe(fn) {
      this.observers = this.observers.filter(observer => observer !== fn);
    }
    
    broadcast(data) {
      this.observers.forEach(observer => observer(data));
    }   }
    const observer = new EventObserver();const logData = (data) => console.log(Received: ${data});
    observer.subscribe(logData);
    observer.broadcast(‘Event 1’); // Received: Event 1```
  1. Singleton Pattern
  • Purpose: Ensure a class has only one instance and provide a global point of access to it.
  • Example:```jsx
    javascriptCopy code
    class Singleton {
    constructor() {
    if (!Singleton.instance) {
    Singleton.instance = this;
    }
    return Singleton.instance;
    }
    someMethod() {
      console.log('Singleton method');
    }   }
    const instance1 = new Singleton();
    const instance2 = new Singleton();
    console.log(instance1 === instance2); // true```
  1. Factory Pattern
  • Purpose: Define an interface for creating objects, but let subclasses alter the type of objects that will be created.
  • Example:```jsx
    javascriptCopy code
    class Car {
    constructor(model) {
    this.model = model;
    }
    }class CarFactory {
    createCar(model) {
    return new Car(model);
    }
    }const factory = new CarFactory();
    const myCar = factory.createCar(‘Tesla Model S’);
    console.log(myCar.model); // Tesla Model S```
  1. Decorator Pattern
  • Purpose: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
  • Example:```jsx
    javascriptCopy code
    function addFeature(obj) {
    obj.newFeature = function() {
    console.log(‘New Feature’);
    };
    return obj;
    }const myObject = { existingFeature: () => console.log(‘Existing Feature’) };
    const decoratedObject = addFeature(myObject);
    decoratedObject.existingFeature(); // Existing Feature
    decoratedObject.newFeature(); // New Feature```
  1. Strategy Pattern
  • Purpose: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
  • Example:```jsx
    javascriptCopy code
    class SortStrategy {
    sort(data) {}
    }class QuickSort extends SortStrategy {
    sort(data) {
    console.log(‘QuickSort’);
    // QuickSort algorithm implementation
    }
    }class BubbleSort extends SortStrategy {
    sort(data) {
    console.log(‘BubbleSort’);
    // BubbleSort algorithm implementation
    }
    }class SortContext {
    constructor(strategy) {
    this.strategy = strategy;
    }
    setStrategy(strategy) {
      this.strategy = strategy;
    }
    
    sort(data) {
      this.strategy.sort(data);
    }   }
    const context = new SortContext(new QuickSort());
    context.sort([1, 2, 3]); // QuickSort
    context.setStrategy(new BubbleSort());
    context.sort([1, 2, 3]); // BubbleSort```
  1. MVC Pattern (Model-View-Controller)
  • Purpose: Separate the application into three interconnected components: Model, View, and Controller to separate internal representations of information from the ways that information is presented to and accepted by the user.
  • Example:```jsx
    javascriptCopy code
    class Model {
    constructor() {
    this.data = 0;
    }
    increment() {
      this.data++;
    }
    
    getData() {
      return this.data;
    }   }
    class View {
    render(data) {
    console.log(Data: ${data});
    }
    }class Controller {
    constructor(model, view) {
    this.model = model;
    this.view = view;
    }
    increment() {
      this.model.increment();
      this.view.render(this.model.getData());
    }   }
    const model = new Model();
    const view = new View();
    const controller = new Controller(model, view);controller.increment(); // Data: 1```
How well did you know this?
1
Not at all
2
3
4
5
Perfectly