Buenas gente,
a ver si me podeis echar una manita con el rediseño de un programa.
Tengo un form principal, una serie de frames y una máquina de estados para controlar la visualización de los frames, entre otras cosas. Hasta ahora el diseño era un poco chapuza, el form principal contiene las referencias a todos los frames y la creación de los estados de la máquina. A parte, cada frame contiene referencias a otros frames "hermanos" que necesita y referencias al frame principal, con lo cual tengo un lío tremendo de referencias circulares.
Algunas partes del código
Form principal
Código:
TfrMain = class(TForm)
frToolbar: TfrToolbar;
procedure mmExitClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
frNavBarLeft :TfrNavBarLeft;
frNavBarRight :TfrNavBarRight;
frEditPatient :TfrEditPatient;
frViewPatient :TfrViewPatient;
frBlank :TfrBlank;
frViewImage :TfrViewImage;
frViewStudy :TfrVIewStudy;
frViewImageTwin :TfrViewImageTwin;
frCamera :TfrCamera;
frEditStudy :TfrEditStudy;
frPrint :TfrPrint;
frProcesses :TfrProcesses;
end;
var
frMain: TfrMain;
implementation
{$R *.dfm}
procedure TfrMain.mmExitClick(Sender: TObject);
begin
frMain.Close;
end;
procedure TfrMain.FormCreate(Sender: TObject);
begin
frBlank :=TfrBlank.Create(Self);
frNavBarLeft :=TfrNavBarLeft.Create(Self);
frViewPatient :=TfrViewPatient.Create(self);
frEditPatient :=TfrEditPatient.Create(Self);
frViewImage :=TfrViewImage.Create(Self);
frViewStudy :=TfrViewStudy.Create(Self);
frViewImageTwin :=TfrViewImageTwin.Create(Self);
frCamera :=TfrCamera.Create(Self);
frEditStudy :=TfrEditStudy.Create(Self);
frPrint :=TfrPrint.Create(Self);
frProcesses :=TfrProcesses.Create(Self);
frBlank.Parent :=Self;
frNavBarLeft.Parent :=Self;
frViewPatient.Parent :=Self;
frEditPatient.Parent :=Self;
frViewImage.Parent :=Self;
frViewStudy.Parent :=Self;
frViewImageTwin.Parent :=Self;
frCamera.Parent :=Self;
frEditStudy.Parent :=Self;
frPrint.Parent :=Self;
frProcesses.Parent :=Self;
sessiondate:=now;
states:=TStateMachine.Create;
frNavBarLeft.ReLoadTreeView;
frMain.TabStop:=false;
states.Add (
stBlank,
frBlank.frEnter,
frBlank.frExit,
frBlank,
true,
frToolBar.menu
);
states.Add (
stViewPatient,
frViewPatient.frEnter,
frViewPatient.frExit,
frViewPatient,
true,
frToolBar.menu
);
// y bla bla bla... demás estados ....
states.Enter(stBlank);
end;
Máquina Estados (parte de interés)
Código:
TProc = procedure of object;
{
TState:
state type for the State Machine
}
TState = record
id:integer; //id state (for ex. stBlank, stViewPatient)
inProc:TProc; //initial process
outProc:TProc; //fianl process
frame:TFrame; //frame corresponding for this state
navBarL:Boolean; //bars must be shown
menu:TMainMenu; //main menu for this frame (use to be main menu of the
//toolbar frame
end;
{
TStateMachine:
Controls the state of the program and manage the interface active frame
}
TStateMachine = class
private
states:array of TState; //posible states
procedure stExit(id:integer);
public
current:integer; // current state
last:integer; //last state (for goback function)
OnRecordChange:TNotifyEvent;
stack:TStack;
procedure Add(id:integer;ip,op:TProc;frame:TFrame;nbl:boolean;menu:TMainMenu); procedure SetInProc(proc:TProc;state:integer);
procedure Enter(id:integer);
constructor Create;
destructor Destroy; override;
var
states :TStateMachine;
sessionDate :TDateTime;
implementation
procedure TStateMachine.Add(id:integer;ip,op:TProc;frame:TFrame;
nbl:boolean;menu:TMainMenu);
var
l :integer;
begin
l := Length(states);
SetLength(states,l+1);
states[l].id := id;
states[l].inProc := ip;
states[l].outProc := op;
states[l].frame := frame;
states[l].menu := menu;
states[l].navBarL := nbl;
end;
procedure TStateMachine.Enter(id: integer);
var
i,j:integer;
begin
for i := 0 to length(states)-1 do
if id = states[i].id then
begin
stExit(current);
current := id;
frMain.frNavBarLeft.Visible := states[i].navBarL;
frMain.Menu := states[i].menu;
with states[i].frame do
begin
Align := alClient;
Visible := True;
end;
states[i].inProc;
end;
end;
procedure TStateMachine.stExit(id:integer);
var
i :integer;
begin
for i := 0 to length(states)-1 do
if id=states[i].id then
begin
last := current;
current := -1;
states[i].outProc;
states[i].frame.Align := alNone;
states[i].frame.Visible := False;
end;
end;
Pues bien, ahora la idea es hacer un diseño más correcto, algo de este tipo:
Y un pequeño resumen, de cómo considero que son/deberían ser las cosas, pero que no consigo ver claro cómo hacer:
- Un frame representa un "estado" del programa claramente definido.
- Las acciones del usuario cambian el estado del programa, por lo tanto debe haber un sistema para cambiar entre los estados correspondientes.
- Cada frame debe ser independiente y no debe incluir units de otros frames "hermanos".
- Los frames NO deben referenciar al formulario principal directamente, ni otros miembros del mismo.
- Para los elementos de la pantalla que si o si deben compartirse entre frames, como los botones, o que deben ser persistentes, como los thumbnails, debe haber un sistema que publique globalmente los diferentes bloques (botonera, thumbnails), para que cada frame pueda obtener y manejar los bloques que necesita y conozca, mientras el resto de los bloques permanece desactivado.
- Debe haber un sistema para transferir datos entre los distintos frames durante un cambio de estado, ya sean imagenes, claves de la DB, u otros parametros.
- El uso y la asociacion de los frames a los estados, debe ser lo mas transparente posible ( o sea, no tener que agregar el frame en 50 lugares diferentes)
- Cada frame o estado deberia tener un procedimiento de entrada y de salida, en el que recibe el estado desde o hacia el cual se transfiere, junto con parametros opcionales. Este procedimiento debe ser privado, y no utilizado afuera del frame en cuestion.
- Los parametros utilizados para la transferencia de datos deben estar especificados en una estructura estandar, como podria ser una lista de nombres y valores.
Ahora las preguntas...
¿Qué patrones de diseño veis aquí?
¿Singleton para la máquina de estados?
¿Alguna manera de evitar el rollo de las referencias circulares... sin hacer el chapuceo de los uses en la implementación?
(Perdón por el rollo :P )