Главная страница
    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.57 MB
Время: 0.068 c
4-1237968794
Новичок
2009-03-25 11:13
2010.08.27
Зависание меню после установки хука


15-1271622604
Юрий
2010-04-19 00:30
2010.08.27
С днем рождения ! 19 апреля 2010 понедельник


3-1242884568
dort12
2009-05-21 09:42
2010.08.27
Сохранение всех файлов с blob поля


15-1265194467
зодиак
2010-02-03 13:54
2010.08.27
Странный метод


2-1270793257
Abcdef123
2010-04-09 10:07
2010.08.27
Проблема после перевода проекта из Дельфи 6 в Дельфи 2007.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский