Рефакторинг. Зачем? - страница 9

Шрифт
Интервал

стр.

>begin

>Result:= Sqrt(Sqr(X — aX) + Sqr(Y — aY)) <= D;

>end;


>function TRectangle. HitTest(X, Y: Integer): Boolean;

>begin

>Result:= (X1 <= X) and (X <= X2) and (Y1 <= Y) and (Y <= Y2);

>end;

>Это была реализация классов, а тут реализация базовой функции HitTest, которая должна проверить все наши объекты:


>var

>Circles: array of TCircle;

>Rectangles: array of TRectangle;


>function HitTest(X, Y: Integer): Boolean;

>var

>I: Integer;

>begin

>Result:= False;

>for I:= 0 to Length(Rectangles) — 1 do

>begin

>Result:= Rectangles[I].HitTest(X, Y);

>if Result then

>Exit;

>end;

>for I:= 0 to Length(Circles) — 1 do

>begin

>Result:= Circles[I].HitTest(X, Y);

>if Result then

>Exit;

>end;

>end;


Не слишком компактно получилось. А теперь представим, что разных типов примитивов у нас десятки и даже сотни. Получается для каждого нужен свой массив, и свой цикл для функции HitTest? Есть способ сделать проще, можно применить наследование.

В чём тут смысл? Вместо нескольких массивов для каждого графического примитива отдельно, мы заводим класс, который описывает графический примитив, назовём его, например, TShape. Далее мы хотим хранить все наши примитивы, независимо от их типа в массиве «Shapes: array of TShape». Но так просто у нас это не получится, компилятор ругнётся, что типы не совместимы.

Для того, чтобы мы смогли положить наши прямоугольники и круги в массив TShape, нам надо поменять их наследование. Заменим TObject на TShape в объявлении класса:


>type

>TShape = class(TObject)

>end;


>TCircle = class(TShape)

>public

>…..

>end;


>TRectangle = class(TShape)

>public

>…..

>end;

Теперь наш круг одновременно и круг и графический примитив, равно как и в действительности. То же самое можно сказать и о квадрате. Что это значит с точки зрения программирования? То, что класс TCircle, наследник класса TShape можно использовать везде, где можно использовать класс TShape. Более того, все переменные и методы класса TShape (кроме private) будут также доступны в классе TCircle.

Не буду сильно углубляться в теорию, всё–таки я предпочитаю объяснять на примерах, поэтому сразу перейдём к тому как изменится наша функция с попмощью этого нехитрого преобразования:


>var

>Shapes: array of TShape;


>function HitTest(X, Y: Integer): Boolean;

>var

>I: Integer;

>begin

>Result:= False;

>for I:= 0 to Length(Shapes) — 1 do

>begin

>if Shapes[I] is TCircle then

>Result:= (Shapes[I] as TCircle).HitTest(X, Y)

>else if Shapes[I] is TRectangle then

>Result:= (Shapes[I] as TRectangle).HitTest(X, Y)

>if Result then

>Exit;

>end;

>end;

На самом деле тоже не очень красиво. Приходится для каждого примитива делать проверку, поддерживает–ли он нужный нам тип (оператор is) и осуществлять приведение типов (оператор as). Операторы is и as предназначены для работы только с объектами и не работают с простыми типами. Подробнее о них можно прочитать в документации.

Чтобы оценить мощь наследования нам остался всего один шаг. В класс TShape добавим строку «function HitTest(X, Y: Integer): Boolean; virtual; abstract;”, а в классы TCircle и TRectangle добавим после аналогичных строчек ключевое слово override:


>type

>TShape = class(TObject)

>public

>function HitTest(X, Y: Integer): Boolean; virtual; abstract;

>end;


>TCircle = class(TShape)

>public

> …..

>function HitTest(X, Y: Integer): Boolean; override;

>end;


>TRectangle = class(TShape)

>public

>…..

>function HitTest(X, Y: Integer): Boolean; override;

>end;

Что это означает? Мы как бы говорим, что класс TShape в принципе может проверить, попали в него координаты мыши или нет, но конкретная реализация зависит от того, какой именно примитив используется. То есть абстрактно функциональность есть, но её реализация должна быть переопределена в классах потомках.

Нашу многострадальную функцию теперь можно переписать так:


>var

>Shapes: array of TShape;


>function HitTest(X, Y: Integer): Boolean;

>var

>I: Integer;

>begin

>Result:= False;

>for I:= 0 to Length(Shapes) — 1 do

>begin

>Result:= Shapes[I].HitTest(X, Y);

>if Result then

>Exit;

>end;

>end;

При этом, в случаю кругов, в реальности будет вызываться функция TCircle. HitTest, а в случае прямоугольников — TRectangle. HitTest.

Понятно, что в случае с одной абстрактной функцией выигрышь не совсем очевиден, но ведь можно расширить базовый класс, добавив в него функции:


стр.

Похожие книги