Ответ
SOLID — это набор из пяти принципов объектно-ориентированного дизайна, направленных на создание поддерживаемого и расширяемого кода.
1. S (Single Responsibility Principle — Принцип единственной ответственности)
Класс должен иметь одну и только одну причину для изменения.
// НАРУШЕНИЕ: Класс занимается и логикой, и сохранением, и валидацией.
class UserManager {
func authenticate(user: String, pass: String) { /* ... */ }
func saveToDatabase(user: User) { /* ... */ }
func validateEmail(_ email: String) -> Bool { /* ... */ }
}
// СОБЛЮДЕНИЕ: Ответственности разделены.
class UserAuthenticator {
func authenticate(credentials: Credentials) -> Bool { /* ... */ }
}
class UserRepository {
func save(user: User) throws { /* ... */ }
func fetch(userId: String) -> User? { /* ... */ }
}
class EmailValidator {
func validate(_ email: String) -> Bool { /* ... */ }
}
2. O (Open/Closed Principle — Принцип открытости/закрытости)
Классы должны быть открыты для расширения, но закрыты для модификации.
protocol ReportGenerator {
func generateReport() -> String
}
class PDFReportGenerator: ReportGenerator {
func generateReport() -> String { return "PDF Report" }
}
class HTMLReportGenerator: ReportGenerator {
func generateReport() -> String { return "HTML Report" }
}
class ReportProcessor {
// Класс закрыт для модификации (не меняем его код),
// но открыт для расширения (можно добавить новый тип генератора).
func processReport(using generator: ReportGenerator) {
let report = generator.generateReport()
print("Processing: (report)")
}
}
3. L (Liskov Substitution Principle — Принцип подстановки Барбары Лисков)
Объекты базового класса должны быть заменяемы объектами подклассов без изменения корректности программы.
// Базовый класс и подклассы, которые его корректно заменяют.
protocol Shape {
var area: Double { get }
}
struct Rectangle: Shape {
let width, height: Double
var area: Double { width * height }
}
struct Circle: Shape {
let radius: Double
var area: Double { .pi * radius * radius }
}
// Функция работает с любым Shape, соблюдая LSP.
func printArea(of shape: Shape) {
print("Area is: (shape.area)")
}
4. I (Interface Segregation Principle — Принцип разделения интерфейса)
Клиенты не должны зависеть от методов, которые они не используют.
// НАРУШЕНИЕ: Многофункциональный протокол заставляет SimplePrinter реализовывать ненужный метод.
protocol MultiFunctionDevice {
func print(document: String)
func scan() -> Data
func fax(document: String)
}
// СОБЛЮДЕНИЕ: Протоколы разделены по функциям.
protocol Printable {
func print(document: String)
}
protocol Scannable {
func scan() -> Data
}
class SimplePrinter: Printable { // Реализует только нужное
func print(document: String) { /* ... */ }
}
class Photocopier: Printable, Scannable { // Реализует несколько ролей
func print(document: String) { /* ... */ }
func scan() -> Data { /* ... */ }
}
5. D (Dependency Inversion Principle — Принцип инверсии зависимостей)
Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
// Абстракция (протокол)
protocol DataStorage {
func save(data: Data, forKey key: String)
func loadData(forKey key: String) -> Data?
}
// Низкоуровневый модуль (деталь реализации)
class UserDefaultsStorage: DataStorage {
func save(data: Data, forKey key: String) { UserDefaults.standard.set(data, forKey: key) }
func loadData(forKey key: String) -> Data? { UserDefaults.standard.data(forKey: key) }
}
class KeychainStorage: DataStorage { /* ... */ }
// Высокоуровневый модуль зависит от абстракции, а не от конкретного хранилища.
class SettingsManager {
private let storage: DataStorage // Зависимость от протокола
// Внедрение зависимости через инициализатор (Dependency Injection)
init(storage: DataStorage = UserDefaultsStorage()) {
self.storage = storage
}
func saveSettings(_ settings: Settings) {
let data = try! JSONEncoder().encode(settings)
storage.save(data: data, forKey: "userSettings")
}
} Ответ 18+ 🔞
А, SOLID, ёпта! Ну, классика, блядь! Пять этих принципов, которые все вроде как знают, но соблюдают их так, будто это инструкция по сборке мебели из Икеи — вроде читаешь, а на выходе всё равно получается кривой табурет, на который сесть страшно. Давай разберём, что это за звери такие, но без заумного официоза, а по-человечьи.
S — Single Responsibility Principle (Принцип единственной ответственности)
Слушай, суть проще пареной репы: один класс — одна работа. Не надо делать из него швейцарский нож, который и бутерброд намажет, и гвоздь забьёт, и в жопу почешет. Вот смотри, как делать НЕ НАДО:
class UserManager {
func authenticate(user: String, pass: String) { /* ... */ }
func saveToDatabase(user: User) { /* ... */ }
func validateEmail(_ email: String) -> Bool { /* ... */ }
}
Этот UserManager — просто маньяк-универсал, блядь! Он и логин проверяет, и в базу пихает, и почту валидирует. Представь, база данных поменялась — придётся лезть в этот класс и ковырять его. Почтовый стандарт обновился — опять за него. Полный пиздец, одним словом. А должно быть вот так:
class UserAuthenticator {
func authenticate(credentials: Credentials) -> Bool { /* ... */ }
}
class UserRepository {
func save(user: User) throws { /* ... */ }
func fetch(userId: String) -> User? { /* ... */ }
}
class EmailValidator {
func validate(_ email: String) -> Bool { /* ... */ }
}
Каждый занят своим делом. Один аутентифицирует, другой сохраняет, третий почту тыкает палкой. И если что-то сломается — сразу понятно, в каком именно чуваке искать проблему. Красота, ёпта!
O — Open/Closed Principle (Принцип открытости/закрытости)
Звучит как оксюморон, но смысл гениален: твой код должен быть как хороший конструктор — чтобы добавить новую детальку, не нужно ломать уже собранную башню. Расширяй, но не переделывай.
protocol ReportGenerator {
func generateReport() -> String
}
class PDFReportGenerator: ReportGenerator {
func generateReport() -> String { return "PDF Report" }
}
class HTMLReportGenerator: ReportGenerator {
func generateReport() -> String { return "HTML Report" }
}
class ReportProcessor {
func processReport(using generator: ReportGenerator) {
let report = generator.generateReport()
print("Processing: (report)")
}
}
Видишь? ReportProcessor — он тупой как пробка, но в хорошем смысле. Ему похуй, какой генератор ты ему сунешь. PDF, HTML, хоть отчёт на бересте. Он просто говорит: «Дай сюда твой генератор, я с ним поработаю». И не надо его самого каждый раз переписывать. Захотел добавить вывод в Excel — просто делаешь новый класс ExcelReportGenerator, и всё. Сам процессор даже не чихнёт.
L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков)
Это про то, что если у тебя есть, условно, «Птица», и от неё унаследованы «Воробей» и «Пингвин», то везде, где в коде ожидается «Птица», должен спокойно работать и воробей, и пингвин. А если пингвин не умеет летать и ломает всю логику — значит, наследование было ебаным идиотизмом.
protocol Shape {
var area: Double { get }
}
struct Rectangle: Shape {
let width, height: Double
var area: Double { width * height }
}
struct Circle: Shape {
let radius: Double
var area: Double { .pi * radius * radius }
}
func printArea(of shape: Shape) {
print("Area is: (shape.area)")
}
Вот тут всё чисто. Функция printArea принимает любую фигуру. Прямоугольник, круг, хоть треугольник — ей по барабану. Главное, чтобы площадь была. Если подсунешь что-то, что не умеет считать площадь — вот тогда и будет пиздец, нарушение принципа.
I — Interface Segregation Principle (Принцип разделения интерфейса)
Представь, ты покупаешь микроволновку, а в инструкции к ней на трёх страницах расписано, как ещё и стирать в ней бельё, и кофе молоть. Ты этим никогда пользоваться не будешь, но читать пришлось. Вот это и есть нарушение. Не заставляй класс реализовывать методы, которые ему нахуй не сдались.
// ТАК ДЕЛАТЬ — ПИЗДЕЦ КАК НЕ НАДО.
protocol MultiFunctionDevice {
func print(document: String)
func scan() -> Data
func fax(document: String)
}
Зачем простому принтеру реализовывать scan и fax? Он же только печатать должен! Это как заставлять таксиста ещё и двигатель тебе починить по дороге. Бред.
// А вот так — красиво и правильно.
protocol Printable {
func print(document: String)
}
protocol Scannable {
func scan() -> Data
}
class SimplePrinter: Printable {
func print(document: String) { /* ... */ } // И всё! Никакого лишнего геморроя.
}
class Photocopier: Printable, Scannable {
func print(document: String) { /* ... */ }
func scan() -> Data { /* ... */ } // А этот уже умеет и то, и другое. Молодец.
}
Каждый берёт только то, что ему нужно. Никакого насилия.
D — Dependency Inversion Principle (Принцип инверсии зависимостей)
Самый, пожалуй, важный для архитектуры. Высокоуровневые штуки (логика приложения) не должны зависеть от низкоуровневых (база данных, сеть, файловая система). Оба должны зависеть от какой-то абстракции (протокола, интерфейса).
protocol DataStorage {
func save(data: Data, forKey key: String)
func loadData(forKey key: String) -> Data?
}
class UserDefaultsStorage: DataStorage {
func save(data: Data, forKey key: String) { UserDefaults.standard.set(data, forKey: key) }
func loadData(forKey key: String) -> Data? { UserDefaults.standard.data(forKey: key) }
}
class KeychainStorage: DataStorage { /* ... */ }
class SettingsManager {
private let storage: DataStorage // Смотри-ка! Зависит от протокола, а не от UserDefaults.
init(storage: DataStorage = UserDefaultsStorage()) {
self.storage = storage
}
func saveSettings(_ settings: Settings) {
let data = try! JSONEncoder().encode(settings)
storage.save(data: data, forKey: "userSettings") // Ему всё равно, что там под капотом.
}
}
Вот в чём магия, блядь! SettingsManagerу похуй, куда сохраняются данные — в UserDefaults, в Keychain, в облако или на бумажку. Он просто тыкает в абстрактное DataStorage. Захотел поменять хранилище — подсунул другую реализацию в инициализатор. И ни одна строчка в самом менеджере не изменилась. Это и есть гибкость, ёпта!
Вот и весь SOLID, если без академического бреда. Соблюдаешь эти принципы — код живёт долго и счастливо, не соблюдаешь — превращается в монолитный говнокод, который все боятся трогать. Выбор за тобой.