Ответ
Принцип открытости/закрытости (OCP) — второй принцип SOLID. Он гласит: «Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации».
- «Закрыты для модификации»: Исходный код стабильной, протестированной сущности не должен изменяться для добавления новой функциональности.
- «Открыты для расширения»: Поведение сущности можно расширить, создавая новые классы, реализующие её интерфейс (абстракцию).
Проблема (нарушение OCP):
class Logger {
func log(message: String, to type: String) {
if type == "console" {
print(message)
} else if type == "file" {
// Запись в файл
}
// Добавление нового типа лога (например, "network") требует изменения этого метода!
}
}
Решение (соблюдение OCP через абстракцию):
// Абстракция, закрытая для модификации
protocol LogDestination {
func write(_ message: String)
}
// Расширения, открытые для добавления
class ConsoleDestination: LogDestination {
func write(_ message: String) { print(message) }
}
class FileDestination: LogDestination {
func write(_ message: String) { /* Запись в файл */ }
}
class NetworkDestination: LogDestination {
func write(_ message: String) { /* Отправка по сети */ }
}
// Класс Logger теперь стабилен
class Logger {
private let destination: LogDestination
init(destination: LogDestination) {
self.destination = destination
}
func log(message: String) {
destination.write(message)
}
}
// Использование
let logger = Logger(destination: NetworkDestination())
logger.log("Test message")
Выгода: Чтобы добавить новый способ логирования, мы создаем новый класс, реализующий LogDestination, не трогая существующий код Logger, ConsoleDestination или FileDestination. Это снижает риск появления новых ошибок в уже работающем коде.
Ответ 18+ 🔞
Да ты посмотри на эту дичь, блядь! Второй принцип SOLID, а звучит как инструкция к какому-то ёбаному сейфу. «Открыт для расширения, но закрыт для модификации». Ну, типа, ты можешь к нему новые приблуды цеплять, но сам корпус долбить отверткой — низя, блядь. Иначе весь этот ваш хрупкий мир из говна и палок развалится.
Вот смотри, классический пример говнокода, который все пишут, пока не прилетит по шапке. Делают они класс Logger, а в нём метод, который решает, куда лог пихнуть.
class Logger {
func log(message: String, to type: String) {
if type == "console" {
print(message)
} else if type == "file" {
// Запись в файл
}
// Добавление нового типа лога (например, "network") требует изменения этого метода!
}
}
Ну и что тут такого, спросит какой-нибудь юный пирожок? А то, сука, что завтра приходит менеджер и орёт: «Хочу логи в телеграм-бот слать, ёпта!». И ты, блядь, лезешь в этот метод, который уже, может, сто раз протестирован и работает, и начинаешь там тыкать:
else if type == "telegram" { ... }
А потом ещё захотят в Slack, а потом в БД, а потом на печать, в рот меня чих-пых! И с каждым разом ты рискуешь сломать то, что уже работало. Это ж как Герасим, который всех подряд мочил, только ты сам себе Герасим, и мочишь свой же рабочий код, блядь!
А теперь, внимание, магия, ёпта! Делаем по-взрослому, с абстракцией.
Сначала объявляем протокол — это такая бумажка с печатью, которая говорит: «Всё, что умеет write(_ message: String), может быть местом для логов». Закрыли тему нахуй.
protocol LogDestination {
func write(_ message: String)
}
А дальше — вжух, вжух, вжух! — делаем сколько угодно реализаций. Это и есть «открыто для расширения». Хочешь в консоль?
class ConsoleDestination: LogDestination {
func write(_ message: String) { print(message) }
}
Хочешь в файл? Пожалуйста, на тебе файл!
class FileDestination: LogDestination {
func write(_ message: String) { /* Запись в файл */ }
}
Пришла дикая идея слать логи на Марс через сеть? Да хуй с ним, делаем новый класс, а старый код даже не чихнёт!
class NetworkDestination: LogDestination {
func write(_ message: String) { /* Отправка по сети */ }
}
И сам Logger превращается в элегантную, блядь, недотрогу. Он больше не знает НИХУЯ о том, куда пишутся логи. Он просто берёт то, что ему дали, и говорит: «На, запиши».
class Logger {
private let destination: LogDestination
init(destination: LogDestination) {
self.destination = destination
}
func log(message: String) {
destination.write(message)
}
}
Использование? Элементарно, Ватсон!
let logger = Logger(destination: NetworkDestination())
logger.log("Test message")
Выгода-то какая, блядь? А такая, что если завтра понадобится логировать в облако амазона, ты не полезешь в Logger, ConsoleDestination или FileDestination с криками «мама, прости!». Ты просто создашь новый класс AmazonCloudDestination, реализуешь один ёбаный метод write, и всё. Существующий код останется нетронутым, как девственница в монастыре. Риск накосячить — ноль ебать. Вот что значит следовать принципу, а не писать код, как бомж в подворотне.