The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
Инкапсуляция, как вы знаете, является одним из четырех ключевых принципов в объектно-ориентированном программировании. Инкапсуляция, согласно Грэди Буч и др., - это “процесс сокрытия всех секретов объекта, которые не способствуют его существенным характеристикам.” Практически говоря, это касается тех private
атрибутов, которые мы используем в Java и C++: они не видимы для пользователей наших объектов, поэтому их нельзя изменять или даже читать. Буч и др. считают, что цель инкапсуляции - “обеспечить явные барьеры между различными абстракциями”, что приводит к “четкому разделению задач”. Однако, работает ли это действительно по плану? Действительно ли у нас есть явные барьеры между объектами? Давайте посмотрим.
Во-первых, я не первый и не единственный, кто задает этот вопрос. Дэвид Уэст сказал еще раньше, что “в большинстве случаев инкапсуляция - это дисциплина, а не настоящий барьер”, и что “очень редко целостность объекта защищена в абсолютном смысле”. На практике “это зависит от пользователя объекта, чтобы уважать инкапсуляцию этого объекта.” Действительно, давайте взглянем на класс Temperature
из моей статьи в блоге о голых данных:
Можно ли сказать, что атрибут t
действительно инкапсулирован? Технически да: невозможно изменить его непосредственно через точечную нотацию. Проще говоря, мы не можем сделать это:
И даже этого мы не можем сделать:
Однако, мы можем сделать то же самое через геттер getT()
и сеттер setT()
. Таким образом, разработчик класса Temperature
дает нам возможность получить доступ к его атрибуту, но косвенно, через геттеры и сеттеры. Я бы сказал, что здесь нарушается принцип инкапсуляции, и, я уверен, Аллен Холуб согласился бы со мной. Какое решение? Статья о голых данных предлагает использовать принцип TellDontAsk и избавиться от геттера.
Теперь класс Temperature
не позволяет нам читать его атрибут t
. Вместо этого мы можем только сказать ему подготовить строковое представление температуры и вернуть его нам обратно. Возможно, это не совсем классический пример парадигмы “сообщить”, так как некоторые данные возвращаются, но теперь это выглядит гораздо лучше, чем раньше. Красота этого рефакторинга заключается в меньшей связности между клиентом и объектом. С помощью геттера (или прямого доступа к атрибуту через точечную нотацию) клиент мог получить числовое значение температуры и пересчитать его в фаренгейты, предполагая, что оно было в цельсиях. С возвращаемой строкой клиент бы этого не делал. Строка использовалась бы только как конечный продукт, неизменяемый. Или, может быть, нет?
Что, если клиент сделает это:
Как это выглядит сейчас? Разве это не нарушение инкапсуляции? Результат toString()
не обрабатывается так, как должен. Не как простая строка, а как данные с некой внутренней структурой, которая известна клиенту. Основная проблема здесь заключается в знаниях, которыми клиент обладает о выводе. Клиент знает слишком много и использует это знание в своих интересах: для деконструкции данных и манипуляции с результатом.
Можем ли мы действительно запретить клиенту это делать? Нет такой возможности в любом языке программирования, насколько мне известно. Когда вывод метода передается клиенту, клиенту разрешается делать с ним все, что нужно. В этом, если я правильно понял доктора Веста, и заключается неуважение к инкапсуляции. И мы даже не обсуждаем Reflection API, которое позволило бы нам извлечь t
из Temperature
даже без вызова методов.
Таким образом, инкапсуляция не является явным барьером. Она существует до тех пор, пока у нас есть стремление соблюдать ее. Если мы этого не делаем, ничто не помешает нам злоупотреблять объектом любым способом. И даже модификаторы атрибутов private
не помогут. Более того, они только создадут иллюзию инкапсуляции, тогда как на самом деле каждый может делать все, что ему кажется подходящим для его делового случая.
У меня есть предложение, которое может помочь нам сделать инкапсуляцию более явной.
Что, если бы у нас была возможность контролировать, что происходит с данными и объектами после их возвращения клиентам? Что, если мы могли бы предотвратить клиенту выполнение split()
для вывода метода toString()
? Я думаю, что мы могли бы сделать это на этапе компиляции, пройдясь по всему коду и проверив, насколько далеко находятся моменты взаимодействия с нашими объектами от мест, где они были возвращены. В приведенном выше примере расстояние равно двум: 1) сначала мы выполняем split
, 2) затем мы выполняем parseInt()
. В больших приложениях это число будет больше, конечно.
Похоже, что мы можем использовать это число расстояния в качестве метрики связи между объектами во всем приложении. Чем больше число (или среднее всех чисел), тем хуже дизайн: в хорошем дизайне мы не должны извлекать что-то из метода и затем выполнять сложную обработку. Мы должны, в соответствии с принципом TellDontAsk, уполномочить наши объекты выполнять работу и возвращать только краткую сводку. Метрика расстояния покажет нам именно это: сколько раз и насколько мы нарушали принцип слабой связи.
Вас бы заинтересовало создание такого анализатора, скажем, для Java-кода?
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-16 at 15:11