Является ли хрупкий базовый класс единственной причиной, по которой наследование нарушает инкапсуляцию?

Как заявляет "Банда четырех" в "шаблонах проектирования": " часто говорят, что 'наследование нарушает инкапсуляцию' "< / em>, перефразируя Снайдера в «Инкапсуляции и наследовании в объектно-ориентированных языках программирования».

Однако каждый раз, когда я читаю, что «наследование нарушает инкапсуляцию», причины этого утверждения либо неясны. объяснил или объяснил на примере проблемы хрупкого базового класса.

Читая статьи, я чувствую, что единственное свойство наследования, которое действительно нарушает инкапсуляцию, - это downcalls, функция, разрешенная открытая рекурсия (динамическая отправка на this) и определяется как «когда метод суперкласса вызывает метод, переопределенный в подклассе», согласно Ruby & Leavens в «Безопасном создании правильных подклассов без просмотра кода суперкласса».
Более того, открытая рекурсия, по-видимому, является причиной проблемы хрупкого базового класса, согласно Олдричу в «Селективная открытая рекурсия: решение проблемы хрупкого базового класса».

Итак, если проблема хрупкого базового класса является единственной причиной, по которой «наследование нарушает инкапсуляцию», было бы яснее сказать, что вызов вниз нарушает инкапсуляцию. Поскольку существуют некоторые решения, позволяющие избежать переадресации вызовов при сохранении наследования, само наследование на самом деле не участвует в нарушении инкапсуляции. Более того, шаблон делегирования, предложенный Бандой четырех для избавления от наследования, также может допускать открытую рекурсию и обратные вызовы, поскольку контекст делегатора (this) используется делегатом (что может привести к своего рода проблеме хрупкого класса делегата).

Отсюда мой вопрос:
Является ли проблема хрупкого базового класса единственной причиной, по которой говорится, что «наследование нарушает инкапсуляцию»?


person CidTori    schedule 23.06.2018    source источник


Ответы (3)


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

Я понимаю, что вы хотели спросить, является ли проблема хрупкого базового класса единственным проявлением нарушения принципа «наследование нарушает инкапсуляцию», верно?

TL; DR;

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

Так что в этом смысле или интерпретации я думаю, что вы, вероятно, правы.

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

Проблема в сцеплении

Я внимательно прочитал ту же библиографию, которую вы предоставили, и этот отрывок может пролить некоторый свет на этот вопрос.

Из Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения:

«Родительские классы часто определяют по крайней мере часть физического представления своих подклассов. Поскольку наследование предоставляет подклассу сведения о реализации его родителя, часто говорят, что «наследование нарушает инкапсуляцию» [Sny86] ».

Итак, похоже, что GoF подразумевал здесь, что фундаментальная проблема заключается в связи. Ясно, что хрупкий базовый класс - это проявление проблемы сцепления, поэтому вы все еще можете что-то

У объекта обычно есть общедоступный интерфейс, который предоставляет миру договор о том, что он может делать. Эти методы в общедоступном интерфейсе объекта должны удовлетворять нескольким предварительным условиям, инвариантам и постусловиям для услуг, которые предоставляет объект. Таким образом, пользователи объекта взаимодействуют с ним на основе этого контракта.

Пользователям объекта не обязательно знать детали реализации контракта. Так что, если мы когда-нибудь нарушим этот контракт, пострадают все пользователи объекта.

Эта зависимость от общедоступного интерфейса объекта является формой сцепления, а когда есть взаимосвязь, возникает форма хрупкости к изменениям.

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

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

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

Связь состояний

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

Из инкапсуляции и наследования в объектно-ориентированных языках программирования:

«Чтобы сохранить все преимущества инкапсуляции, внешние интерфейсы определения класса не должны включать переменные экземпляра»

