Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2010.08.27;
Скачать: CL | DM;

Вниз

Иерархия классов настроек   Найти похожие ветки 

 
bss   (2010-05-14 10:29) [0]

Существует некая иерархия классов настроек. Базовый класс:

TBaseSetting = class
 constructor Create(XMLSettingNode: IXMLDOMNode); virtual;
...
 property Prop1;
 property Prop2;
 property Prop3;
...
end;


Достаточно удобно, при создании класса настроек передается ссылка на XML ноду откуда класс забирает свои данные. Потомки имеют вид:

TMySetting = class(TBaseSetting)
 constructor Create(XMLSettingNode: IXMLDOMNode); override;
...
 property Prop4;
 property Prop5;
...
end;


Конструктор потомка забирает свои настройки, родитель забирает свои.

Есть некоторая некрасивость, связанная с тем, когда родственные настройки хочется упаковать в свое пространство имен. Типа такого:

TBaseSetting = class
 constructor Create(XMLSettingNode: IXMLDOMNode); virtual;
...
 property Name;
 (?)property(?) DatabaseSetting;
...
end;


То есть, настройки связанные с базой данных, допустим, хочется выделить в поле DatabaseSetting. И проблема в том, что наследник вполне возможно захочет расширить и добавить какую-то настройку, связанную с базой данных. Что тогда есть DatabaseSetting? Структура record не подходит, тогда класс?

TDatabaseSetting = class
 constructor Create(XmlDBNode: IXMLDOMNode); virtual;
...
 property DBProp1;
 property DBProp2;
 property DBProp3;
...
end;


Допустим, TBaseSetting содержит поле TDatabaseSetting, а класс TMySetting должен хранить поле TMyDatabaseSetting. Но вот как красиво сделать создание этих экземпляров?

Первое что приходит в голову: TBaseSetting заполняет поле экземпляром TDatabaseSetting, а наследник TMySetting скрывает это поле новым полем и заполняет экземпляром TMyDatabaseSetting. На примере:

Иерархия:
TBaseSetting = class ;
TMySetting = class(TBaseSetting);

TDatabaseSetting = class;
TMyDatabaseSetting = class(TMyDatabaseSetting);


Реализация:

TBaseSetting = class
...
 FDatabaseSetting: TDatabaseSetting;
...
public
 constructor Create(...); virtual;
 property DatabaseSetting: TDatabaseSetting read FDatabaseSetting;
end;

TMySetting = class(TBaseSetting)
...
 FDatabaseSetting: TMyDatabaseSetting;
...
public
 constructor Create(...); override;
 property DatabaseSetting: TMyDatabaseSetting read FDatabaseSetting;
end;

constructor TMySetting.Create(...);
begin
...
 inherited ;
 FDatabaseSetting := TMyDatabaseSetting.Create(...);
...
end;


С одной стороны вариант. С другой стороны налицо, допустим, избыточность. Данные относящиеся к TDatabaseSetting будут считаны дважды, первый раз при создании класса TDatabaseSetting в конструкторе TBaseSetting, второй раз при создании класса TMyDatabaseSetting в конструкторе TMySetting и соответственно наследственном вызове конструктора TDatabaseSetting, который предок для TMyDatabaseSetting.

Конечно, в конструкторе базового класса можно предусмотреть что-то типа такого:

TBaseSetting.Create(...);
begin
 if not Assigned(FDatabaseSetting) then ...
end;


А в предках писать:

TMySetting.Create(...);
begin
 FDatabaseSetting := TMyDatabaseSetting.Create(...);
 inherited FDatabaseSetting := FDatabaseSetting;
 inherited;
 <Дальнейшая инициализация>
end;


Но код очень ненативный, в том числе правильность работы зависит от порядка вызова inherited, хотя обычно все привыкли в наследниках перекрывая конструктор писать inherited первой строчкой...

Ну и в целом создается впечатление некоторой кривоватости решения... Может, кто уже решал такие задачи и придумал красивое, логичное решение?


 
Медвежонок Пятачок ©   (2010-05-14 10:33) [1]

