The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
Приведение типов - очень полезная техника, когда нет времени или желания тщательно продумывать и проектировать объекты. Приведение типов (или приведение классов) позволяет нам работать с предоставленными объектами по-разному, основываясь на классе, к которому они принадлежат, или на интерфейсе, который они реализуют. Приведение классов помогает нам дискриминировать бедные объекты и разделять их по расе, полу и религии. Может ли это быть хорошей практикой?
Это очень типичный пример приведения типов (Google Guava полна таких примеров, например Iterables.size()
):
Метод sizeOf()
вычисляет размер итерируемого объекта. Однако, он достаточно умён, чтобы понять, что если items
также являются экземплярами Collection
, нет необходимости итерировать их. Быстрее будет привести их к типу Collection
и затем вызвать метод size()
. Звучит логично, но что не так с этим подходом? Вижу две практические проблемы.
Во-первых, есть скрытая связь между sizeOf()
и Collection
. Эта связь не видна клиентам sizeOf()
. Они не знают, что метод sizeOf()
зависит от интерфейса Collection
. Если мы завтра решим его изменить, sizeOf()
перестанет работать. И мы будем очень удивлены, так как его сигнатура ничего не говорит об этой зависимости. Это, очевидно, не произойдет с Collection
, так как он является частью Java SDK, но с пользовательскими классами это может и произойдет.
Вторая проблема - неизбежное увеличение сложности метода sizeOf()
. Чем больше специальных типов ему приходится обрабатывать по-разному, тем сложнее он становится. Эта ветвистость с помощью if/then
неизбежна, так как метод должен проверить все возможные типы и обрабатывать их по-особому. Такая сложность является результатом нарушения принципа единственной ответственности. Метод не только вычисляет размер Iterable
, но и выполняет приведение типов и ветвление на основе этого приведения.
Какая есть альтернатива? Их несколько, но самым очевидным является перегрузка методов (недоступна в полноценных ООП-языках, таких как Ruby или PHP):
Разве это не более элегантно?
Философски говоря, приведение типов - это дискриминация в отношении объекта, передающегося в метод. Объект соответствует контракту, предоставленному сигнатурой метода. Он реализует интерфейс Iterable
, который является контрактом, и ожидает равного обращения со всеми остальными объектами, передаваемыми в тот же метод. Но метод дискриминирует объекты по их типам. В принципе, метод спрашивает объект о его… расе. Черные объекты идут вправо, а белые - влево. Вот что делает instanceof
, и в этом суть дискриминации.
Используя instanceof
, метод отделяет входящие объекты по определенной группе, к которой они принадлежат. В данном случае существуют две группы: коллекции и все остальные. Если вы являетесь коллекцией, вы получаете особое обращение. Даже если вы соблюдаете контракт Iterable
, мы все равно особо обращаемся к некоторым объектам, потому что они принадлежат к “элитной” группе, называемой Collection
.
Можно сказать, что Collection
- это всего лишь еще один контракт, с которым объект может согласиться. Это верно, но в этом случае должна быть еще одна дверь, через которую должны проходить те, кто работает по этому контракту. Вы заявили, что sizeOf()
принимает всех, кто работает с контрактом Iterable
. Я - объект, и я делаю то, что говорит контракт. Я вхожу в метод и ожидаю равного обращения ко всем остальным, кто также входит в этот метод. Но, очевидно, как только я попадаю внутрь метода, я понимаю, что некоторые объекты имеют некоторые специальные привилегии. Разве это не дискриминация?
В заключение, я бы рассмотрел использование instanceof
и приведения типов как анти-паттерны и запахи кода. Как только вы видите необходимость использования их, начните задумываться о рефакторинге.
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-17 at 17:10