Single Responsibility Principle
1. Meaning
A class should have only one responsibility, which means the class should be highly cohesive and implement strongly related logic.
2. Example — BAD
class PlaceOrder
def initialize(product)
@product = product
end
def run
# 1. Stock availability
# 2. Payment process
# 3. Shipment process
end
end
Example — GOOD
class PlaceOrder
def initialize(product)
@product = product
end
def run
StockAvailability.new(@product).run
ProductPayment.new(@product).run
ProductShipment.new(@product).run
end
end
3. How to recognize code smell?
- More than one contextually separated piece of code within a single class
- Large setup in tests; TDD can help detect SRP violations
4. Benefits
- Separated classes responsible for a use case can be reused elsewhere
- Separated classes can be tested independently
Open/Closed Principle
1. Meaning
A class should be open for extension and closed for modification. Extend behavior without modifying the existing implementation.
2. Example — BAD
class Logger
def initialize(logging_form)
@logging_form = logging_form
end
def log(message)
puts message if @logging_form == "console"
File.write("logs.txt", message) if @logging_form == "file"
end
end
Example — GOOD
class EventTracker
def initialize(logger: ConsoleLogger.new)
@logger = logger
end
def log(message)
@logger.log(message)
end
end
class ConsoleLogger
def log(message)
puts message
end
end
class FileLogger
def log(message)
File.write("logs.txt", message)
end
end
3. How to recognize code smell?
- Class X directly references class Y from within its code base
- Complex if-else or switch statements
4. Benefits
- Functionality can be extended with a separate class without changing class X
- Code is loosely coupled
- Injected class Y can be easily mocked in tests
Liskov Substitution Principle
1. Meaning
Derived classes should not change the behavior of the base class. If Y is a subclass of X, instances referencing X should be able to reference Y as well.
2. Example — BAD
class Rectangle
def initialize(width, height)
@width, @height = width, height
end
def set_width(width)
@width = width
end
def set_height(height)
@height = height
end
end
class Square < Rectangle
# LSP violation: inherited class
# changes parent behavior
end
Example — GOOD
class Rectangle
def initialize(width, height)
@width, @height = width, height
end
end
# Prefer a shared abstraction whose contract
# both shapes can honor without changing
# inherited behavior unexpectedly.
3. How to recognize code smell?
- Modification of inherited behavior in a subclass
- Exceptions raised in overridden inherited methods
- A type looks substitutable but needs special behavior to work
4. Benefits
- Avoid unexpected and incorrect results
- Clear distinction between shared inherited interface and extended functionality
Interface Segregation Principle
1. Meaning
Clients should not depend on interfaces or methods that they do not use.
2. Example — BAD
class Car
def open
end
def start_engine
end
def change_engine
end
end
class Drive
def take_a_ride(car)
car.open
car.start_engine
end
end
class Mechanic
def repair(car)
car.open
car.change_engine
end
end
Example — GOOD
# Split the large interface into
# smaller client-specific interfaces.
class DriverCar
def open
end
def start_engine
end
end
class MechanicCar
def open
end
def change_engine
end
end
3. How to recognize code smell?
- One fat interface is implemented by many classes, while none use 100% of its methods
- The fat interface should be split into smaller interfaces suitable for client needs
4. Benefits
- Highly cohesive code
- Avoids coupling between classes using one fat interface
- Clear separation of business logic by grouping responsibilities into separate interfaces
Dependency Inversion Principle
1. Meaning
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.
2. Example — BAD
class EventTracker
def initialize
# Low-level class is created
# directly inside high-level class
@logger = ConsoleLogger.new
end
def log(message)
@logger.log(message)
end
end
Example — GOOD
class EventTracker
def initialize(logger: ConsoleLogger.new)
# Dependency injection
# makes the high-level class
# independent of a concrete logger
@logger = logger
end
def log(message)
@logger.log(message)
end
end
3. How to recognize code smell?
- Instantiation of low-level modules in high-level ones
- Direct calls to low-level module/class methods
4. Benefits
- Increases reusability of higher-level modules by making them independent of lower-level modules
- Injected classes can be easily mocked in tests
Original PDF Pages
The original five pages are preserved below so the source layout and examples remain available alongside the converted HTML content.