Текущий архив: 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.57 MB
Время: 0.068 c