Недавно посещал одно интересное собеседование по Java.
Прозвучал вопрос , что такое инкапсуляция - ну в общем, выдал всё как есть. А дальше собеседующий приводит мне пример: представим, что мы - разработчик крупного фреймворка, какова будет наша главная проблема, если нет инкапсуляции?
Всяческие мои ответы про несекюрный доступ к служебным методам и переменным, про то, что юзер будет наружу видеть кучу кишок, которые ему не нужны и про то, что юзер может ломать всю логику фреймворка - были пропущены с комментариями "это не то" :) .
Hа мои встречные вопросы - только безумные глазки и хитрая ухмылка :) . Единственное, что он ответил, подсказал так что и не сильно помогло, - "с этой проблемой столкнулся создатель JDK".
Отсутсвие инкапсуляции в данном контексте, очевидно, в первую очередь означает излишне обширный публичный интерфейс. А публичный интерфейс разработчик фреймворка просто так менять не может - нужно соблюдать обратную совместимость. Соответственно, чем больше у нас торчит наружу ушей, тем сложнее поддерживать и развивать фреймворк. Достаточно посмотреть на код Битрикса, например, чтобы увидеть к чему это приводит.
Думаю, вопрос был именно об этом.
инкапсуляция - "сокрытие реализации", не знаю что там от вас на собеседовании ожидали, обычно сложно угадать что там человек себе понапридумывал, что хочет услышать, я не обращаю на это внимание тк это не представляет никакого интереса.
хотя, я бы может сказал что-то типа такого:
если не скрывать реализацию - может быть проблема при изменении этой реализации, например на более эффективную, и тогда прийдётся переписывать много кода, тем кто использует вашу библиотеку, им это доставит адскую боль. никто же не хочет переписывать свой код при обновлении вашего фреймворка)
но реальность конечно куда сложнее, и код рано или поздно всё равно приходится переписывать)))))
Да нет особой проблемы. Просто ответственность за правильное использование апи перекладывается с разработчика апи на разработчика, использующего это апи.
Ну и к слову: наследование классов нарушает инкапсуляцию (для protected-полей и методов). А приватные поля/методы можно спокойно получить через рефлексию.
Вообще, инкапсуляция в виде модификаторов доступа - так себе. Лучше это реализовывать через замыкания и контексты. Но в java такого нет (и не будет). Так что и так сойдёт.
> Просто ответственность за правильное использование апи перекладывается с разработчика апи на разработчика, использующего это апи.
Если уж это не проблема, то тогда что вообще можно назвать проблемой?)
> Ну и к слову: наследование классов нарушает инкапсуляцию (для protected-полей и методов).
м, каким образом?
Станислав Макаров: Ну вот в питоне инкапсулировать что-то можно только через замыкания. Все поля объектов - публичные. Но ничего, есть договорённости об именовании. Тот, кто использует библиотеку вполне может получить "приватный" член класса. Но это как бы будут его проблемы.
>> Ну и к слову: наследование классов нарушает инкапсуляцию (для protected-полей и методов).
> м, каким образом?
Protected-поля не доступны другим классам, однако доступны классам-наследникам. От внешнего мира их прячут, потому что они инкапсулируют детали реализации публичных методов. Но в дочернем классе к ним есть доступ, т.е. сокрытие нарушается.
Но даже если все внутренние поля сделать private, инкапсуляция всё равно нарушится. Дочерний класс зависит от деталей реализации родительского. Мы можем изменить эти реализации и сломать работу дочернего класса. Т.е. реализация некоторых элементов дочернего класса не сокрыта от родительского.
В общем, наследование реализаций - плохо. Композиция - хорошо. Наследование интерфейсов - тоже хорошо. Описал иерархию интерфейсов, потом нужные из них реализовал. У каждого интерфейса может быть много реализаций, главное, чтобы класс не расширял другой класс. Тогда они будут независимы и не будут мешать друг другу. Как вариант, можно общую логику реализовывать в абстрактных классах, а в их наследниках реализовывать только абстрактные методы родителя.
Ринат Велиахмедов:
> Что за глупости.
Глупости - не знать этих вещей. На плюсах же пишешь, такие вещи нужно знать, чтобы писать нормальный код.
Есть куча источников, где это объясняется. Например, в Effective Java.
Или тут.
Или тут. Вот наглядный пример, к каким ошибкам это может привести (там ява, но на плюсах аналогично).
В GoF вроде тоже про это писали. Короче, инфы куча.
bromzh
> Тот, кто использует библиотеку вполне может получить "приватный" член класса. Но это как бы будут его проблемы.
Вот очень правильно вы написали, что если человек обращается к "приватному" члену, то это его проблемы. Только об этом ему будет в каждой строке кода кричать нижнее подчеркивание. В любой момент достаточно глянуть на код и будет видно, что некоторые соглашения нарушаются.
А вот когда я смотрю на API, я думаю о нем как об API. Т.е. как о некотором контракте, который мы оба - я, пользователь либы, и ее разработчик - должны соблюдать. И обычно люди рассчитывают на то, что этот контракт был предварительно обдуман. Более того, если либа следует semantic versioning, это накладывает доп. ограничения на свободу модификации API и требует корректной смены версий. И получается, я должен как-то догадаться, что разработчик НЕ соблюдает все эти негласные правила? Даже, если я догадаюсь, то тогда мне нужно читать код либы, чтобы разобраться. А либа, чей куда нужно читать для ее использования, по сути не нужна, т.к. не решает за меня нужную мне подзадачу (я частично превращаюсь в ее разработчика).
Пару слов о protected. В принципе можно довольно долго рассуждать, реально ли необходима protected область видимости или же это излишнее усложнение, но раз мы взялись говорить о языках, где такой модификатор наличествует, то мне кажется вы совершенно не представляете, как им пользоваться.
> Мы можем изменить эти реализации и сломать работу дочернего класса.
Точно также я могу изменить реализации публичных методов, и сломать работу клиентов класса. protected-члены это тоже интерфейс, и к нему тоже нужно подходить ответственно. Конечно, он подразумевает более плотное взаимодействие, чем public, и как правило иерархия классов не выходит за пределы подсистемы/библиотеки/модуля, но тем не менее для protected методов точно также нужно соблюдать пред- и постусловия, как и для public. Да, эти условия невозможно задавать в рамках рассматриваемых языков, чтобы их проверял компилятор, но нарушает ли это инкапсуляцию? Ведь в Питоне интерпретатор тоже не бьет по рукам за обращение к "приватным" членам, т.к. это всего лишь договоренность.
В последнем примере например нарушены постусловия для переопределенного add: разумеется, пользователь класса ожидает, что add добавит элемент ОДИН раз и количество элементов множества увеличится НА 1. Собственно, проваленный тест в каком-то смысле и есть задокументированное постусловие, которое было нарушено.
А вообще, "inheritance CAN break encapsulation" != "inheritance break encapsulation". Вы или забыли это упомянуть в ответе, либо предпочли не знакомиться с деталями проблемы.
> Как вариант, можно общую логику реализовывать в абстрактных классах, а в их наследниках реализовывать только абстрактные методы родителя.
Да, вот именно такой вариант часто и используют, делая protected virtual функции, позволяя переопределить только некоторые фрагменты логики, вынося общее в родителя.
bromzh Вообще, инкапсуляция с точки зрения состояния объекта - это выпиливание "некорректных" состояний из множества состояний, являющегося декартовым произведением состояний частей объекта. Делая все или часть полей приватными, а методы с нужной логикой - публичными, мы тем самым не позволяем объекту перейти в некорректное с нашей точки зрения состояние. В этом собственно и отличие объекта от кортежа/записи, в которой значение каждого компонента можно задать независимо от других.
Поэтому, и public и protected методы должны в равной степени заботиться о состоянии объекта, а методы, переопределенные в наследниках - заботиться о том, чтобы сохранялись все поведенческие черты родительского метода. Интерфейс метода - это НЕ ТОЛЬКО его параметры и возвращаемое значение. Это еще и все то, что клиентский код ожидает от класса, что описывается в документации и согласуется с общей логикой класса (это к примеру о методе add).