-
Здравствуйте,
Допустим у нас есть два вида объектов А и Б.
С ними можно выполнять похожие по смыслы(интерфейсу) операции, только реализованы они будут по разному.
Понятно, что удобно узать стратегии. Тогда надо определить общий интерфейс, и сделать его конкретный реализации для А и Б.
Пример:
IItemsOperationsController = interface
['']
procedure AddItem;
procedure DeleteCurrentItem;
procedure SetCurrentItem(AnObject: TObject);
procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
IDeviceOperationPerformer);
end;
TCustomItemsOperationsController = class(TInterfacedObject, IItemsOperationsController)
public
procedure AddItem; virtual; abstract;
procedure DeleteCurrentItem; virtual; abstract;
procedure SetCurrentItem(AnObject: TObject); virtual; abstract;
procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
IDeviceOperationPerformer); virtual; abstract;
end;
TNetItemsOperationsController = class(TCustomItemsOperationsController)
protected
function QueryNetName(var AName: string): Boolean;
public
procedure AddItem; override;
procedure DeleteCurrentItem; override;
procedure SetCurrentItem(AnObject: TObject); override;
procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
IDeviceOperationPerformer); override;
end;
TDeviceItemsOperationsController = class(TCustomItemsOperationsController)
protected
function QueryDeviceAddress(var AnAddress: Byte): Boolean;
public
procedure AddItem; override;
procedure DeleteCurrentItem; override;
procedure SetCurrentItem(AnObject: TObject); override;
procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
IDeviceOperationPerformer); override;
end;
Тогда вызов будет такой(это код экшена):
procedure TMainForm.DeleteItemExecute(Sender: TObject);
begin
GetConfigurationUtilityController.DeleteCurrentItem;
end;
Где
GetConfigurationUtilityController
это синглетон, который делегирует вызов нужному контроллеру.
procedure TConfigurationUtilityController.DeleteCurrentItem;
begin
GetItemsOperationsController.DeleteCurrentItem;
end;
Но вот проблемма, объекты типа Б не поддерживают операцию удаления(например). Для них хорошо бы кнопочку(экшен) удалить сделать недоступной.
Вопрос: Как?
-
определяем базовый интерфейс
IItemsOperationsController = interface
procedure AddItem;
procedure DeleteCurrentItem;
procedure SetCurrentItem(AnObject: TObject);
procedure PerformOpertionWithCurretnItem(ADeviceOperationPerformer:
IDeviceOperationPerformer);
end;
определяем интерфейс наследник с поддержкой удаления
IItemsOperationsControllerWithDeleteSupport = interface(IItemsOperationsController)
procedure DeleteCurrentItem;
end;
и фсе... теперь можно определять поддержку удаления поддержкой интерфейса
-
ой, в базе обшибся, там Delete не нужен
-
> и фсе
теперь можно определять поддержку удаления поддержкой
> интерфейса
Я примерно понял. Только ты, плз пример напиши
Нопонятно кто должен проверять поддержку и дисейблить
-
IsSupported(operationName): boolean; virtual
а вызывать в Action.OnUpdate
так, что-ли?
-
> IsSupported(operationName): boolean; virtual
Ну у кого это операция будет реализована?
В данном примере в Синглетоне (GetConfigurationUtilityController — это синглетон, который делегирует вызов нужному контроллеру.)?
Что-то вроде:
procedure TMainForm.DeleteItemUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled := GetConfigurationUtilityController.GetItemsOperationsController.GetInterface(IIte msOperationsControllerWithDeleteSupport, Int);
end;
?
-
> [5] Kolan © (25.01.08 15:57)
что-то у тебя мудренно как-то всё...
наверно, в IItemsOperationsController и объявить
а переопределять ниже по иерархии
-
> а переопределять ниже по иерархии
Непонял.
Как в [5] по идее не надо будет ничего перопределять
ЗЫ
Я же поэтому и прошу примерчик, а то непонятно
-
> [7] Kolan © (25.01.08 16:16)
ну я тогда не понял твою задумку...
мне как-то старый добрый полиморфизм ближе в этом отношении
-
> procedure TMainForm.DeleteItemUpdate(Sender: TObject);
> begin
> (Sender as TAction).Enabled := GetConfigurationUtilityController.GetItemsOperationsController.GetInterface(IIte
> msOperationsControllerWithDeleteSupport, Int);
> end;
да, именно так
-
> Но вот проблемма, объекты типа Б не поддерживают операцию
> удаления(например). Для них хорошо бы кнопочку(экшен) удалить
> сделать недоступной.
Замечания к реализации. IMHO
Тогда и интерфейс IItemsOperationsController не должен ее содержать.
Добиться можно выделив операцию в отдельную сущность.
А именно либо отдельный интерфейс, либо класс на каждую операцию.
Аля command согласно GOF.
-
Удалено модератором
Примечание: дубль
-
Либо сделать посетителя операций. и сделать посетителя Menu Manager.
IItemsOperationsController = interface
function OpsVisitor(...) <- сюда твоего Menu Manager
{
}
end
-
Удалено модератором
Примечание: дубль
-
> А именно либо отдельный интерфейс
Паладин так и предлагает.
> Аля command согласно GOF
Возится с каждой командой долго выходит
-
> function OpsVisitor(
) < сюда твоего Menu Manager
Ну и что я буду говорить этому менеджеру? Задисейбли кнопку «Удалить». А если кнопок много? Что же их все пересчислять?
Паладин действительно прав. Получается я контракт нарущил. Говорю что оддерживаю удаление, а на самом деле нет
Осталось грамотно связать
-
> Kolan © (25.01.08 16:37) [15]
Menu Manager - отключает все кнопки.
MenuManager=class(CommandVisitor)
procedure OpAdd
procedure OpRemove
Procedure OpCurrent
end;
Реализатор IItemsOperationsController пробегает по всем его коммандам и делает вызов
AbstractCommand.Accept(visitor:CommandVisitor)
procedure MenuManager.OpAdd
begin
ADDITEM.VISIBLE:=TRUE;
end;
procedure MenuManager.OpRemove
begin
RemoveITEM.VISIBLE:=TRUE;
end;
Procedure MenuManager.OpCurrent
begin
....
end;
-
Удалено модератором
Примечание: да что'ж ты дублями то :)
-
Что то задублилось у меня. Sorry.
Что касаемо реализации то я уже тут предлагал улучшенную реализацию GOF паттерна VISITOR без привязки к VMT.
Кстати напишу в своем блоге об этом.
:)
-
> offff (25.01.08 16:42) [17]
> Удалено модератором
> Примечание: да что'ж ты дублями то :)
Sorry снова. Сам не знаю что за дела. Под oxffff пользователем порядок.
:)
-
> [16] offff (25.01.08 16:42)
Не, не понял
Как устроен MenuManager непонял.
> Реализатор IItemsOperationsController пробегает по всем
> его коммандам и делает вызов
Что за команды?
> ADDITEM.VISIBLE:=TRUE;
Какое-то жесткое присвоение
Нихрена не понял короче
-
> Нихрена не понял короче…
AddCommand=class;
DeleteCommand=class;
CurrentCommand=class;
AbstractCommandVisitor=class
procedure VAdd(Command:AddCommand);virtual;abstract;
procedure VCurrent(Command:CurrentCommand);virtual;abstract;
procedure VDelete(Command:DeleteCommand);virtual;abstract;
end;
MenuBarCommandVisitor=class(AbstractCommandVisitor)
procedure VAdd(Command:AddCommand);override;
procedure VDelete(Command:DeleteCommand);override;
procedure VCurrent(Command:CurrentCommand);override;
end;
AbstractCommand=class
function Exec(const param):integer;virtual;
procedure Accept(Visitor:AbstractCommandVisitor);virtual;
end;
TAbstractCommandClass=Class of AbstractCommand;
AddCommand=class(AbstractCommand)
function Exec(const param):integer;override;
procedure Accept(Visitor:AbstractCommandVisitor);override;
end;
DeleteCommand=class(AbstractCommand)
function Exec(const param):integer;override;
procedure Accept(Visitor:AbstractCommandVisitor);override;
end;
CurrentCommand=class(AbstractCommand)
function Exec(const param):integer;override;
procedure Accept(Visitor:AbstractCommandVisitor);override;
end;
DynaCommands=class
Commands:Tlist;
constructor create;overload;
constructor create(Commands:array of AbstractCommand);overload;
destructor destroy;override;
procedure Accept(Visitor:AbstractCommandVisitor);
function TryPerfomOp(CommandClass:TAbstractCommandClass;const param):boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ DynaCommands }
constructor DynaCommands.create;
begin
Commands:=TList.Create;
end;
procedure DynaCommands.Accept(Visitor: AbstractCommandVisitor);
var i:integer;
begin
for i:=0 to commands.Count-1 do AbstractCommand(commands.Items[i]).Accept(Visitor);
end;
constructor DynaCommands.create(Commands: array of AbstractCommand);
var i:integer;
begin
create;
for i:=0 to length(commands)-1 do self.Commands.Add(commands[i]);
end;
destructor DynaCommands.destroy;
var i:integer;
begin
for i:=0 to commands.Count-1 do Tobject(commands.Items[i]).Free;
Commands.free;
inherited;
end;
function DynaCommands.TryPerfomOp(CommandClass:TAbstractCommandClass;const param):boolean;
var i:integer;
begin
for i:=0 to commands.count-1 do
if (AbstractCommand(commands.Items[i]) is CommandClass) then
begin
AbstractCommand(commands.Items[i]).Exec(param);
result:=true;
exit;
end;
result:=false;
end;
{ AbstractCommand }
procedure AbstractCommand.Accept(Visitor: AbstractCommandVisitor);
begin
//
end;
function AbstractCommand.Exec(const param): integer;
begin
//
end;
{ AddCommand }
procedure AddCommand.Accept(Visitor: AbstractCommandVisitor);
begin
Visitor.VAdd(self);
end;
function AddCommand.Exec(const param): integer;
begin
//
end;
{ DeleteCommand }
procedure DeleteCommand.Accept(Visitor: AbstractCommandVisitor);
begin
Visitor.VDelete(self);
end;
function DeleteCommand.Exec(const param): integer;
begin
//
end;
{ MenuBarCommandVisitor }
procedure MenuBarCommandVisitor.VAdd(Command: AddCommand);
begin
Showmessage('Enable ADD Command');
end;
procedure MenuBarCommandVisitor.VCurrent(Command: CurrentCommand);
begin
Showmessage('Enable Current Command');
end;
procedure MenuBarCommandVisitor.VDelete(Command: DeleteCommand);
begin
Showmessage('Enable Delete Command');
end;
{ CurrentCommand }
procedure CurrentCommand.Accept(Visitor: AbstractCommandVisitor);
begin
Visitor.VCurrent(self);
end;
function CurrentCommand.Exec(const param): integer;
begin
//
end;
procedure TForm1.Button1Click(Sender: TObject);
var DynaCmds:DynaCommands;
MenuBar:MenuBarCommandVisitor;
begin
try
DynaCmds:=DynaCommands.create([AddCommand.Create,CurrentCommand.Create]);
try
//Disable menu item
MenuBar:=MenuBarCommandVisitor.Create;
try
//Menu init
DynaCmds.Accept(MenuBar);
if not DynaCmds.TryPerfomOp(DeleteCommand,self) then showmessage('Not such command DeleteCommand');
if DynaCmds.TryPerfomOp(CurrentCommand,self) then showmessage('Not such command CurrentCommand')
finally
MenuBar.free;
end;
finally
DynaCmds.free;
end;
except
end;
end;
-
> if DynaCmds.TryPerfomOp(CurrentCommand,self) then showmessage('Not
> such command CurrentCommand')
Есть такая команда. :)
-
> [21] oxffff © (26.01.08 01:24)
Смотрю
-
Понял, таки это решение с командами. А хотелось бы от них избавится.
Благодарю за обсужение. Пользу извлек такую если ты не пддерживаешь интерфейс, то нехрен говорить, что поддреживаешь.
:)
-
> Как дисэйблить кнопки если операция не поддерживается… ?
>
Button.Enabled := false;
Чего гадать-то ?
В свое время тоже маялся подобной заумью, получилось следующее:
Есть MDI-форма с тулбаром, на тулбаре кнопки, на кнопках Actions, в зависимости от допустимости операций с текущим активным MDI-дитем, кнопки должны быть открыты или закрыты.
type
TCommandType = (ctFind, ctGoto);
TCommandTypes = set of TCommandType;
IToolbarCommands = interface
['{D97A7812-091F-4E2F-84DA-08A394646BC2}']
function SupportedCommands : TCommandTypes;
procedure FindCommand;
procedure GotoCommand;
end;
И использование:
procedure TfrmMain.ActionList1Update(Action: TBasicAction;
var Handled: Boolean);
var
Supported: TCommandTypes;
IC: IToolbarCommands;
begin
if Assigned(ActiveMDIChild) and
ActiveMDIChild.GetInterface(IToolbarCommands, IC) then
Supported := IC.SupportedCommands
else
Supported := [];
actFind.Enabled := ctFind in Supported;
actGoto.Enabled := ctFind in Supported;
end;
Теперь бы делал иначе.
> Понятно, что удобно узать стратегии.
Keep It Simple, Stupid
-
> получилось следующее:
Просто и понятно.
> Теперь бы делал иначе.
А как? если не секрет? Оч. интересно
Хотелось бы, чтобы дабавлять команды было просто, дисэбл делался автоматически и код был простым
> Keep It Simple, Stupid
В смысле Short and Simple ? :)
-
> actFind.Enabled := ctFind in Supported;
> actGoto.Enabled := ctFind in Supported;
Вот это только плохой кусок, тут придется добавлять вручную
-
> Kolan © (27.01.08 09:53) [27]
> > actFind.Enabled := ctFind in Supported;
> > actGoto.Enabled := ctFind in Supported;
>
> Вот это только плохой кусок, тут придется добавлять вручную…
Рассмотри другой способ.
Команда сама предоставляет callback процедуру и/или иконку.
Твой Menu опрашивает команды на наличие этой функциональности.
И в случае отсутствия ставит произвольную иконку.
Так ты сможешь избавиться от статической привязки построителя меню к набору обрабатываемых им конанд.
+ обеспечишь подрузку внешних комманд.
-
Надо подумать
-
Kolan © (27.01.08 09:52) [26]
> А как? если не секрет? Оч. интересно…
Унаследовал бы класс формы от предка, в котором были бы методы CanFind, CanGoto (это в моем случае) и честно бы их опрашивал.
Код получился бы короче и яснее.
> В смысле Short and Simple ? :)
В смысле будь проще. Не умножай сущности сверх необходимости. Подумай что тебе важнее - иметь возможность быстро и с удовольствием прочитать исходный текст программы и понять, что она делает или городить огород с динамическими присвоениями, запутывая код нагромождением слоев.
Задай себе вопрос - что тебе чаще придется делать - читать код или дополнять его функциональность.
> Вот это только плохой кусок, тут придется добавлять вручную…
В твоем случае, как бы ты не сделал, тоже придется добавлять вручную.
Напоследок немножно помедитируй над цитатой:
"ОО-языки упрощают абстракцию, возможно, даже слишком ее упрощают. Они поддерживают создание структур с большим количеством связующего кода и сложными уровнями.
Это может оказаться полезным в случае, если предметная область является
действительно сложной и требует множества абстракций, и вместе с тем такой подход может обернуться неприятностями, если программисты реализуют простые вещи сложными способами, просто потому что им известны эти способы и они умеют ими пользоваться.
Все ОО-языки несколько сколнны "втягивать" программистов в ловушку избыточной иерархии. Чрезмерное количество уровней разрушает прозрачность: крайне затрудняется их просмотр и анализ ментальной модели, которую по существу реализует код. Всецело нарушаются правила простоты, ясности и прозрачности, а в результате код наполняется скрытыми ошибкми и создает постоянные проблемы при сопровождении.
Данная тенденция, вероятно, усугубляется тем, что множество курсов по
программированию преподают громоздкую иерархию как способ удовлетворения правила представления. С этой точки зрения множество классов приравнивается к внедрению знаний в данные. Проблема данного подхода заключается в том, что слишком часто "развитые данные" в связующих уровнях фактически не относятся у какому-либо естественному объекту в области действия программы - они предназначены только для связующего уровня.
Одной из причин того, что ОО-языки преуспели в большинстве характерных для них предметных областей (GUI-интерфейсы, моделирование, графические средства), возможно, является то, что в этих областях относительно трудно неправильно определить онтологию типов.
Например, в GUI-интерфейсах и графических средствах присутствует довольно естественное соотвествие между манипулируемыми
визуальными объектами и классами. Если выясняется, что создается большое
количество классов, которые не имеют очевидного соответствия с тем, что
происходит на экране, то, соотвественно, легко заметить, что связующий уровень стал слишком большим."
(с) Эрик Рэймонд, Искусство программирования для Unix
-
> честно бы их опрашивал
Что это значит? То есть кнопки бы всегда были активны?
> Напоследок немножно помедитируй над цитатой:
Медитирую
-
Kolan © (27.01.08 12:56) [31]
> Что это значит? То есть кнопки бы всегда были активны?
Это значит, что в обработчике события ActionListUpdate я бы смотрел, является ли текущее окно экземпляром класса, поддерживающего эти операции и если да, то опрашивал бы эти методы.
actFind.Enabled := (ActiveMDIChild is TMyClass) and TMyClass(ActiveMDIChild).CanFind
как-то так.
-
> Это значит,
Ааа, не увидел что тут
CanFind.
Понял.
Тогда на каждую операцию еще надо добавить Can операцию. Довольно просто.
Но мне кажется, что мой вариант с интерфейсам еще лучьше, бо не надо будет Can операцию добавлять и проверка попроще.
Будет так:
actFind.Enabled := ActiveMDIChild.GetInterface(IFind, TheInt);
Вообщем понятно. Много вариантов получил. Благодарю за обсуждение.
-
> Будет так:
> actFind.Enabled := ActiveMDIChild.GetInterface(IFind, TheInt);
>
Тогда уж:
actFind.Enabled := Supports(ActiveMDIChild, IFind);
Небольшая разница, но код становится яснее.
-
> Небольшая разница, но код становится яснее.
Согласен.
Имхо ниче так решение особенно в сочетании со стратегиями. Можно сделать мелкие интерфейсы (IFind, IGoto), а стратегии(или MDI Children) будут реализовывать их комбинации.
Благодарю. :)