а зачем вообще декларированы классы и свойства?
Не проще ли и не гибче ли обычные отдельно стоящие функции, читающие значение свойства прямо из xml?


 
jack128_   (2010-05-14 10:37) [2]

фабричным методом настройки DB создавать?
TBaseSetting = class
protected
 function CreateDBSettings: TDatabaseSetting; virtual; // создает TDatabaseSetting
end;

TMySetting = class
protected
 function CreateDBSettings: TDatabaseSetting; override; // создает TMyDatabaseSetting
end;


 
Медвежонок Пятачок ©   (2010-05-14 10:38) [3]

Структура record не подходит, тогда класс?

Зачем?
Обычный IXMLDOMNode и набор xpath выражений для доступа к узлам/атрибутам в которых лежат данные.

ключевой момент в том, что xml документ - это уже само по себе и структура хранения и механизм декларативного доступа к данным.

не надо никаких доп. структур вообще.


 
bss   (2010-05-14 10:50) [4]


> Не проще ли и не гибче ли обычные отдельно стоящие функции,
>  читающие значение свойства прямо из xml?

тогда каждый класс должен уметь открывал файл непосредственно с XML - это лишний гемор с синхронизацией.. А так файлы открывает/закрывает менеджер, создавая классы настроек, передавая им XML ноды...

Плюс структура XML ведь может быть сложной, иерархической. Может быть множество элементов с одинаковыми тегами.. Я думаю так будет неудобно, а в данном случае все разложено по полочкам, созданы массивы если нужно и так далее...


> фабричным методом настройки DB создавать?

не очень понял идею... Какая реализация у CreateDBSettings первого и второго класса?


 
Медвежонок Пятачок ©   (2010-05-14 10:55) [5]

тогда каждый класс должен уметь открывал файл непосредственно с XML -

если уж класс, то он вообще не должен работать с файлом.
он должен работать с IXMLDOMNode рассматривая его как рутовый элемент настроек.

а файл должен открываться этажом выше, тем более, что это может быть не файл, а урл на документ настроек.

я поначалу тоже пошел по подобному пути: создавал структуру нодов в документе, затем создавал рекорды им соответствующие, затем писал кучу кода гоняющего данные из xml в структуры и обратно, пока наконец не пришел к выводу, что все это выглядит крайне коряво и совершенно избыточно.

с тех пор работа идет исключительно с нодами и никаких рекордов и тем более классов.


 
bss   (2010-05-14 11:02) [6]


> Зачем?

я не понял, что ты предлагаешь. Вот есть функциональный класс:

procedure TWorkClass.Update(...);
begin
 if MySetting.Prop3 > 0 then ...;
end;


Ты предлагаешь заменить это на:

procedure TWorkClass.Update(...);
begin
 if GetXMLSetting(FilePath, XPath...) > 0 then ...;
end;


отказавшись от настроечных классов. Или ты предлагаешь делать так:

TMySetting = class ...
...
 function getProp2: integer;
public
 property Prop2: integer read getProp2;
...
end;

procedure TMySetting.getProp2;
begin
 Result := GetFromXML(XMLDoc, cParamProp2, ...);
end;


?


 
Медвежонок Пятачок ©   (2010-05-14 11:03) [7]

как у тебя выглядит вызов редактора настроек?
примерно вот так?
function EditDatabaseSettings(ASettings : TSomeSettingsClass) : boolean;

а у меня вот так:

function EditDatabaseSettings(ASettingsNode : IXMLDOMNode) : boolean;

или вот так

function EditDatabaseSettings(ASettingsNode : IXMLDOMNode; const RootXPath : string = "") : boolean;

во втором случае в качестве параметра может передаваться как сам искомый нод с пустым путем, так и весь документ в целом с путем к ноду с настройками.

и никакого гемора с синхронизацией.
никакого гемора с необходимостью синхронизировать иерархию классов при изменении структуры документа.


 
jack128_   (2010-05-14 11:04) [8]

