2010-09-27 5 views
8

Несколько месяцев назад я писал кучу кода, и теперь я добавляю в него материал. Я понял, что написал кучу функций, которые спускаются из класса, который имеет около 2/3rds его абстрактных функций и оставшегося 1/3-го виртуального.Delphi: Поддержание работоспособности Virtual vs Virtual Abstract

Я довольно много надоело видеть:

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

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

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

и возненавидит заводиться с:

function descendent.doSomething() : TList; 
begin 

end; 

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

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

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

ответ

1

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

+0

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

7

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

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; 

Здесь у вас есть базовый класс для всех классов. В этом классе вы можете иметь общие свойства или события других классов и т. Д. Но все процедуры отмечены как абстрактные, потому что они не выполняют никаких общих задач. Аннотация гарантирует, что они будут реализованы в производных классах, но вам не нужно реализовывать «FieldA» в каждом классе, вы реализуете его только в «TAllAbstract_Custom». Это гарантирует использование принципа DRY.

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

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

Да, обрезайте свой код.

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