Как стать автором
Обновить

Комментарии 24

Обычно это реализуется с помощью полиморфизма.

Это про реализацию OCP. Вообще OCP не ограничивается только рамками ООП, а пример в виде полиморфизма это неявно предполагает.

То есть, можно делать модули, что бы мы не понимали под этим, в соответствии с принципом OCP, и при этом это не будет ООП вообще. И будет тоже обычно, но с помощью функций высшего порядка, например.
Спасибо, что выделил время на статью. Автор оригинала приводил примеры в стиле ООП, поэтому, как я понял, он акцентировал внимание именно на полиморфизме. Вы могли заметить, что часто шло перечисление классы/модули/функции/сервисы и так далее. При этом если глянуть в википедию, там следующее: «Полиморфизм в языках программирования и теории типов — способность функции обрабатывать данные разных типов». Поэтому сам полиморфизм не ограничен рамками ООП.
А, да, логично. Согласен.

Полиморфизм называется явной чертой ООП, но он не характерен только для ООП.

Любая вариация интерфейсов, вызовов функций с разными параметрами является полиморфизмом в широком смысле.

Да я уже чуть выше согласился с комментарием на эту же тему.
когда вы единственный потребитель своего кода (разработка корпоративного программного обеспечения)


Корпоративное программное обеспечение не обязательно связано с кучей потребителей или с одним. Всяко бывает.
Согласен. Ну это просто как пример был.
Тут на самом деле интересный вопрос, что такое использование. У меня в конкретном проекте код используется командами. Это не внешние команды — они даже внутри одного со мной отдела. Но у них свои задачи, отличные от моих. И у каждой из команд свои потребности. Они наш софт даже не дописывают, а просто конфигурируют. И с одной стороны, мы единственные, кто код меняет, а с другой — менять надо именно так, чтобы код расширялся, но при этом не ломался, чтобы минизировать эффект от изменений на остальные части проекта. Т.е. OCP к нам применим в чистом виде.

Вот только лет через 10 после того, как сущности постоянно расширяли, не изменяя, для каждой операции получится штук по 5-10 слегка отличающихс классов/сервисов/модулей, которые расширяют друг друга разными способами, любое действие проходит через множество слоев и точек расширения, зависимости всех этих сущностей представляют собой лапшу.

И в результате на понимание 'где и как что-то должно происходить?' уходит просто куча времени или оно становится вооще невозможным.

Совершенно не исключаю. OCP это принцип, он описывает, к чему надо стремиться, а не как этого достичь в каждом конкретном случае. Поэтому может и не получиться. Но если об этом не думать вообще — обычно через 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>{
        ...     
     }
}  


У меня ощущение, что всё ООП - это такие речекряки, в стиле шляпы, слетающей мимо станции с головы едующего.

А чем это фактически отличается от введения интерфейса Drawable с методом Draw? Который вполне себе ООП.

В том, что вам не надо наследоваться. Вы можете просто реализовывать то, что нужно для произвольного типа (свои трейты для любых типов, любые трейты для своих типов). Вся вот эта бесконечная жвачка на тему того, должен ли красный квадрат наследоваться от класса красных или от класса квадратов просто исчезает. `impl Red`, `impl Square` и готов.

Интерфейсы в ООП добавили сильно потом, от безысходности.

Но трейты, например, могут быть generic'ами, иметь типопараметры высшего порядка. Собственно, если бы вместо ООП были интерфейсы, то жизнь и в java, и в C++ была бы сильно проще.

Интерфейсы в ООП добавили сильно потом, от безысходности.
Точнее, в те языки, которые стали распространенными.

Multiple Dispatch, как я понимаю, в первых OOP языках (Smalltalk?), на которых его придумывали, таки был.

Когда говорим про ООП надо уточнять язык. ООП ala Java версии 1998 года - это очень ограниченная модель программирования. Но, например, в Common List (CLOS) образца 1991 года или Smalltalk из 80х можно было делать вещи значительно более интересные чем то, что вы показали.

Мой скромный опыт показывает, что вопрос приоритета "YAGNI" vs "OCP" сильно сложнее вопроса "внешних" и "внутренних" клиентов кода.

Часто вижу, что многие критически важные части кода не планируются к расширению, но обречены на него, и поэтому растут эволюционно, как придется, раз за разом откладывая вопрос обобщения и встраивания в структуру.

То есть порядок примерно такой:

  1. Есть основной алгоритм А, но у него появляется вариация Б, связанная с типом входных данных. Ты пишешь первый if

  2. Появляется вариант В, перед тобой встает выбор - переделать на switch или каскад if или все же попытаться обобщить и сделать через полиморфизм

    2.1 Ты выбираешь вариант сделать простенько и добавляешь еще один switch

    2.2 Ты выбираешь вариант сделать нормально и вынести обобщение в отдельные классы для будущих поколений

  3. И тут возможны варианты

    3.1 Никогда до тепловой смерти вселенной ты не возвращаешься к этому куску кода. Он хорошо работает и только в варианте 2.2 ты потратил на него немного больше времени

    3.2 К тебе приходят и просят сделать варианты Г и Д.

  4. История может повторяться Х раз, вынуждая тебя каждый раз либо принять решения о новой декомпозиции (что каждый раз сделать все сложнее, так как все больше элементов вокруг) либо оставить как есть, плодя новый пластелин.

Тут все очень зависит от отношения к риску в команде, кто-то может быть не против отрефакторить 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".

А теперь ещё пройтись по всем наследникам и его заимплементить

Вероятнее всего где-то выше уровнем (в супертипе слоя, или ещё где-то). Врядли прям для каждого типа будет своя индивидуальная реализация, скорее всего она будет обобщена, и реализацию вы напишете в одном месте.


Но да, новые фичи требуют писать новый код, а вы как хотели?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации