Delphi: ремонтопригодность виртуальных и виртуальных абстрактных

Я писал кучу кода несколько месяцев назад, и теперь я добавляю в него материал. Я понял, что написал кучу функций, которые происходят от класса, который имеет около 2/3 абстрактных функций, а оставшуюся 1/3 виртуальную.

Мне очень надоело видеть:

function descendent.doSomething() : TList;
begin
   inherited;
end;

когда я получил это для базового класса:

function descendent.doSomething() : TList;
begin
   result := nil;
end;

и не хотелось бы заводить

function descendent.doSomething() : TList;
begin

end;

а потом удивляешься, почему что-то не сработало.

Мне нравится использовать абстрактные функции, потому что компилятор сообщит вам, можете ли вы получить абстрактную ошибку, потому что вы не реализовали некоторые функции.

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

3 ответа

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

 IAllInterfaced = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

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

Второй вариант:

 IAllAbstract = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
  end;

  TAllAbstract_ClassA = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllAbstract_ClassB = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

Здесь у вас есть базовый класс для всех классов. В этом классе вы можете иметь общие свойства или события других классов и т. Д.... Но все процедуры помечены как абстрактные, потому что они не выполняют каких-либо общих задач. Abstract гарантирует, что они будут реализованы в производных классах, но вам не нужно реализовывать "FieldA" в каждом классе, вы реализуете его только в "TAllAbstract_Custom". Это гарантирует, что используется принцип СУХОЙ.

Последний вариант:

 IAllVirtual = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
  end;

  TAllVirtual_ClassA = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllVirtual_ClassB = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

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

Естественно, это все крайние случаи, в них есть место. Вы можете иметь сочетание этих концепций.

Просто помни:

  1. Интерфейсы - мощный инструмент, позволяющий скрыть реализацию от пользователя и иметь общую точку использования (интерфейс). Они также заставляют использовать некоторые нормы, потому что интерфейс должен быть реализован полностью.
  2. Аннотация - хороший инструмент, поэтому вам не нужно использовать пустые заглушки для процедур, в которых они не нужны. С другой стороны, они заставляют вас внедрять их в производные классы.
  3. Виртуал пригодится, когда у вас есть общий код, который должен быть реализован каждым классом и который обеспечивает принцип чистого OP и DRY. Они также приветствуются, когда у вас есть процедуры, которые есть не в каждом производном классе.

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

Да, удалите код.

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

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

Другие вопросы по тегам