Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« : 10-10-2008 19:59 » |
|
Допустим, должен иметься жёстко заданный и неизменный во время эксплуатации программы иерархический классификатор типа:
(B(A(C, B), P(L, C, R, P, M, W, T)), O(I(D), C(N, O)))
Соответственно, имеем допустимые классификационные коды:
BAC, BAB, BPL, BPC, BPR, BPP, BPM, BPW, BPT, OID, OCN, OCO.
Если буквы в программе заменить на осмысленные названия, то получим набор enum-ов.
Спрашивается, как написать такой(ие) класс(ы) в программе (а точнее, какой у него(них) должен быть интерфейс), чтобы он(и): 1) описывал(и) значение классификатора; 2) позволял(и) бы пользователю читать отдельные позиции классификационного кода (поскольку они имеют самостоятельный смысл); 3) позволял(и) бы конструировать классификационный код в рамках допустимых классификатором возможностей - записывать отдельные позиции классификационного кода.
Можно, конечно, по позициям классификационного кода завести 3 enum-а: 1) B, O 2) A, P, I, C 3) C, B, L, C (2-й раз повторяется, можно убрать), R, P, M, W, T, D, N, O и дальше на уровне логики записи блокировать присваивание недопустимых для 1 и 2-го уровней значений 3-го уровня и т.д.
Но можно ли изящнее решить задачу?
P.S. Прошу предлагать решения для языков программирования со строгой типизацией. Понятно, что в языках без строгой типизации можно менять интерфейс объекта во время исполнения.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #1 : 10-10-2008 20:06 » |
|
dimka, А можно более наглядно и на пальцах объяснить? На примере. Что то в абстракции у меня туго. Не могу понять задачу.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #2 : 10-10-2008 20:23 » |
|
Из того, что я понял. Класс в принципе может быть один. Но он должен сам уметь выстраивать иеархические деревья. Теперь задача другая. Например B это просто буква, или это может быть и слово?
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #3 : 10-10-2008 21:39 » |
|
Например B это просто буква, или это может быть и слово? Например, значение enum-а. При этом тип enum-а желательно сохранить.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #4 : 10-10-2008 21:48 » |
|
На стадии компиляции это шаблоны. Но я так понял, тебе уже нужно на стадии работы программы?
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #5 : 10-10-2008 21:55 » |
|
Finch, да. Видимо, нужно решение в духе объявления типов Variant.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #6 : 10-10-2008 21:59 » |
|
Тут LogRus как-то в Эврике свой пример с enum давал. Можно сделать абстрактный базовый enum класс, и от него уже плодить потомков. Твоя Иеархия будет работать как с абстрактным классом, Но подставлять ты будеш потомков.
Теперь встает вопрос один. Как сделать так, чтобы класс знал, какие и сколько членов в enum. Ясно, что он будет работать как с числами. Но все равно, можно задать константы в разнобой.
|
|
« Последнее редактирование: 10-10-2008 22:04 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #7 : 11-10-2008 08:36 » |
|
Тут LogRus как-то в Эврике свой пример с enum давал. Можно сделать абстрактный базовый enum класс, и от него уже плодить потомков. Твоя Иеархия будет работать как с абстрактным классом, Но подставлять ты будеш потомков. Ни в коем случае. Будет в духе: class MyEnum { public: enum Enum { A, B }; private: Enum value; public: MyEnum(Enum value); Enum getValue() const; void setValue(Enum value); }; Такой класс имеет методы, типы результатов и аргументов которого привязаны к конкретному enum-у. Если мы вынесем enum в шаблонный параметр класса - ничего по сути не изменится, поскольку MyEnum<EnumA> и MyEnum<EnumB> - это разные типы с разными интерфейсами, и они не сводимы к общему классу с единым интерфейсом.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #8 : 11-10-2008 12:49 » |
|
Если писать на языке со строгой типизацией без применения параметризации классов, а также без RTTI и всяких хакерских приёмов, то получится примерно так: Модуль классификатора: { Прототип реализации иерархического классификатора вида (A(C D) B(E F)). } unit Classifier;
interface
type EnumType = (A, B); EnumAType = (C, D); EnumBType = (E, F);
{ Любой уровень классификатор может как иметь, так и не иметь некоторое значение. Любой уровень кроме последнего имеет ссылку на следующий уровень. Любой уровень кроме первого имеет ссылку на предыдущий уровень. Следующий уровень может быть произвольным для текущего. Предыдущий уровень должен быть конкретным для текущего.
Пользователь может попытаться прочитать значение уровня и получит в результате: либо признак успешного выполнения операции и прочитанное значение, если какое-то значение хранится в уровне, и пользователь запросил значение типа, соответствующего типу хранящегося в уровне значения; либо признак неудачного выполнения операции, если в уровне не хранится никакого значения, или тип запрашиваемого значения не соответствует типу хранящегося значения.
Пользователь может попытаться установить значение уровня - устанавливаемое значение будет проверено на соответствие типам ранее установленных значений в предыдущем и следующем уровнях. Если такое соответствие будет обнаружено, значение будет сохранено, и пользователь получит признак успешного выполнения операции, в противном случае он получит признак неудачного выполнения операции. }
ClassifierLevelPointerType = ^ClassifierLevelType; ClassifierLevelType = object private HasValue: Boolean; NextLevelPointer: ClassifierLevelPointerType; constructor Create; procedure SetNextLevel(ANextLevel: ClassifierLevelPointerType); function Verify: Boolean; virtual; function NextConformsToCurrent: Boolean; public procedure Clear; function IsEmpty: Boolean; end;
ClassifierLevel1PointerType = ^ClassifierLevel1Type; ClassifierLevel1Type = object(ClassifierLevelType) private Value: EnumType; public constructor Create; function SetValue(AValue: EnumType): Boolean; function GetValue(var AValue: EnumType): Boolean; end;
ClassifierLevel2PointerType = ^ClassifierLevel2Type; ClassifierLevel2Type = object(ClassifierLevelType) private PreviousLevelPointer: ClassifierLevel1PointerType; ValueIn: Pointer; ValueA: EnumAType; ValueB: EnumBType; function Verify: Boolean; virtual; function PreviousConformsToCurrent(TestPreviousValue: EnumType): Boolean; public constructor Create(APreviousLevelPointer: ClassifierLevel1PointerType); function SetValueA(AValue: EnumAType): Boolean; function SetValueB(AValue: EnumBType): Boolean; function GetValueA(var AValue: EnumAType): Boolean; function GetValueB(var AValue: EnumBType): Boolean; end;
ClassifierType = object private Level1: ClassifierLevel1Type; Level2: ClassifierLevel2Type; public constructor Create; function GetLevel1Pointer: ClassifierLevel1PointerType; function GetLevel2Pointer: ClassifierLevel2PointerType; end;
implementation
constructor ClassifierLevelType.Create; begin HasValue := False; NextLevelPointer := nil; end;
procedure ClassifierLevelType.SetNextLevel; begin NextLevelPointer := ANextLevel; end;
function ClassifierLevelType.Verify; begin Verify := True; end;
function ClassifierLevelType.NextConformsToCurrent: Boolean; begin if NextLevelPointer <> nil then NextConformsToCurrent := NextLevelPointer^.Verify else NextConformsToCurrent := True; end;
procedure ClassifierLevelType.Clear; begin HasValue := False; end;
function ClassifierLevelType.IsEmpty; begin IsEmpty := not HasValue; end;
constructor ClassifierLevel1Type.Create; begin inherited Create; end;
function ClassifierLevel1Type.SetValue; var OldHasValue: Boolean; OldValue: EnumType; begin OldValue := Value; OldHasValue := HasValue; Value := AValue; HasValue := True; if not NextConformsToCurrent then begin SetValue := False; Value := OldValue; HasValue := OldHasValue; end else SetValue := True; end;
function ClassifierLevel1Type.GetValue; begin if HasValue then begin AValue := Value; GetValue := True; end else GetValue := False; end;
function ClassifierLevel2Type.PreviousConformsToCurrent; var PreviousValue: EnumType; begin if PreviousLevelPointer^.GetValue(PreviousValue) then PreviousConformsToCurrent := PreviousValue = TestPreviousValue else PreviousConformsToCurrent := True; end;
constructor ClassifierLevel2Type.Create; begin inherited Create; if APreviousLevelPointer = nil then Fail; PreviousLevelPointer := APreviousLevelPointer; ValueIn := nil; end;
function ClassifierLevel2Type.Verify; begin if not HasValue then Verify := True else if (ValueIn = @ValueA) and PreviousConformsToCurrent(A) then Verify := True else if (ValueIn = @ValueB) and PreviousConformsToCurrent(B) then Verify := True else Verify := False end;
function ClassifierLevel2Type.SetValueA; var OldHasValue: Boolean; OldValueA: EnumAType; OldValueB: EnumBType; OldValueIn: Pointer; begin OldValueA := ValueA; OldValueB := ValueB; OldValueIn := ValueIn; OldHasValue := HasValue; ValueA := AValue; HasValue := True; ValueIn := @ValueA; if not PreviousConformsToCurrent(A) or not NextConformsToCurrent then begin SetValueA := False; ValueA := OldValueA; ValueB := OldValueB; ValueIn := OldValueIn; HasValue := OldHasValue; end else SetValueA := True; end;
function ClassifierLevel2Type.SetValueB; var OldHasValue: Boolean; OldValueA: EnumAType; OldValueB: EnumBType; OldValueIn: Pointer; begin OldValueA := ValueA; OldValueB := ValueB; OldValueIn := ValueIn; OldHasValue := HasValue; ValueB := AValue; HasValue := True; ValueIn := @ValueB; if not PreviousConformsToCurrent(B) or not NextConformsToCurrent then begin SetValueB := False; ValueA := OldValueA; ValueB := OldValueB; ValueIn := OldValueIn; HasValue := OldHasValue; end else SetValueB := True; end;
function ClassifierLevel2Type.GetValueA; begin if HasValue and (ValueIn = @ValueA) then begin AValue := ValueA; GetValueA := True; end else GetValueA := False; end;
function ClassifierLevel2Type.GetValueB; begin if HasValue and (ValueIn = @ValueB) then begin AValue := ValueB; GetValueB := True; end else GetValueB := False; end;
constructor ClassifierType.Create; begin Level1.Create; Level2.Create(@Level1); Level1.SetNextLevel(@Level2); end;
function ClassifierType.GetLevel1Pointer; begin GetLevel1Pointer := @Level1; end;
function ClassifierType.GetLevel2Pointer; begin GetLevel2Pointer := @Level2; end;
end. Тестовая программа: { Тестовая программа для прототипа реализации иерархического классификатора. } program TestClassifier(Input, Output);
uses Classifier;
procedure WriteResult(Message: string; Result: Boolean); begin Write(Message, ' => '); if Result then WriteLn('Успешно.') else WriteLn('Неудачно.'); end;
var ClassifierInstance: ClassifierType; Value: EnumType; ValueA: EnumAType; ValueB: EnumBType; begin WriteLn('Тесты классификатора (A(C D) B(E F)).'); ClassifierInstance.Create; WriteResult('Установить A в уровне 1', ClassifierInstance.GetLevel1Pointer^.SetValue(A)); WriteResult('Уровень 1 имеет значение', not ClassifierInstance.GetLevel1Pointer^.IsEmpty); WriteResult('Прочитать из уровня 1 значение', ClassifierInstance.GetLevel1Pointer^.GetValue(Value)); WriteResult('Значение в уровне 1 равно A', Value = A); WriteResult('Не установить F в уровне 2', not ClassifierInstance.GetLevel2Pointer^.SetValueB(F)); WriteResult('Уровень 2 не имеет значения', ClassifierInstance.GetLevel2Pointer^.IsEmpty); WriteResult('Установить C в уровне 2', ClassifierInstance.GetLevel2Pointer^.SetValueA(C)); WriteResult('Уровень 2 имеет значение', not ClassifierInstance.GetLevel2Pointer^.IsEmpty); WriteResult('Прочитать из уровня 2 значение типа A', ClassifierInstance.GetLevel2Pointer^.GetValueA(ValueA)); WriteResult('Значение типа A в уровне 2 равно C', ValueA = C); WriteResult('Не прочитать из уровня 2 значение типа B', not ClassifierInstance.GetLevel2Pointer^.GetValueB(ValueB)); WriteResult('Не установить B в уровне 1', not ClassifierInstance.GetLevel1Pointer^.SetValue(B)); ClassifierInstance.GetLevel1Pointer^.Clear; WriteLn('Очистить уровень 1.'); WriteResult('Уровень 1 не имеет значения', ClassifierInstance.GetLevel1Pointer^.IsEmpty); WriteResult('Не прочитать из уровня 1 значение', not ClassifierInstance.GetLevel1Pointer^.GetValue(Value)); WriteResult('Установить F в уровне 2', ClassifierInstance.GetLevel2Pointer^.SetValueB(F)); WriteResult('Не установить A в уровне 1', not ClassifierInstance.GetLevel1Pointer^.SetValue(A)); ClassifierInstance.GetLevel2Pointer^.Clear; WriteLn('Очистить уровень 2.'); WriteResult('Не прочитать из уровня 2 значение типа A', not ClassifierInstance.GetLevel2Pointer^.GetValueA(ValueA)); WriteResult('Не прочитать из уровня 2 значение типа B', not ClassifierInstance.GetLevel2Pointer^.GetValueB(ValueB)); WriteResult('Установить A в уровне 1', ClassifierInstance.GetLevel1Pointer^.SetValue(A)); end. Протокол тестирования: Тесты классификатора (A(C D) B(E F)). Установить A в уровне 1 => Успешно. Уровень 1 имеет значение => Успешно. Прочитать из уровня 1 значение => Успешно. Значение в уровне 1 равно A => Успешно. Не установить F в уровне 2 => Успешно. Уровень 2 не имеет значения => Успешно. Установить C в уровне 2 => Успешно. Уровень 2 имеет значение => Успешно. Прочитать из уровня 2 значение типа A => Успешно. Значение типа A в уровне 2 равно C => Успешно. Не прочитать из уровня 2 значение типа B => Успешно. Не установить B в уровне 1 => Успешно. Очистить уровень 1. Уровень 1 не имеет значения => Успешно. Не прочитать из уровня 1 значение => Успешно. Установить F в уровне 2 => Успешно. Не установить A в уровне 1 => Успешно. Очистить уровень 2. Не прочитать из уровня 2 значение типа A => Успешно. Не прочитать из уровня 2 значение типа B => Успешно. Установить A в уровне 1 => Успешно.
Оно хоть всё и логично, но, по-моему, весьма громоздко. И громоздко потому, что типы участвующих в операциях переменных жёстко определены во время компиляции.
|
|
« Последнее редактирование: 11-10-2008 12:56 от dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #9 : 11-10-2008 14:29 » |
|
Я с Variant типом знаком по QVariant из библиотеки Qt. Судя по тому, что я видел в этом классе, он тоже знает все типы еше на стадии компиляции, и со всеми типами он умеет обрашаться. Теперь при, задании ему какого либо значения, тип которого он не знает, вызовет у него панику, еше на стадии компиляции, так как, компилятор не может подобрать соответствующий метод обработки.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
zubr
Гость
|
|
« Ответ #10 : 12-10-2008 14:11 » |
|
Как вариант: Класс, создающий древовидный список из классов-элементов, описывающих элемент классификатора. Вот примерный интерфейс на объектном паскале: PClassifierType = ^TClassifierType; TClassifierType = class(TObject) private FNext: PClassifierType; FPrev: PClassifierType; FParent: PClassifierType; FChild: PClassifierType; FData: Pointer; FName: string; public property Next: PClassifierType read FNext write FNext; property Prev: PClassifierType read FPrev write FPrev; property Parent: PClassifierType read FParent write FParent; property Child: PClassifierType read FChild write FChild; property Data: Pointer read FData write FData; property Name: string read FName write FName;
constructor Create; overload; destructor Destroy; override; end;
PClassifier = ^TClassifier; TClassifier = class(TObject) public //получение массива строк возможных классов (типа 'BAC', 'BAB', 'BPL') для конкретного класса function GetPossebleClassString(ClassifierType: TClassifierType; ClassifierList: TStrings): boolean; //создает новый классифиткатор на основе имеющегося function CreateNewClassifier(const ClassifierArray: Variant; LenArray: Integer): PClassifier; overload; //создает новый классифиткатор на основе имеющегося по строке function CreateNewClassifier(const ClassifierString: string): PClassifier; overload; constructor Create(const ClassifierArray: Variant; LenArray: Integer); overload; destructor Destroy; override; end;
Пример создания вышеуказанного класса: var A, B, C, D: TClassifierType; Classifier: TClassifier; v: Variant;
begin A := TClassifierType.Create; A.Name := 'A'; B := TClassifierType.Create; B.Name := 'B'; C := TClassifierType.Create; C.Name := 'C'; D := TClassifierType.Create; D.Name := 'D';
v := VarArrayOf([Integer(B), '(', Integer(A), '(', Integer(C), Integer(B), ')', ')']); Classifier := TClassifier.Create(v, 8);
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #11 : 12-10-2008 17:44 » |
|
zubr, не вполне уловил интерфейс - т.е. как пользователю удобно работать с такими классами. Где и как задаются базовые константы, с которыми пользователь будет сравнивать части классификатора?
Вообще, последовательности констант в классификаторе образуют слова некоторого простого языка, а сам классификатор - его грамматика + средство контроля составления пользователем слов такого языка (синтаксический анализатор).
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
zubr
Гость
|
|
« Ответ #12 : 12-10-2008 19:01 » |
|
1) описывал(и) значение классификатора; constructor Create(const ClassifierArray: Variant; LenArray: Integer); overload; - здесь в конструкторе создается древовидный список описывающий древовидную структуру классификатора. Предварительно создается массив указателей на созданные элементы классификатора и скобки, обозначающие иерархию. 2) позволял(и) бы пользователю читать отдельные позиции классификационного кода (поскольку они имеют самостоятельный смысл); function GetPossebleClassString(ClassifierType: TClassifierType; ClassifierList: TStrings): boolean; - метод по объекту элемента (можно сделать по имени) возращает список всех возможных вариантов классификатора, начиная с указанного элемента, к примеру для объекта A - вернется список 'AC', 'AB'. позволял(и) бы конструировать классификационный код в рамках допустимых классификатором возможностей - записывать отдельные позиции классификационного кода. function CreateNewClassifier(const ClassifierArray: Variant; LenArray: Integer): PClassifier; - создание нового древовидного списка, сохраняя структуру базового З.Ы. Интерфейс приблизительный и требует доработки. З.Ы.З.Ы. Может я не правильно понял задачу?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #13 : 12-10-2008 19:58 » |
|
zubr, а зачем пользователю получать список всех возможных вариантов? Тогда уж проще взять их все и перечислить в enum-е...
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
zubr
Гость
|
|
« Ответ #14 : 12-10-2008 21:18 » |
|
dimka, ну пусть это будет промежуточный внутренний метод класса, а для сравнения с пользовательской частью классификатора можно будет сделать что то типа: function GetPossebleClassString(const ClassifierName: string; ClassifierList: TStrings): boolean; - где ClassifierName - имя первого элемента function CompareClassifier(const UserClassifier:string):boolean; - метод внутри которого будет вызван GetPossebleClassString и из полученного списка сравниваться с UserClassifier Вариант перечисления в энуме не может работать в рантайм. В варианте же с древовидным списком можно классификатор формировать любой глубины вложенности, причем динамически.
|
|
|
Записан
|
|
|
|
|