Delphi: ремонтопригодность виртуальных и виртуальных абстрактных - PullRequest
8 голосов
/ 27 сентября 2010

Я писал кучу кода несколько месяцев назад, а теперь добавляю в него что-то новое.Я понял, что написал множество функций, которые происходят от класса, который имеет около 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 ]

7 голосов
/ 27 сентября 2010

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

 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. Они также приветствуются, когда у вас есть процедуры, которые есть не в каждом производном классе.

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

1 голос
/ 28 сентября 2010

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

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

1 голос
/ 27 сентября 2010

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

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