Мдя. Детский сад - точно.

TBAseSettings.Create;
begin
 FDBSettings := CreateDBSetttings;
end;

TBaseSettings.CreateDBSettings
begin
 Result := TDBSettings.Create;
end;

TMyBaseSettings.CreateDBSettings
begin
 Result := TMyDBSettings.Create;
end;


 
Медвежонок Пятачок ©   (2010-05-14 11:04) [9]

Ты предлагаешь заменить это на:

Я предлагаю отказаться от :
1. Настроечных классов.
2. От структур хранения (рекордов)


 
bss   (2010-05-14 11:27) [10]


>
> Я предлагаю отказаться от :
> 1. Настроечных классов.
> 2. От структур хранения (рекордов)

то есть, ты предлагаешь вариант:

procedure TWorkClass.Update(...);
begin
if GetXMLSetting(FilePath, XPath...) > 0 then ...;
end;


?


 
Медвежонок Пятачок ©   (2010-05-14 11:37) [11]

Этот вариант черезчур жесток.

procedure TWorkClass.Update(...);
begin
if GetXMLSetting(FilePath, XPath...) > 0 then ...;
end;

в нем подразумевается что нужно открыть документ из файла, затем найти в нем нод и считать значение.

документ надо открывать при необходимости, а затем передавать его ноды в качестве параметров функций.

procedure TWorkClass.Update(iNode : IXMLDOMNode);
begin
if GetXMLSettingInt(iNode, XPath) > 0 then
begin
 SetSomePropertyInt(GetXMLSettingInt(iNode, AnotherXPath));
 ....
 SetSomePropertyStr(GetXMLSettingStr(iNode, AnotherXPath));
end;
end;

как-то вот так.


 
bss   (2010-05-14 11:40) [12]


> как у тебя выглядит вызов редактора настроек?
> примерно вот так?
> function EditDatabaseSettings(ASettings : TSomeSettingsClass)
> : boolean;

а как так возможно? Вообще-то выглядит так:

SomeSettingClass.Edit ;


> и никакого гемора с синхронизацией.
> никакого гемора с необходимостью синхронизировать иерархию
> классов при изменении структуры документа.

ну зато ошибка в XML документе выяснится только на этапе выполнения, когда потребуется этот параметр. В моем случае на этапе загрузки, это удобнее для отладки.

И я все таки не понимаю чем твой подход проще? Да, кода немало с одной стороны. Но у меня код какой? Типа такого:

FID := GetValueInt(XMLSettingNode, cParamID, true);
FCaption := GetValueString(XMLSettingNode, cParamCaption, true);
FShowInMenu := GetValueBoolean(XMLSettingNode, cParamShowInExplorer, true);


Это загрузка. Для трех параметров три строки. Еще три строки будут на сохранение.

По 2 строчки кода на одно property простого типа.
У тебя на одно property будет тоже 2 строчки кода - код get"ера и код set"ера. Да еще обрамленные объявлениями прототипа метода. То есть, в твом случае кода больше получается. Смысл?


 
Медвежонок Пятачок ©   (2010-05-14 11:46) [13]

Это загрузка. Для трех параметров три строки. Еще три строки будут на сохранение.

Да понял я это.
И у меня примерно такие же методы.
Но чтобы ими воспользоваться я не создаю классы.

У тебя наследование и полиморфичность обеспечивают гибкость.
А у меня этим занимаются строки представляющие xpath.
Все так же гибко, но нет мусорного кода.

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


 
bss   (2010-05-14 12:00) [14]


> Все так же гибко, но нет мусорного кода

так я вот и не понимаю этого. Какой именно код ты называешь мусорным? У тебя кода по строчкам выходит больше же


 
Медвежонок Пятачок ©   (2010-05-14 12:23) [15]

Все твои настроечные классы - это и есть мусорный код.

Чем они по сути занимаются?
Они просто "знают" что читать и откуда читать.

То же самое можно обеспечить xpath-строками а не классами.

