Является ли Fragile Base Class единственной причиной, по которой «наследование нарушает инкапсуляцию»? - PullRequest
0 голосов
/ 23 июня 2018

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

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

При чтении статей у меня возникает ощущение, что единственное свойство наследования, которое действительно нарушает инкапсуляцию, - это нисходящие вызовы , функция, допускаемая open recursion ( динамическая отправка on this) и определяется как ", когда метод суперкласса вызывает метод, который переопределен в подклассе" , в соответствии с Ruby & Leavens в "Безопасном создании правильных подклассов без просмотра кода суперкласса".
Более того, открытая рекурсия, по-видимому, является причиной проблемы Fragile Base Class , согласно Олдричу в статье «Выборочная открытая рекурсия: решение проблемы Fragile Base Class».

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

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

Ответы [ 3 ]

0 голосов
/ 04 июля 2018

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

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

TLDR;

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

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

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

Соединение - это проблема

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

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

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

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

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

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

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

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

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

State Coupling

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

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

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

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

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

Соединение защищенного интерфейса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0 голосов
/ 05 июля 2018

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

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

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

0 голосов
/ 23 июня 2018

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

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

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

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

...