Действовать на базе или подклассе без RTTI или модификации базового класса - PullRequest
1 голос
/ 18 апреля 2009

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

Для простоты, у нас есть два класса, A и B, где B получен из A. B действительно "является" A, и все подпрограммы, определенные в A, имеют то же значение в B.

Допустим, мы хотим отобразить список As, некоторые из которых на самом деле являются Bs. Когда мы пересекаем наш список As, если текущий объект на самом деле представляет собой B, мы хотим отобразить некоторые из дополнительных свойств Bs ... или, может быть, мы просто хотим по-разному окрашивать Bs, но ни A, ни B не имеют никакого представления о "color" или "display stuff".

Решения:

  1. Сделайте класс A полуосознающим B, включив в него метод isB (), который возвращает false. B переопределит метод и вернет true. Код отображения будет иметь следующую проверку: if (currentA.isB ()) B b = currentA;

  2. Предоставьте метод display () в A, который B может переопределить .... но затем мы начнем объединять пользовательский интерфейс и модель. Я не буду обдумывать это, если не найдется какой-нибудь крутой трюк, которого я не вижу.

  3. Используйте instanceof, чтобы проверить, является ли текущий отображаемый объект A действительно B.

  4. Просто добавьте весь мусор из B в A, даже если он не относится к A. В основном просто поместите B (который не наследуется от A) в A и установите его в ноль, пока он не будет применен. Это несколько привлекательно. Это похоже на # 1, я думаю, w / композиция за наследование.

Кажется, что эта конкретная проблема должна возникать время от времени и иметь очевидное решение.

Так что я думаю, что вопрос, возможно, действительно сводится к:

Если у меня есть подкласс, который расширяет базовый класс на , добавляя дополнительную функциональность (не просто изменяя существующее поведение базового класса), я делаю что-то трагически неправильно? Это кажется, что все мгновенно разваливается, как только мы пытаемся воздействовать на набор объектов, которые могут быть A или B.

Ответы [ 3 ]

2 голосов
/ 18 апреля 2009

Вариант варианта 2 (или гибрид 1 и 2) может иметь смысл: в конце концов, полиморфизм является стандартным решением для «Bs являются As, но должны вести себя по-разному в ситуации X». Согласитесь, метод display (), вероятно, слишком привязал бы модель к пользовательскому интерфейсу, но, вероятно, различные визуализации, которые вы хотите получить на уровне пользовательского интерфейса, отражают семантические или поведенческие различия на уровне модели. Могут ли они быть захвачены в методе? Например, вместо прямого метода getDisplayColour (), это может быть метод getPriority () (например), которому A и B возвращают разные значения, но пользовательский интерфейс все еще должен решить, как преобразовать это в цвет

Однако, учитывая ваш более общий вопрос: «Как мы можем обрабатывать дополнительное поведение, которое мы не можем или не можем позволить полиморфно получить доступ через базовый класс», например, если базовый класс не находится под нашим контролем, ваши варианты, вероятно, вариант 3, шаблон Visitor или вспомогательный класс. В обоих случаях вы эффективно распространяете полиморфизм на внешнюю сущность - в варианте 3 пользовательский интерфейс (например, презентатор или контроллер), который выполняет проверку instanceOf и выполняет разные действия в зависимости от того, является ли это B или нет; в случае посетителя или помощника, новый класс. Учитывая ваш пример, Visitor, вероятно, излишне (также, если бы вы не смогли / не захотели изменить базовый класс, чтобы приспособиться к нему, я думаю, что было бы невозможно реализовать его), поэтому я бы предложил простой класс, называемый чем-то как "рендерер":

public abstract class Renderer {
  public static Renderer Create(A obj) {
    if (obj instanceOf B)
      return new BRenderer();
    else
      return new ARenderer();
  }

  public abstract Color getColor();
}

// implementations of ARenderer and BRenderer per your UI logic

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

1 голос
/ 18 апреля 2009

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

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

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

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

Использование методов в качестве 'instanceof' или downcasts (dynamic_cast <> () в C ++) для определения реального типа среды выполнения - это то, что я бы отметил в обзоре кода и принял только после того, как программист подробно объяснит, почему любые другие вариант хуже, чем это решение. Я бы принял это, например, в ответе itowlson при условии, что информация не доступна с данными операциями в базе. То есть базовый тип не имеет какого-либо метода, который предлагал бы вызывающей стороне достаточно информации для определения цвета. И если нет смысла включать такую ​​операцию: помимо цвета препрезентации, собираетесь ли вы выполнять какие-либо операции над объектами на основе этой же информации? Если логика зависит от реального типа, то операция должна быть в базовом классе, чтобы быть переопределенной в производных классах. Если это невозможно (операция является новой и только для некоторых заданных подтипов), в базе должна быть, по крайней мере, операция, позволяющая вызывающему абоненту определить, что обратное преобразование не завершится неудачей. И опять же, мне бы действительно потребовалась веская причина, чтобы код вызывающей стороны требовал знания реального типа. Почему пользователь хочет видеть его в разных цветах? Будет ли пользователь выполнять различные операции для каждого из типов?

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

0 голосов
/ 18 апреля 2009

Это похоже на книжный шкаф для шаблона дизайна посетителя (также известного как «Двойная отправка»).

См. этот ответ для ссылки на подробное объяснение шаблонов Visitor и Composite.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...