но хозяин - барин. не нравится, не навязываю.


 
han_malign   (2010-05-14 12:29) [16]


> Какой именно код ты называешь мусорным?

- у тебя обращение к настройкам 1000000 раз в секунду? Ну или хотя бы 500?
Тогда, возможно, повторное кэширование структуры даст небольшой  прирост производительности... Правда памяти в несколько раз больше сожрется...

Не плодите сущностей - это приближает тепловую смерть вселенной...

З.Ы. Хотя попробуй..., это полезно. Когда увидишь, что навигация по твоей оболочке не проще(в лучшем случае), сам поймешь - а надо ли делать еще и третий уровень кэширования...


 
bss   (2010-05-14 12:37) [17]


> Все твои настроечные классы - это и есть мусорный код

а, само объявление прототипов классов? Согласен, у тебя этого нету...

Но там возникает хорошее преимущество, полное чтение в одном месте позволяет сразу выявить ошибки в XML-файле, например, отсутствие обязательного параметра. Конечно, можно сказать про XSD... В общем, наверное, кому как удобнее...


 
Суслик__   (2010-05-14 12:43) [18]

нет счастья в этих иерархиях, только запутаешься.
наследовать реализации (т.е. код) надо, а не интерфейсы.

имхо, конечно.


 
bss   (2010-05-14 13:22) [19]

Суслик__, расшифруй мысль?


 
Медвежонок Пятачок ©   (2010-05-14 13:26) [20]

Но там возникает хорошее преимущество, полное чтение в одном месте позволяет сразу выявить ошибки в XML-файле, например, отсутствие обязательного параметра.

Оно и у меня есть.
Благодаря чему приложение работает даже если при запуске нет самого xml файла.
Просто существует либо одна процедура CheckConfig, которая создает файл и базовую иерархию узлов если что-то отсутствует, либо несколько предметно-заточенных прцедур.


 
Суслик__   (2010-05-14 14:01) [21]


> bss   (14.05.10 13:22) [19]
>
> Суслик__, расшифруй мысль?


мысль такая. я, опираясь на свой и только свой личный опыт проектирования и разработки систем, могу высказать такое личное мнение.

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

прежде все наследование должно использоваться для того, чтобы реализовывать конкретных потомков асбтрактных интерфейсов (см. например паттерн GoF Стратегия). т.е. речь про полиформизм.

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

В твоем бы случае я бы не стал делать элементы предметной области в предке (т.е.не делал бы свойств с настройками). В предке я бы сосредоточил сервисный код - чтение настроек, запись (если есть), проверки и т.д. Для каждого конкретного случая, где нужны настройки сделал бы потомка, в которой бы вынес все настройки для этого случая. При этом опытным путем я бы отшлифовал данную иерархию так, чтобы в потомках не было сервисного кода (он должен быть в предке), а в предке, соответственно, не было прикладного кода. Конечно, все зависит от твоего случая. Но, на мой взгляд, легче поддерживать целостные стуктуры, нежели логику размазанную по иерархии.


 
Игорь Шевченко ©   (2010-05-14 14:28) [22]

Суслик__   (14.05.10 14:01) [21]

