Comments 24
Обычно это реализуется с помощью полиморфизма.
Это про реализацию OCP. Вообще OCP не ограничивается только рамками ООП, а пример в виде полиморфизма это неявно предполагает.
То есть, можно делать модули, что бы мы не понимали под этим, в соответствии с принципом OCP, и при этом это не будет ООП вообще. И будет тоже обычно, но с помощью функций высшего порядка, например.
Полиморфизм называется явной чертой ООП, но он не характерен только для ООП.
Любая вариация интерфейсов, вызовов функций с разными параметрами является полиморфизмом в широком смысле.
когда вы единственный потребитель своего кода (разработка корпоративного программного обеспечения)
Корпоративное программное обеспечение не обязательно связано с кучей потребителей или с одним. Всяко бывает.
Вот только лет через 10 после того, как сущности постоянно расширяли, не изменяя, для каждой операции получится штук по 5-10 слегка отличающихс классов/сервисов/модулей, которые расширяют друг друга разными способами, любое действие проходит через множество слоев и точек расширения, зависимости всех этих сущностей представляют собой лапшу.
И в результате на понимание 'где и как что-то должно происходить?' уходит просто куча времени или оно становится вооще невозможным.
Оно такое грустное...
Вот как выглядит современное решение задачи.
trait Draw {
fn draw(&self) -> Result<(), &str>;
}
И его расширение:
impl Draw for Cirle {
fn draw(&self) -> Result<(), &str>{
...
}
}
impl Draw for Square {
fn draw(&self) -> Result<(), &str>{
...
}
}
У меня ощущение, что всё ООП - это такие речекряки, в стиле шляпы, слетающей мимо станции с головы едующего.
В том, что вам не надо наследоваться. Вы можете просто реализовывать то, что нужно для произвольного типа (свои трейты для любых типов, любые трейты для своих типов). Вся вот эта бесконечная жвачка на тему того, должен ли красный квадрат наследоваться от класса красных или от класса квадратов просто исчезает. `impl Red`, `impl Square` и готов.
Интерфейсы в ООП добавили сильно потом, от безысходности.
Но трейты, например, могут быть generic'ами, иметь типопараметры высшего порядка. Собственно, если бы вместо ООП были интерфейсы, то жизнь и в java, и в C++ была бы сильно проще.
Когда говорим про ООП надо уточнять язык. ООП ala Java версии 1998 года - это очень ограниченная модель программирования. Но, например, в Common List (CLOS) образца 1991 года или Smalltalk из 80х можно было делать вещи значительно более интересные чем то, что вы показали.
Мой скромный опыт показывает, что вопрос приоритета "YAGNI" vs "OCP" сильно сложнее вопроса "внешних" и "внутренних" клиентов кода.
Часто вижу, что многие критически важные части кода не планируются к расширению, но обречены на него, и поэтому растут эволюционно, как придется, раз за разом откладывая вопрос обобщения и встраивания в структуру.
То есть порядок примерно такой:
Есть основной алгоритм А, но у него появляется вариация Б, связанная с типом входных данных. Ты пишешь первый if
Появляется вариант В, перед тобой встает выбор - переделать на switch или каскад if или все же попытаться обобщить и сделать через полиморфизм
2.1 Ты выбираешь вариант сделать простенько и добавляешь еще один switch
2.2 Ты выбираешь вариант сделать нормально и вынести обобщение в отдельные классы для будущих поколений
И тут возможны варианты
3.1 Никогда до тепловой смерти вселенной ты не возвращаешься к этому куску кода. Он хорошо работает и только в варианте 2.2 ты потратил на него немного больше времени
3.2 К тебе приходят и просят сделать варианты Г и Д.
История может повторяться Х раз, вынуждая тебя каждый раз либо принять решения о новой декомпозиции (что каждый раз сделать все сложнее, так как все больше элементов вокруг) либо оставить как есть, плодя новый пластелин.
Тут все очень зависит от отношения к риску в команде, кто-то может быть не против отрефакторить 20 if в классы, а другие скажут что код трогать вообще нельзя так что добавляй 2й. И вот так неожиданно для себя вы обнаруживаете что глючный CI влияет на архитектуру приложения
Именно так.
С опытом накапливается некоторое понимание, когда расширяемость нужно закладывать сразу, по сути правильный подход - перед реализацией каждой задачи сначала провести рефакторинг. То есть если класс не подходит под расширение - нужно сначала адаптировать его, и только потом писать новый кейс.
Кроме того, многие штуки работают по 80% результата / 20% усилий - заложить расширяемость довольно дешево.
YAGNI, который говорит, что этот оператор switch допустим, если результирующий код прост и его легко понять и поддерживать
Принцип не применим, если изначально держится на субъективных параметрах.
Сначала программисту показалось что его реализация читается и поддерживается легко, поэтому наворотил неподдерживаемого нечитабельного кода (ему кажется, что норм).
Потом ему показалось что требование меняться не будет, поэтому он захардкодил реализацию, сделав её замену трудной задачей.
В итоге мы получили неподдерживаемую нечитаемую реализацию, которую трудно заменить, страдаем и несём убытки, при этом разработчик считает (не без оснований — ему ведь действительно казалось) что он принимал решение в соответствии со здравым принципом.
Принцип должен стандартизировать логику принятия решения, защищать от субъективного самодурства, YAGNI этого не делает.
YAGNI — вредный принцип, потому что полагается на субъективность, при этом даёт ложноё ощущение, что ты принимаешь разумное решение.
OCP vs YAGNI — это формальные требования vs субъективные предположения.
OCP тоже субъективен до основания.
public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw()
{
/* … */
}
}
А если только показалось, что Draw достаточно? Вводить DrawableShape, DescribableShape( и модифицировать существующий код, о ужас)? А потом третий метод?
А если только показалось, что Draw достаточно
Именно поэтому OCP предписывает делать не Shape с методом Draw, не DrawableShape, а "Shape implements Drawable".
Если Draw в будущем окажется недостаточно — "Shape implements Drawable, SomeFeatureAble".
А теперь ещё пройтись по всем наследникам и его заимплементить
del
OCP против YAGNI