What are the SOLID principles and how are they applied in software development?
sobes.tech AI
Answer from AI
SOLID is a set of five principles of object-oriented design aimed at creating more flexible, maintainable, and scalable code.
- Single Responsibility Principle (SRP): A class should have only one reason to change.
- Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification.
- Liskov Substitution Principle (LSP): Subtypes should be replaceable for their base types without affecting the correctness of the program.
- Interface Segregation Principle (ISP): Clients should not depend on interfaces they do not use.
- 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.
Application in development:
-
SRP: Dividing functionality into small, specialized classes (e.g., a separate class for network operations, another for database operations).
-
OCP: Using protocols (interfaces) and abstract classes to extend behavior without changing existing code (e.g., implementing strategies).
// Example of OCP with protocol protocol PaymentStrategy { func processPayment(amount: Double) } class CreditCardPayment: PaymentStrategy { func processPayment(amount: Double) { // Credit card payment logic } } class PaypalPayment: PaymentStrategy { func processPayment(amount: Double) { // PayPal payment logic } } class PaymentProcessor { let strategy: PaymentStrategy init(strategy: PaymentStrategy) { self.strategy = strategy } func makePayment(amount: Double) { strategy.processPayment(amount: amount) } } -
LSP: Guarantee that inheriting classes do not violate the expected behavior of the base class (e.g., when working with collections).
// Example violating LSP class Bird { func fly() { print("Flying") } } class Ostrich: Bird { // Overriding fly, which does not make sense for an ostrich // Violates user expectations that Bird can fly. override func fly() { fatalError("Ostrich cannot fly") } } let birds: [Bird] = [Bird(), Bird(), Ostrich()] // During iteration, calling fly() on Ostrich may cause a crash for bird in birds { bird.fly() } -
ISP: Creating small, specific protocols instead of one large one (e.g., a separate protocol for data loading, another for displaying).
// Example of ISP protocol DataLoading { func loadData() } protocol DataDisplaying { func displayData() } class ViewController: DataLoading, DataDisplaying { func loadData() { // Data loading } func displayData() { // Data display } } class DataLoader: DataLoading { func loadData() { // Only data loading } } -
DIP: Using dependency injection to invert dependencies (e.g., passing dependencies through the constructor).
// Example of DIP protocol NetworkService { func fetchData() } class APIService: NetworkService { func fetchData() { // Implementation of fetching data from API } } class DataManager { let networkService: NetworkService // Dependency on abstraction init(networkService: NetworkService) { self.networkService = networkService } func getData() { networkService.fetchData() } } // Injecting dependency let api = APIService() let manager = DataManager(networkService: api) manager.getData()