"ОО-языки упрощают абстракцию, возможно, даже слишком ее упрощают. Они поддерживают
создание структур с большим количеством связующего кода и сложными уровнями.
Это может оказаться полезным в случае, если предметная область является
действительно сложной и требует множества абстракций, и вместе с тем такой
подход может обернуться неприятностями, если программисты реализуют простые
вещи сложными способами, просто потому что им известны эти способы и они умеют
ими пользоваться.
Все ОО-языки несколько сколнны "втягивать" программистов в ловушку избыточной
иерархии. Чрезмерное количество уровней разрушает прозрачность: крайне
затрудняется их просмотр и анализ ментальной модели, которую по существу
реализует код. Всецело нарушаются правила простоты, ясности и прозрачности,
а в результате код наполняется скрытыми ошибкми и создает постоянные проблемы
при сопровождении.
Данная тенденция, вероятно, усугубляется тем, что множество курсов по
программированию преподают громоздкую иерархию как способ удовлетворения
правила представления. С этой точки зрения множество классов приравнивается
к внедрению знаний в данные. Проблема данного подхода заключается в том, что
слишком часто "развитые данные" в связующих уровнях фактически не относятся
у какому-либо естественному объекту в области действия программы -
они предназначены только для связующего уровня.
Одной из причин того, что ОО-языки преуспели в большинстве характерных для них
предметных областей (GUI-интерфейсы, моделирование, графические средства),
возможно, является то, что в этих областях относительно трудно неправильно
определить онтологию типов. Например, в GUI-интерфейсах и графических средствах
присутствует довольно естественное соотвествие между манипулируемыми
визуальными объектами и классами. Если выясняется, что создается большое
количество классов, которые не имеют очевидного соответствия с тем, что
происходит на экране, то, соотвественно, легко заметить, что связующий уровень
стал слишком большим.
"


 
Суслик__   (2010-05-14 14:56) [23]


> Игорь Шевченко ©   (14.05.10 14:28) [22]

да, где-то так.

кто написал?

такое ощущение, что я читал текст, только кто не помню


 
bss   (2010-05-14 15:01) [24]


> В предке я бы сосредоточил сервисный код - чтение настроек,
>  запись (если есть), проверки и т.д. Для каждого конкретного
> случая, где нужны настройки сделал бы потомка, в которой
> бы вынес все настройки для этого случая. При этом опытным
> путем я бы отшлифовал данную иерархию так, чтобы в потомках
> не было сервисного кода (он должен быть в предке), а в предке,
>  соответственно, не было прикладного кода

хм... Не понял нифига. Давай поговорим в контексте справочников в базе данных. Например, моя структура хранит описание справочника.

Рассмотрим линейный справочник (фактически список значений). У него есть, например, параметры:

TLinear = class...
 property TableName;      // имя таблицы, где хранится справочник
 property PKName;          // имя поля с primary ключом
 property ValueName;      // имя поля со значениями
...
end;


Теперь рассмотрим иерархический справочник. Чем он отличается от линейного? С точки зрения реляционной БД только одним полем, ссылкой на родителя. Значит пишем так:

THierarchic = class(TLinear)
 property ParentName;   // имя поля со ссылкой на родителя
...
end;


Что неверного в этой иерархии?


 
Суслик__   (2010-05-14 15:13) [25]


> Что неверного в этой иерархии?

да все верно.
это не совсем похоже на прикладной код.
это скорее код из какой-то библиотеки.
тут можно и иерархию.

сложно сказать, у меня были больше теоретический рассуждения, которые Игорь подкрепил ссылкой.


 
Mystic ©   (2010-05-14 15:27) [26]

Мне больше нравиться такое:

unit ClassIo;

interface

uses Classes;

procedure WritePersistent(S: TStream; Instance: TPersistent);
procedure ReadPersistent(S: TStream; Instance: TPersistent);

procedure WriteCollection(S: TStream; Instance: TCollection);
procedure ReadCollection(S: TStream; Instance: TCollection);

type
 TWriterEx = class(TWriter)
 public
   procedure WriteCollectionEx(Instance: TCollection);
   procedure WritePersistent(Instance: TPersistent);
 end;

 TReaderEx = class(TReader)
 public
   procedure ReadCollectionEx(Instance: TCollection);
   procedure ReadPersistent(Instance: TPersistent);
 end;

implementation

{ TWriterEx }

procedure TWriterEx.WriteCollectionEx(Instance: TCollection);
begin
 WriteCollection(Instance);
end;

procedure TWriterEx.WritePersistent(Instance: TPersistent);
begin
 WriteProperties(Instance);
 WriteListEnd;
end;

{ TReaderEx }

procedure TReaderEx.ReadCollectionEx(Instance: TCollection);
var
 Value: TValueType;
begin
 Value := ReadValue;
 if Value <> vaCollection then
   raise EStreamError.Create("vaCollection excepted");
 ReadCollection(Instance);
