morfizm (morfizm) wrote,
morfizm
morfizm

Category:

Современное ООП

Помню 20 лет назад учил детей на курсах, что инкапсуляция это круто. Каноническим примером был объект круг, умеющий себя рисовать. Правда же, здорово: описание данных (координаты круга, радиус) находятся рядом с описанием действий (метод draw). Был также родственный канонический пример наследования: цветной круг, в котором наследуются координаты и радиус, но появляется также атрибут "цвет", он же канонический пример полиморфизма: метод рисования перекрыт, потому что теперь нужно рисовать в цвете.

Я уже упоминал, что современное ООП трансформировалось, как наследование, так и полиморфизм, в общем виде считаются опасными. Их трудно поддерживать, и программерское сообщество сошлось на том, что инкапсуляция это хорошо, а вот наследование и полиморфизм лучше ограничить, допустимый компромисс это использование интерфейсов. Все остальные взаимодействия между объектами лучше моделировать через ассоциацию (ссылки), агрегацию (членство) и композицию (составная часть). Интерфейсы не имеют атрибутов, а все методы полностью абстрактны, ты обязан их переопределить в потомке.

Что я забыл упомянуть, так это то, что инкапсуляция тоже хороша далеко не всегда. Хрестоматийный пример с кружком и рисованием это как раз неудачный пример инкапсуляции. Хорошие примеры атрибутов в классе это сложные состояния объекта, приватные мутабельные поля. Мутабельные объекты нужно инициализировать. Это накладывает жёсткие ограничения на сериализацию коллекций таких объектов. Поэтому поля сериализации обычно объединяют в отдельный класс, который "простой", в идеале в нём немутабельные публичные поля и больше ничего. Можно добавить методов, которые не требуют промежуточного состояния. Например, разные конструкторы или всевозможные хелперы для конвертации или вычисляемые атрибуты. Релизаций рисования может быть много, поэтому рисование выделяется в интерфейс, и может быть много классов, его по-разному реализующих. Понятно, что при этом описание круга (координаты, радиус, цвет) должны быть выделены в отдельный класс, рисовальщик будет принимать объекты-круги через параметры.

Я, вот, столкнулся с библиотечным классом, в котором этот принцип был нарушен (в нём было нечто похожее на класс круг как с координатами, так и с рисованием), и сразу возникло неприятное ощущение. Не понятно, как расширять функционал. Наследовать от такого класса - противно. Нужно будет внимательно следить, чтобы все методы были статичными, иначе прозеваешь и не сможешь корректно сериализовать, или будешь платить overhead'ом на сохранение ненужного тебе состояния. Делать же всё по уму (выносить данные в отдельный класс, а методы в интерфейс) требует создания кучи адаптеров. Тоже противно. Ну, значит, чтобы потом не было противно, лучше изначально так не делать. Круг, умеющий себя рисовать, это плохой круг. Хороший круг - пассивный круг, просто знающий, где он и какого он размера :) А рисовальщик живёт от него отдельно.

Upd.: если наследовать, то возникает ещё такая проблема. Расширенный функционал может требовать дополнительных параметров. Например, крутому рисовальщику-v2 нужен объект "кисть". Но в параметрах метода draw нет кисти. Если кисть достаточно сложна, что нельзя создать временную кисть прямо в draw, то ссылку на кисть можно передать только в конструкторе. Это накладывает кучу ограничений, например:
- вот теперь уже точно нужно специально выкручиваться, чтобы сериализовать класс, потому что придётся исключать из сериализации эту кисть.
- чтобы нарисовать круг другой кистью, придётся создавать его заново.
Бррр.
Tags: 1, software development, work
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 41 comments