
Object-Oriented Programming Refresher
/ 20 min read
Table of Contents
What is Object-Oriented Programming?
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of “objects” - data structures that contain data (attributes) and code (methods). OOP organizes software design around objects rather than functions and logic.
Key Benefits of OOP
- Code Reusability: Inherit and extend existing code
- Modularity: Break complex problems into smaller, manageable pieces
- Maintainability: Easier to modify and extend code
- Abstraction: Hide complex implementation details
- Real-world modeling: Map real-world entities to code objects
Core OOP Concepts
1. Classes and Objects
Class
A class is a blueprint or template for creating objects. It defines the structure and behavior that objects will have.
class Car: # Class variable (shared by all instances) wheels = 4
# Constructor method def __init__(self, make, model, year): # Instance variables (unique to each object) self.make = make self.model = model self.year = year self.is_running = False
# Instance method def start_engine(self): self.is_running = True return f"{self.make} {self.model} engine started!"
def stop_engine(self): self.is_running = False return f"{self.make} {self.model} engine stopped!"
# String representation def __str__(self): return f"{self.year} {self.make} {self.model}"Object
An object is an instance of a class - a concrete realization of the class blueprint.
# Creating objects (instances)car1 = Car("Toyota", "Camry", 2023)car2 = Car("Honda", "Civic", 2022)
# Accessing attributesprint(car1.make) # Toyotaprint(car1.wheels) # 4 (class variable)
# Calling methodsprint(car1.start_engine()) # Toyota Camry engine started!print(car1) # 2023 Toyota CamryJava Example
public class Car { // Class variable public static final int WHEELS = 4;
// Instance variables private String make; private String model; private int year; private boolean isRunning;
// Constructor public Car(String make, String model, int year) { this.make = make; this.model = model; this.year = year; this.isRunning = false; }
// Methods public String startEngine() { this.isRunning = true; return this.make + " " + this.model + " engine started!"; }
public String getMake() { return this.make; }
@Override public String toString() { return this.year + " " + this.make + " " + this.model; }}The Four Pillars of OOP
1. Encapsulation
Encapsulation is the bundling of data and methods that operate on that data within a single unit (class), and restricting access to internal implementation details.
Python Example
class BankAccount: def __init__(self, account_number, initial_balance=0): self.account_number = account_number self.__balance = initial_balance # Private attribute (name mangling) self._transaction_history = [] # Protected attribute (convention)
# Public method to access private data def get_balance(self): return self.__balance
# Public method to modify private data def deposit(self, amount): if amount > 0: self.__balance += amount self._add_transaction(f"Deposited ${amount}") return True return False
def withdraw(self, amount): if 0 < amount <= self.__balance: self.__balance -= amount self._add_transaction(f"Withdrew ${amount}") return True return False
# Private method (internal implementation) def _add_transaction(self, description): from datetime import datetime self._transaction_history.append({ 'date': datetime.now(), 'description': description, 'balance': self.__balance })
# Property decorator for controlled access @property def balance(self): return self.__balance
@balance.setter def balance(self, value): if value >= 0: self.__balance = value
# Usageaccount = BankAccount("123456", 1000)print(account.get_balance()) # 1000account.deposit(500)print(account.balance) # 1500 (using property)
# This won't work - private attribute# print(account.__balance) # AttributeErrorJava Example
public class BankAccount { private String accountNumber; private double balance; private List<String> transactionHistory;
public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; this.balance = initialBalance; this.transactionHistory = new ArrayList<>(); }
// Getter (accessor) public double getBalance() { return this.balance; }
// Setter (mutator) with validation public void setBalance(double balance) { if (balance >= 0) { this.balance = balance; } }
public boolean deposit(double amount) { if (amount > 0) { this.balance += amount; addTransaction("Deposited $" + amount); return true; } return false; }
private void addTransaction(String description) { this.transactionHistory.add(description); }}Benefits of Encapsulation:
- Data Protection: Prevent unauthorized access to internal data
- Validation: Control how data is modified
- Flexibility: Change internal implementation without affecting external code
- Debugging: Easier to track data changes
2. Inheritance
Inheritance allows a class (child/derived) to inherit properties and methods from another class (parent/base), promoting code reuse and establishing hierarchical relationships.
Python Example
# Base class (Parent)class Animal: def __init__(self, name, species): self.name = name self.species = species self.is_alive = True
def eat(self, food): return f"{self.name} is eating {food}"
def sleep(self): return f"{self.name} is sleeping"
def make_sound(self): return "Some generic animal sound"
# Derived class (Child)class Dog(Animal): def __init__(self, name, breed): super().__init__(name, "Canine") # Call parent constructor self.breed = breed
# Method overriding def make_sound(self): return f"{self.name} says Woof!"
# Additional method specific to Dog def fetch(self, item): return f"{self.name} fetched the {item}!"
class Cat(Animal): def __init__(self, name, breed): super().__init__(name, "Feline") self.breed = breed
def make_sound(self): return f"{self.name} says Meow!"
def climb(self): return f"{self.name} climbed up the tree!"
# Usagedog = Dog("Buddy", "Golden Retriever")cat = Cat("Whiskers", "Persian")
print(dog.eat("dog food")) # Inherited methodprint(dog.make_sound()) # Overridden methodprint(dog.fetch("ball")) # Dog-specific method
print(cat.make_sound()) # Overridden methodprint(cat.climb()) # Cat-specific methodMultiple Inheritance Example
class Flyable: def fly(self): return "Flying through the air!"
class Swimmable: def swim(self): return "Swimming in water!"
class Duck(Animal, Flyable, Swimmable): def __init__(self, name): super().__init__(name, "Bird")
def make_sound(self): return f"{self.name} says Quack!"
# Usageduck = Duck("Donald")print(duck.fly()) # From Flyableprint(duck.swim()) # From Swimmableprint(duck.eat("bread")) # From AnimalJava Example (Single Inheritance)
// Base classpublic class Animal { protected String name; protected String species; protected boolean isAlive;
public Animal(String name, String species) { this.name = name; this.species = species; this.isAlive = true; }
public String eat(String food) { return this.name + " is eating " + food; }
public String makeSound() { return "Some generic animal sound"; }}
// Derived classpublic class Dog extends Animal { private String breed;
public Dog(String name, String breed) { super(name, "Canine"); // Call parent constructor this.breed = breed; }
@Override public String makeSound() { return this.name + " says Woof!"; }
public String fetch(String item) { return this.name + " fetched the " + item + "!"; }}Types of Inheritance:
- Single Inheritance: One parent class
- Multiple Inheritance: Multiple parent classes (Python supports, Java doesn’t)
- Multilevel Inheritance: Child class becomes parent to another class
- Hierarchical Inheritance: Multiple child classes from one parent
3. Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common base class, while still maintaining their own specific behavior.
Runtime Polymorphism (Method Overriding)
class Shape: def area(self): raise NotImplementedError("Subclass must implement area()")
def perimeter(self): raise NotImplementedError("Subclass must implement perimeter()")
class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height
def area(self): return self.width * self.height
def perimeter(self): return 2 * (self.width + self.height)
class Circle(Shape): def __init__(self, radius): self.radius = radius
def area(self): return 3.14159 * self.radius ** 2
def perimeter(self): return 2 * 3.14159 * self.radius
class Triangle(Shape): def __init__(self, base, height, side1, side2): self.base = base self.height = height self.side1 = side1 self.side2 = side2
def area(self): return 0.5 * self.base * self.height
def perimeter(self): return self.base + self.side1 + self.side2
# Polymorphic functiondef print_shape_info(shape): print(f"Shape: {type(shape).__name__}") print(f"Area: {shape.area()}") print(f"Perimeter: {shape.perimeter()}") print("-" * 20)
# Usage - same function works with different object typesshapes = [ Rectangle(5, 3), Circle(4), Triangle(6, 4, 5, 5)]
for shape in shapes: print_shape_info(shape) # Polymorphic behaviorCompile-time Polymorphism (Method Overloading)
class Calculator: def add(self, a, b=None, c=None): """Method overloading simulation using default parameters""" if c is not None: return a + b + c elif b is not None: return a + b else: return a
# Using multiple dispatch (requires functools.singledispatch) from functools import singledispatch
@singledispatch @staticmethod def multiply(x): raise NotImplementedError("Unsupported type")
@multiply.register @staticmethod def _(x: int, y: int): return x * y
@multiply.register @staticmethod def _(x: float, y: float): return x * y
@multiply.register @staticmethod def _(x: str, y: int): return x * y
# Usagecalc = Calculator()print(calc.add(1, 2)) # 3print(calc.add(1, 2, 3)) # 6Java Polymorphism Example
// Base classabstract class Shape { public abstract double area(); public abstract double perimeter();}
class Rectangle extends Shape { private double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
@Override public double area() { return width * height; }
@Override public double perimeter() { return 2 * (width + height); }}
class Circle extends Shape { private double radius;
public Circle(double radius) { this.radius = radius; }
@Override public double area() { return Math.PI * radius * radius; }
@Override public double perimeter() { return 2 * Math.PI * radius; }}
// Polymorphic usagepublic class ShapeDemo { public static void printShapeInfo(Shape shape) { System.out.println("Area: " + shape.area()); System.out.println("Perimeter: " + shape.perimeter()); }
public static void main(String[] args) { Shape[] shapes = { new Rectangle(5, 3), new Circle(4) };
for (Shape shape : shapes) { printShapeInfo(shape); // Polymorphic behavior } }}4. Abstraction
Abstraction hides complex implementation details and shows only the essential features of an object. It focuses on what an object does rather than how it does it.
Abstract Base Classes
from abc import ABC, abstractmethod
class Vehicle(ABC): def __init__(self, make, model): self.make = make self.model = model
@abstractmethod def start_engine(self): pass
@abstractmethod def stop_engine(self): pass
@abstractmethod def get_fuel_efficiency(self): pass
# Concrete method (shared implementation) def get_info(self): return f"{self.make} {self.model}"
class Car(Vehicle): def __init__(self, make, model, fuel_type): super().__init__(make, model) self.fuel_type = fuel_type self.engine_running = False
def start_engine(self): self.engine_running = True return f"{self.get_info()} engine started"
def stop_engine(self): self.engine_running = False return f"{self.get_info()} engine stopped"
def get_fuel_efficiency(self): return "25 MPG" # Simplified
class ElectricCar(Vehicle): def __init__(self, make, model, battery_capacity): super().__init__(make, model) self.battery_capacity = battery_capacity self.motor_running = False
def start_engine(self): self.motor_running = True return f"{self.get_info()} motor started silently"
def stop_engine(self): self.motor_running = False return f"{self.get_info()} motor stopped"
def get_fuel_efficiency(self): return "100 MPGe" # Miles per gallon equivalent
# Usagevehicles = [ Car("Toyota", "Camry", "Gasoline"), ElectricCar("Tesla", "Model 3", "75 kWh")]
for vehicle in vehicles: print(vehicle.start_engine()) print(f"Efficiency: {vehicle.get_fuel_efficiency()}") print(vehicle.stop_engine()) print("-" * 30)Interface-like Abstraction
class Drawable: """Interface-like class defining drawing contract"""
def draw(self): raise NotImplementedError("Must implement draw()")
def resize(self, factor): raise NotImplementedError("Must implement resize()")
class Circle(Drawable): def __init__(self, radius): self.radius = radius
def draw(self): return f"Drawing circle with radius {self.radius}"
def resize(self, factor): self.radius *= factor return f"Circle resized, new radius: {self.radius}"
class Square(Drawable): def __init__(self, side): self.side = side
def draw(self): return f"Drawing square with side {self.side}"
def resize(self, factor): self.side *= factor return f"Square resized, new side: {self.side}"
def render_shapes(shapes): """Function that works with any Drawable object""" for shape in shapes: print(shape.draw()) print(shape.resize(1.5))Java Interface Example
// Interfaceinterface Drawable { void draw(); void resize(double factor);}
// Implementationclass Circle implements Drawable { private double radius;
public Circle(double radius) { this.radius = radius; }
@Override public void draw() { System.out.println("Drawing circle with radius " + radius); }
@Override public void resize(double factor) { this.radius *= factor; System.out.println("Circle resized, new radius: " + radius); }}Advanced OOP Concepts
Composition vs Inheritance
Composition (“Has-a” relationship)
class Engine: def __init__(self, horsepower, fuel_type): self.horsepower = horsepower self.fuel_type = fuel_type self.running = False
def start(self): self.running = True return f"{self.horsepower}HP {self.fuel_type} engine started"
def stop(self): self.running = False return f"Engine stopped"
class Transmission: def __init__(self, type_name, gears): self.type = type_name self.gears = gears self.current_gear = 1
def shift_up(self): if self.current_gear < self.gears: self.current_gear += 1 return f"Shifted to gear {self.current_gear}"
class Car: def __init__(self, make, model, engine, transmission): self.make = make self.model = model self.engine = engine # Composition self.transmission = transmission # Composition
def start(self): return self.engine.start()
def accelerate(self): if self.engine.running: return self.transmission.shift_up() return "Start the engine first!"
# Usageengine = Engine(200, "Gasoline")transmission = Transmission("Manual", 6)car = Car("Honda", "Civic", engine, transmission)
print(car.start()) # Delegates to engineprint(car.accelerate()) # Uses transmissionWhen to use Composition vs Inheritance:
- Inheritance: “Is-a” relationship (Dog IS-A Animal)
- Composition: “Has-a” relationship (Car HAS-A Engine)
- Prefer composition for flexibility and loose coupling
Design Patterns in OOP
1. Singleton Pattern
class DatabaseConnection: _instance = None _connection = None
def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance
def __init__(self): if self._connection is None: self._connection = self._create_connection()
def _create_connection(self): # Simulate database connection return "Connected to database"
def get_connection(self): return self._connection
# Usagedb1 = DatabaseConnection()db2 = DatabaseConnection()print(db1 is db2) # True - same instance2. Factory Pattern
class AnimalFactory: @staticmethod def create_animal(animal_type, name): if animal_type.lower() == "dog": return Dog(name, "Mixed") elif animal_type.lower() == "cat": return Cat(name, "Mixed") elif animal_type.lower() == "duck": return Duck(name) else: raise ValueError(f"Unknown animal type: {animal_type}")
# Usageanimals = [ AnimalFactory.create_animal("dog", "Buddy"), AnimalFactory.create_animal("cat", "Whiskers"), AnimalFactory.create_animal("duck", "Donald")]
for animal in animals: print(animal.make_sound())3. Observer Pattern
class Subject: def __init__(self): self._observers = [] self._state = None
def attach(self, observer): self._observers.append(observer)
def detach(self, observer): self._observers.remove(observer)
def notify(self): for observer in self._observers: observer.update(self)
def set_state(self, state): self._state = state self.notify()
def get_state(self): return self._state
class Observer: def update(self, subject): raise NotImplementedError
class EmailNotifier(Observer): def update(self, subject): print(f"Email sent: State changed to {subject.get_state()}")
class SMSNotifier(Observer): def update(self, subject): print(f"SMS sent: State changed to {subject.get_state()}")
# Usagesubject = Subject()email_notifier = EmailNotifier()sms_notifier = SMSNotifier()
subject.attach(email_notifier)subject.attach(sms_notifier)
subject.set_state("Order Shipped") # Both notifiers will be updated4. Strategy Pattern
class PaymentStrategy: def pay(self, amount): raise NotImplementedError
class CreditCardPayment(PaymentStrategy): def __init__(self, card_number): self.card_number = card_number
def pay(self, amount): return f"Paid ${amount} using Credit Card ending in {self.card_number[-4:]}"
class PayPalPayment(PaymentStrategy): def __init__(self, email): self.email = email
def pay(self, amount): return f"Paid ${amount} using PayPal account {self.email}"
class BitcoinPayment(PaymentStrategy): def __init__(self, wallet_address): self.wallet_address = wallet_address
def pay(self, amount): return f"Paid ${amount} using Bitcoin wallet {self.wallet_address[:8]}..."
class ShoppingCart: def __init__(self): self.items = [] self.payment_strategy = None
def add_item(self, item, price): self.items.append((item, price))
def set_payment_strategy(self, strategy): self.payment_strategy = strategy
def checkout(self): total = sum(price for item, price in self.items) if self.payment_strategy: return self.payment_strategy.pay(total) return "No payment method selected"
# Usagecart = ShoppingCart()cart.add_item("Laptop", 999.99)cart.add_item("Mouse", 29.99)
# Use different payment strategiescart.set_payment_strategy(CreditCardPayment("1234567890123456"))print(cart.checkout())
cart.set_payment_strategy(PayPalPayment("user@example.com"))print(cart.checkout())Class Relationships
1. Association
Objects are related but independent.
class Student: def __init__(self, name): self.name = name self.courses = []
def enroll(self, course): self.courses.append(course) course.add_student(self)
class Course: def __init__(self, name): self.name = name self.students = []
def add_student(self, student): self.students.append(student)2. Aggregation
“Has-a” relationship where child can exist without parent.
class Department: def __init__(self, name): self.name = name self.employees = []
def add_employee(self, employee): self.employees.append(employee)
class Employee: def __init__(self, name): self.name = name # Employee can exist without Department3. Composition
“Has-a” relationship where child cannot exist without parent.
class House: def __init__(self): self.rooms = [Room("Living Room"), Room("Bedroom")]
def __del__(self): # When house is destroyed, rooms are destroyed too del self.rooms
class Room: def __init__(self, name): self.name = name # Room cannot exist without HouseSOLID Principles
For more more details and TypeScript examples, check SOLID Principles with Examples article.
1. Single Responsibility Principle (SRP)
A class should have only one reason to change.
# Bad: Multiple responsibilitiesclass User: def __init__(self, name, email): self.name = name self.email = email
def save_to_database(self): # Database logic pass
def send_email(self): # Email logic pass
# Good: Single responsibilityclass User: def __init__(self, name, email): self.name = name self.email = email
class UserRepository: def save(self, user): # Database logic pass
class EmailService: def send_email(self, user, message): # Email logic pass2. Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification.
class AreaCalculator: def calculate(self, shapes): total_area = 0 for shape in shapes: total_area += shape.area() # Extensible without modification return total_area
# Adding new shapes doesn't require modifying AreaCalculatorclass Pentagon(Shape): def __init__(self, side): self.side = side
def area(self): return 1.72 * self.side ** 23. Liskov Substitution Principle (LSP)
Objects of a superclass should be replaceable with objects of its subclasses.
class Bird: def fly(self): return "Flying"
class Sparrow(Bird): def fly(self): return "Sparrow flying"
class Penguin(Bird): def fly(self): raise Exception("Penguins can't fly") # Violates LSP
# Better designclass Bird: def move(self): raise NotImplementedError
class FlyingBird(Bird): def fly(self): return "Flying"
def move(self): return self.fly()
class Penguin(Bird): def swim(self): return "Swimming"
def move(self): return self.swim()4. Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they don’t use.
# Bad: Fat interfaceclass Worker: def work(self): pass
def eat(self): pass
class Robot(Worker): def work(self): return "Robot working"
def eat(self): pass # Robots don't eat - violates ISP
# Good: Segregated interfacesclass Workable: def work(self): pass
class Eatable: def eat(self): pass
class Human(Workable, Eatable): def work(self): return "Human working"
def eat(self): return "Human eating"
class Robot(Workable): def work(self): return "Robot working"5. Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
# Bad: High-level depends on low-levelclass EmailService: def send(self, message): # Send email pass
class NotificationManager: def __init__(self): self.email_service = EmailService() # Tight coupling
def send_notification(self, message): self.email_service.send(message)
# Good: Both depend on abstractionclass NotificationService: def send(self, message): raise NotImplementedError
class EmailService(NotificationService): def send(self, message): return f"Email sent: {message}"
class SMSService(NotificationService): def send(self, message): return f"SMS sent: {message}"
class NotificationManager: def __init__(self, notification_service): self.notification_service = notification_service # Dependency injection
def send_notification(self, message): return self.notification_service.send(message)
# Usageemail_manager = NotificationManager(EmailService())sms_manager = NotificationManager(SMSService())OOP Interview Questions and Answers
Q1: What is the difference between a class and an object?
A1: A class is a blueprint or template that defines the structure and behavior of objects, including attributes and methods. An object is a concrete instance of a class - an actual realization created from the class blueprint with specific values. For example, Car is a class, while my_car = Car("Toyota", "Camry", 2023) creates an object instance.
Q2: Explain the four pillars of OOP.
A2: The four pillars are:
- Encapsulation: Bundling data and methods together while hiding internal implementation details
- Inheritance: Allowing a class to inherit properties and methods from another class for code reuse
- Polymorphism: Enabling objects of different classes to be treated uniformly while maintaining their specific behaviors
- Abstraction: Hiding complex implementation details and exposing only essential features
Q3: What is the difference between method overloading and method overriding?
A3: Method overloading (compile-time polymorphism) is having multiple methods with the same name but different parameters in the same class. Method overriding (runtime polymorphism) is when a subclass provides a specific implementation for a method already defined in its parent class. Overloading happens within one class; overriding happens across inheritance hierarchy.
Q4: When should you use composition over inheritance?
A4: Use composition when you have a “has-a” relationship rather than an “is-a” relationship. Composition provides better flexibility, loose coupling, and easier testing. For example, a Car “has-a” Engine (composition) rather than a Car “is-a” Engine (inheritance). Prefer composition when you need to change behavior at runtime or avoid tight coupling between classes.
Q5: What is an abstract class and when would you use it?
A5: An abstract class is a class that cannot be instantiated and may contain abstract methods (methods without implementation). It serves as a base class that defines a contract for subclasses. Use abstract classes when you want to provide common functionality to related classes while forcing them to implement specific methods. For example, a Vehicle abstract class can define common attributes while forcing Car and Motorcycle subclasses to implement their own start_engine() method.
Q6: Explain the Single Responsibility Principle.
A6: The Single Responsibility Principle states that a class should have only one reason to change - it should have only one job or responsibility. For example, a User class should only handle user data, not database operations or email sending. Separate concerns into different classes like UserRepository for database operations and EmailService for sending emails. This makes code more maintainable and testable.
Q7: What is the difference between private, protected, and public access modifiers?
A7:
- Public: Accessible from anywhere (all classes)
- Protected: Accessible within the class and its subclasses
- Private: Accessible only within the class itself
In Python, these are conventions: _protected (single underscore) and __private (double underscore with name mangling). In Java, they are enforced keywords.
Q8: What is a design pattern? Name three common ones.
A8: A design pattern is a reusable solution to a commonly occurring problem in software design. Three common patterns are:
- Singleton: Ensures only one instance of a class exists
- Factory: Creates objects without specifying exact class to create
- Observer: Allows objects to notify other objects about state changes
Q9: Explain polymorphism with a real-world example.
A9: Polymorphism allows objects of different types to be treated uniformly. Real-world example: A payment system that accepts different payment methods (CreditCard, PayPal, Bitcoin). All implement a pay() method, but each executes it differently. The checkout system can call payment_method.pay(amount) without knowing the specific payment type - the correct implementation is called automatically based on the object type.
Q10: What is the Liskov Substitution Principle?
A10: The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. Subclasses must honor the contract of the parent class. For example, if you have a Bird class with a fly() method, creating a Penguin subclass that throws an error in fly() violates LSP because penguins can’t fly. Better design: separate FlyingBird and FlightlessBird classes.
Q11: What is the difference between aggregation and composition?
A11: Both represent “has-a” relationships, but differ in ownership:
- Aggregation: Child can exist independently of parent (e.g., Department has Employees; employees can exist without the department)
- Composition: Child cannot exist without parent (e.g., House has Rooms; rooms don’t exist without the house)
Composition implies stronger ownership and lifecycle dependency.
Q12: Explain the Open/Closed Principle with an example.
A12: The Open/Closed Principle states that classes should be open for extension but closed for modification. Example: An AreaCalculator class that calculates total area of shapes should not need modification when adding new shape types. Instead, new shapes inherit from a Shape base class and implement their own area() method. The calculator works with any shape without code changes.
Q13: What is multiple inheritance and what problems can it cause?
A13: Multiple inheritance allows a class to inherit from multiple parent classes. It can cause the “diamond problem” - when two parent classes have a method with the same name, which one should the child inherit? Python supports multiple inheritance and uses Method Resolution Order (MRO) to resolve conflicts. Java avoids this by not supporting multiple inheritance for classes (but allows multiple interface implementation).
Q14: What is dependency injection and why is it useful?
A14: Dependency injection is passing dependencies to a class from outside rather than creating them internally. Instead of class A { b = new B() }, use class A { constructor(b) { this.b = b } }. Benefits include:
- Loose coupling between classes
- Easier testing (can inject mock objects)
- Better flexibility (can swap implementations)
- Follows Dependency Inversion Principle
Q15: Explain the difference between static and instance methods.
A15:
- Instance methods: Operate on instance data, require an object to be called, access instance variables via
self(Python) orthis(Java) - Static methods: Belong to the class itself, don’t require an instance, can’t access instance variables, used for utility functions related to the class
Example: Car.get_wheel_count() (static) vs my_car.start_engine() (instance).
Q16: What is the Interface Segregation Principle?
A16: The Interface Segregation Principle states that clients should not be forced to depend on interfaces they don’t use. Instead of one large interface, create multiple smaller, specific interfaces. Example: Don’t create a Worker interface with work() and eat() methods that forces Robot class to implement eat(). Instead, create separate Workable and Eatable interfaces.
Q17: What is the difference between association and dependency?
A17:
- Association: A structural relationship where objects are connected and can exist independently (e.g., Student and Course - both can exist independently but are related)
- Dependency: A weaker relationship where one class temporarily uses another (e.g., a method parameter or local variable)
Association is “knows-a” or “has-a”, dependency is “uses-a” temporarily.
Q18: Explain the Strategy Pattern and when to use it.
A18: The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Use it when you have multiple ways of performing an operation and want to switch between them at runtime. Example: A ShoppingCart that can accept different payment strategies (CreditCard, PayPal, Bitcoin) without changing cart logic. The payment method can be set dynamically: cart.set_payment_strategy(PayPalPayment()).
Q19: What is tight coupling vs loose coupling?
A19:
- Tight coupling: Classes are highly dependent on each other; changes in one class require changes in another
- Loose coupling: Classes have minimal dependencies; changes in one class don’t affect others
Loose coupling is achieved through interfaces, dependency injection, and abstraction. Example: Instead of class A { b = new ConcreteB() } (tight), use class A { constructor(interface_b) } (loose).
Q20: What is the difference between an abstract class and an interface?
A20: Abstract Class:
- Can have both abstract and concrete methods
- Can have instance variables and constructors
- Supports single inheritance (in most languages)
- Use for “is-a” relationships with shared implementation
Interface:
- Only method signatures (traditionally; modern languages allow default methods)
- No instance variables (only constants)
- Supports multiple implementation
- Use for “can-do” capabilities and contracts
Example: Animal (abstract class with shared code) vs Flyable (interface defining flying capability).