Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4408.html
 

метаклассы и классы помощники в Delphi

Опубликовано 22.03.2010 г. 23:56

столкнулся со следующей задачей: требуется произвести расчет некоторой потребности, причем первый расчет будет достаточно простым. а второй вариант посложнее, но основан на результатах первого.  пользователь выбирает метод расчета и собственно нажатием кнопки установив нужные параметры проводит расчет.  Возможно когда то появится еще и третий вариант расчета. Но в любом случае вроде как результатом всегда является массив некоторых потребностей - целочисленных значений. массивы в принципе одинаковой размерности. и вот вопрос возник, как устроить проще вывод данных и работу с классом "решебником". Вообще говоря никогда особо не использовал метаклассы (metaclass, class reference) при решении своих задач. Однако в данном случае как будто это оказалось и удобно. допустим мы имеем почти абстрактный класс от которого будут унаследованы два варианта потомков для решения задачи (в рабочем случае второй вариант наследник первого). Итак что нам требуется. Общий класс родитель TSolver который собственно будет иметь абстрактную процедуру решения задачи solve(), свойства для получения результата results[], ну и непосредственно функцию чтения этого свойства. Только данную функцию getResults мы так же сделаем абстрактной, поэтому каждый потомок будет переопределять вывод результата как он хочет, а свойство для доступа к результату будет общим.

  TSolver = class
      function getResults(index:integer):integer; virtual;abstract;
      procedure solve(); virtual; abstract;

      property results[index:integer]:integer read getResults;
  end;

Собственно каждый из вариантов классов для решения задачи, будет иметь свои методы и переменные для реализации собственного алгоритма. для простоты пусть классы будут названы TFirstSolver & TSecondSolver а результаты соответственно будут хранится в массивах first & second

  TFirstSolver = class(TSolver)
    first : array[0..10] of integer;
    function getResults(index:integer):integer; override;
    procedure solve(); override;
  end;
  TSecondSolver = class (TSolver)
    second : array[0..10] of integer;
    function getResults(index:integer):integer; override;
    procedure solve(); override;
  end;

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

function TFirstSolver.getResults(index: integer): integer;
begin
    result := first[index];
end;
function TSecondSolver.getResults(index: integer): integer;
begin
    result := second[index];
end;

процедуры расчета сделаем различными, в первом случае results[i] = i+1, во втором сделаем i^2.

procedure TFirstSolver.solve();
var i:byte;
begin
    for i in [0..10] do
        first[i] := i + 1;
end;
procedure TSecondSolver.solve();
var i:byte;
begin
    for i:=0 to 10 do
        second[i] := i*i;
end;

Теперь на форму добавим grid:TStringGrid для результатов, и typeSelect:TComboBox для выбора варианта решения. в событии создания формы заполним наш селект описаниями классов

procedure TForm1.FormCreate(Sender: TObject);
begin
    with typeSelect.Items do begin
        addObject('first',  TObject(TFirstSolver));
        addObject('second', TObject(TSecondSolver));
    end;
end;

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

  TIntGrid = class helper for TStringGrid
    private
      procedure setInt(c,r,val:integer);
    public
      property ints[c,r:integer]:integer write setInt;
  end;

с нехитрой реализацией метода setInt

procedure TIntGrid.setInt(c, r,val: integer);
begin
    cells[c,r] := intToStr(val);
end;

Собственно реализуем при переключении варианта расчета из комбобокса расчет задачи выбранным вариантом, и вывод результатов в таблицу. Но для этого сначала определим мета класс для всех объектов класса TSolver и его потомков (метаклассы описывают не объекты какого либо класса, а для описания самого класса).

TSolverClass = class of TSolver;

При реализации непосредственно метода переключения комбобокса, заведем переменную для обозначения выбранного типа класса solverType, и для объекта "решебника" solver. Итак, получим из коллекции items.objects нужное нам описание класса, и создадим его экземпляр. вызовем базовый метод решения, который для каждого потомка будет перекрыт, а потом так же выведем результаты работы используя базовое свойство results, функции чтения которого также переопределены у классов потомков

procedure TForm1.typeSelectChange(Sender: TObject);
var solverType : TSolverClass;
    solver : TSolver;
    i : integer;
begin
    i := typeSelect.ItemIndex;
    solverType := TSolverClass(typeSelect.Items.Objects[i]);
    solver := solverType.Create;
    solver.solve();

    for i:= 0 to 10 do begin
         grid.ints[i,0 ] := i;
         grid.ints[i,1 ] := solver.results[i];
    end;
    solver.Free;
end;

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

Метки:  class helper  |  class reference  |  metaclass 

Комментарии

Нет комментариев
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно