метаклассы и классы помощники в Delphi
столкнулся со следующей задачей: требуется произвести расчет некоторой потребности, причем первый расчет будет достаточно простым. а второй вариант посложнее, но основан на результатах первого. пользователь выбирает метод расчета и собственно нажатием кнопки установив нужные параметры проводит расчет. Возможно когда то появится еще и третий вариант расчета. Но в любом случае вроде как результатом всегда является массив некоторых потребностей - целочисленных значений. массивы в принципе одинаковой размерности. и вот вопрос возник, как устроить проще вывод данных и работу с классом "решебником". Вообще говоря никогда особо не использовал метаклассы (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;
Результат заполнения таблицы будет соответствовать выбранному сценарию расчета. Вышло что то на подобии маленького интерфейса.