Class Casting Is a Discriminating Anti-Pattern

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

sixnines availability badge   GitHub stars