end;

procedure TReaderEx.ReadPersistent(Instance: TPersistent);
begin
 while not EndOfList do ReadProperty(Instance);
 ReadListEnd;
end;

procedure WriteCollection(S: TStream; Instance: TCollection);
var
 Writer: TWriterEx;
begin
 Writer := TWriterEx.Create(S, 4096);
 try
   Writer.WriteCollectionEx(Instance);
 finally
   Writer.Free;
 end;
end;

procedure ReadCollection(S: TStream; Instance: TCollection);
var
 Reader: TReaderEx;
begin
 Reader := TReaderEx.Create(S, 4096);
 try
   Reader.ReadCollectionEx(Instance);
 finally
   Reader.Free;
 end;
end;

procedure WritePersistent(S: TStream; Instance: TPersistent);
var
 Writer: TWriterEx;
begin
 Writer := TWriterEx.Create(S, 4096);
 try
   Writer.WritePersistent(Instance);
 finally
   Writer.Free;
 end;
end;

procedure ReadPersistent(S: TStream; Instance: TPersistent);
var
 Reader: TReaderEx;
begin
 Reader := TReaderEx.Create(S, 4096);
 try
   Reader.ReadPersistent(Instance);
 finally
   Reader.Free;
 end;
end;

end.


И потом:


 TProfile = class(TPersistent)
 private
   FTest: string;
 published
   Test: string read FTest write FTest;
 end;

var
 Profile: TProfile;

procedure LoadProfile(const FileName: string);
var
 S: TFileStream;
begin
 S := TFileStream.Create(FileName, fmOpenRead);
 try
   ReadPersistent(S, Profile);
 finally
   S.Free();
 end;
end;  

procedure SaveProfile(const FileName: string);
var
 S: TFileStream;
begin
 S := TFileStream.Create(FileName, fmCreate);
 try
   WritePersistent(S, Profile);
 finally
   S.Free();
 end;
end;  

initialization
 Profile := TProfile.Create();

finalization
 Profile.Free();


 
Игорь Шевченко ©   (2010-05-14 15:36) [27]

Суслик__   (14.05.10 14:56) [23]


> кто написал?


Эрик Рэймонд, "Искусство программирования для Unix"


 
bss   (2010-05-14 15:58) [28]


> это не совсем похоже на прикладной код.
> это скорее код из какой-то библиотеки.

это классы, хранящие настройки

> Мне больше нравиться такое:

не очень понятен функционал примера. Самое интересное (например, реализация ReadProperty) не показано ))

Через RTTI вычисляются имена полей и считываются автоматом из XML?


 
_Юрий ©   (2010-05-16 10:55) [29]


> Через RTTI вычисляются имена полей и считываются автоматом
> из XML?


Вполне годный вариант.


 
Mystic ©   (2010-05-16 12:54) [30]


> Самое интересное (например, реализация ReadProperty) не
> показано ))


Это стандартный метод из Classes. На самом деле мы просто читаем/записываем TPersistern-объект их/в поток, так, как это делают формы. Соответственно формат получается на XML, а DFM. По умолчанию формат бинаррый, но есть метод перекодировки.



Страницы: 1 вся ветка

Текущий архив: 2010.08.27;
Скачать: CL | DM;

Наверх




Память: 0.59 MB
Время: 0.082 c
3-1240848334
IGray
2009-04-27 20:05
2010.08.27
Delphi2009 + BDE + Юникод = НЕВОЗМОЖНО??


2-1265978961
pavelkq
2010-02-12 15:49
2010.08.27
Поразрядная сортировка списка слов


15-1264678479
Илья_
2010-01-28 14:34
2010.08.27
Norton Internet Security заблокировал и удалил файл SysConst.dcu


15-1268978113
oldman
2010-03-19 08:55
2010.08.27
Драйвер для Xerox Phaser 3117 под МЕ


15-1273641886
Дмитрий С
2010-05-12 09:24
2010.08.27
Что большее зло: goto или while true do ?