В статических языках с модификаторами доступности (например, Java или C #) такое нарушение может проявиться, если мы предоставим непубличные поля из родительского класса. В динамических языках без модификаторов доступности такое нарушение проявляется, когда разработчик подкласса обращается к полю, которое предназначалось только для частного использования родительского класса (например, _field в Python).

Считаете ли вы эту проблему доступа к полю частью проблемы хрупкого базового класса? Мне кажется, эта форма наследования не была обязательно охвачена идеей хрупкой проблемы базового класса из известной статьи, которой вы поделились.

Защищенное интерфейсное соединение

Другой способ это проявляется в том, что теперь родительскому классу может потребоваться предоставить новый интерфейс, отличный от общедоступного, но предназначенный только для целей наследования: защищенный интерфейс. Таким образом, может потребоваться предоставить набор «защищенных» или «привилегированных» методов, которые предоставляют доступ к дополнительным сведениям, которые обычно не отображаются в общедоступном интерфейсе, предоставляемом для обычных пользователей объекта.

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

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

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

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

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

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

Хрупкость без повреждения инкапсуляции

При этом у нас возникает вопрос, можем ли мы иметь хрупкий базовый класс без нарушения инкапсуляции?

Я верю, что да. Дочерний класс может полностью зависеть только от открытого интерфейса родительского класса. Например, дочерний класс мог предоставить совершенно новый метод, который не наследуется и не является частью родительских общедоступных или защищенных интерфейсов.

Затем однажды мы добавляем новый метод в родительский класс, который имеет ту же сигнатуру, что мы давно добавили в дочерний класс, но с совершенно другим намерением.

Теперь мы что-то сломали, поскольку пользователи этого объекта ожидали бы, что новый метод будет вести себя так, как указано в интерфейсе родительского класса, а не как реализовано в дочернем классе.

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

Вариантом этой проблемы было бы, если бы новый метод, определенный в родительском классе, имел модификатор доступности, отличный от того же самого метода, определенного в дочернем классе как часть независимой эволюции обоих классов.

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

Другими словами, родительский класс хрупок, несмотря на то, что в этом случае инкапсуляция между родительским и дочерним классами прекрасна.

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

person Edwin Dalorzo    schedule 04.07.2018
comment
Спасибо за обстоятельный ответ. Я не уверен, что вы правильно истолковали мой вопрос, поэтому вот пояснение: если проблема хрупкого базового класса является единственной причиной, по которой говорится, что наследование нарушает инкапсуляцию, и если мы нашли общее и практическое решение проблемы хрупкого базового класса без избавления от наследования, наследование больше не будет нарушать инкапсуляцию при использовании этого решения. Таким образом, вы можете безопасно использовать наследование , если вы используете это решение. Если это не единственная причина, наследование может оказаться небезопасным. - person CidTori; 06.07.2018
comment
Проблема в сцеплении: я полностью согласен с вами. Единственное, с чем я не полностью согласен, - это делегирование как хорошая альтернатива. Действительно легко полностью заменить наследование делегированием, включая вызовы вниз. Таким образом, у вас может быть такая же хрупкость с делегированием, с той лишь разницей, что оно более явное, чем наследование, что делает его более ясным, но и более подробным. Это то, что я называю проблемой класса хрупких делегатов. - person CidTori; 06.07.2018
comment
State Coupling: Вы правы, я мог бы упомянуть об этом. Я не сделал этого, потому что, как вы сказали, состояние не должно быть частью интерфейса. Даже без наследования публичное раскрытие состояния объекта уже нарушает инкапсуляцию. - person CidTori; 06.07.2018
comment
Защищенное соединение интерфейса: я вижу в последнем абзаце, что мы не совсем согласны. Моя проблема в том, что я не могу найти четкого объяснения, с примерами и всем остальным, о том, как связь между родительским и дочерним элементами нарушает инкапсуляцию, помимо проблемы хрупкого базового класса. - person CidTori; 06.07.2018
comment
Хрупкость без нарушения инкапсуляции: эта часть действительно интересна, потому что я не думал об этом. Возможно, в этом случае хрупкий базовый класс разорвет контракт. - person CidTori; 06.07.2018
comment
По крайней мере, мой последний пункт о хрупкости без инкапсуляции демонстрирует, что проблема хрупкого базового класса и инкапсуляция разрыва наследования не являются синонимами. Честно говоря, я не могу думать о других проблемах, кроме проблемы хрупкого базового класса, которая представляет собой дополнительный пример того, как наследование нарушает инкапсуляцию. Я подумаю об этом еще немного. Вопрос действительно очень интересный. - person Edwin Dalorzo; 06.07.2018

Вам просто нужно быть осторожным, когда и как вы используете наследование.

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

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

Итак, вам нужен базовый класс, который был разработан для создания подклассов.

person gnasher729    schedule 23.06.2018
comment
Да, именно поэтому я пытаюсь выяснить, как разработать базовый класс для создания подклассов. Спасибо, что нашли время дать мне ответ, но, к сожалению, я не могу его принять, поскольку он не отвечает на мой вопрос: является ли проблема хрупкого базового класса единственной причиной, по которой говорится, что «наследование нарушает инкапсуляцию» ? Однако это хороший обзор проблемы хрупкого базового класса. - person CidTori; 24.06.2018

Нет. Это не единственная причина, по которой так сказано. Говорят, что из-за неразумного использования наследования вы можете получить более чем необходимое соединение классов (базового и производного): InheritanceBreaksEncapsulation.

Открытая рекурсия - это просто техническая проблема объектно-ориентированного проектирования. Предложение «наследование нарушает инкапсуляцию» является просто более общим утверждением.

Кроме того, не обязательно всегда выполнять полную инкапсуляцию, это зависит от ваших потребностей и контекста.

person Peregring-lk    schedule 05.07.2018