воскресенье, 29 ноября 2015 г.

Значимые типы и ООП (в дотнете)

Не в первый раз встретился в интернетах с мнением, что значимые типы в дотнете как-то ортогональны ООП, что ООП совсем не про них.
Обидно стало за милые сердцу структурки и руки снова добрались до блога.

Самая важная вещь, на мой взгляд, которая определяет ООП — это инкапсуляция. Всё остальное придумано для удобства и совершенно опционально.

Например, наследование. Ещё много лет назад многие специалисты говорили: остерегайтесь его. Не используйте "на всякий случай". А когда используете — будьте способны дать себе отчёт в том, почему это необходимо.
Вкратце, основная причина такого мнения в том, что наследование создаёт очень сильную связь между сущностями, а при развитии кода или изменении требований такие связи не редко приходится ломать. А сильные связи, обросшие ещё и многолетним "энтерпрайзным" кодом, ломать настолько тяжело, что порой представляется даже невозможным.

Или полиморфизм. Это чистой воды, да, крайне удобный, но, извините меня, всего лишь синтаксический сахар.

Тут я, на всякий случай, оговорюсь про традиционные, мейнстримовые языки, ибо с нетрадиционными к стыду своему не знаком; и, чтобы уж совсем облегчить свою задачу, — ограничусь ориентированными под дотнет языками.

Так вот, именно инкапсуляция, то есть возможность объединять данные (и методы доступа к ним или их обработки) в объект и есть краеугольный камень ООП. Остальные "столпы" зиждутся на нём.

Я в своих рассуждениях исхожу из такой точки зрения, потому что мне, кажется, удаётся успешно избегать наследования (кроме самых редких и "канонических" случаев) и даже выходит обеспечивать полиморфизм не языковыми, а библиотечными средствами (например, кодогенерацией через System.Linq). А вот без инкапсуляции я чувствовал бы себя как без рук.

Да что я, вот в JavaScript и полиморфизм, и наследование можно эмулировать достаточно просто. А вот для сокрытия данных приходится применять трюк со специальной конструирующей функцией, которая может привести новичков в ступор. Однако, инкапсуляция кажется настолько важной (хотя она и не обязательна), что этим обычно всегда пользуются, как я могу судить.

Теперь должно быть видно, что раз инкапсуляция применима к значимым типам, то и значимые типы в смысле ООП полноценные игроки на поле дотнета.

Посудите сами: DateTime, CancellationToken, ImmutableArray<>, … Имя им — легион. Значимые типы, которые на вид, снаружи, весьма просты, а на самом деле скрывают весьма нетривиальную логику. Не назвать это отличным примером ООП у меня язык не поворачивается.

Ещё не убедил? Курим дальше. Полиморфизм в C# так же отлично работает для значимых типов.
Например, List::GetEnumerator() возвращает структуру, а компилятор для foreach использует методы этой структуры (не прибегая к боксингу, никаких интерфейсов для этого значимый тип реализовывать не обязан!). Что это как не ad hoc полиморфизм? Примерно то же самое происходит и с await и TaskAwaiter.

Пример наследования по понятным причинам не приведу :о)

Вот и всё. Кажется, выразиться удалось. Прошу прощения за стиль, подрастерял, кажется, его совсем. Остаётся пожелать побольше структур и инкапсуляции, поменьше наследования и правильного полиморфизма.

Update: Продолжение у Сергея Теплякова здесь.