I am trying to define the best way to refactor the project I am working on.
Due to lack of good design almost all project is made up of:
1) forms containing business logic
2) huge datamodules (1 per form + some extra ones)
3) some units that contain common code (libraries)
There is no OOP (except for some small areas), code reuse it is at a minimum level.
One problem is also that dataaware controls are used, so it was very simple to drop many datasets+datasources on the datamodules and link directly to the DB in an highly coupled manner.
Ideally i would like to extract classes, like TCustomer, TEmployee, to get advantage os encapsulation and to make it possible to create new UI in the future without duplicating all code.
Anyway my question is: how do I can keep dealing with dataaware controls? Should I implement a function that returns a dataset, and I link the dataawarecomponent.datasource to the function result?
function TCustomer.LoadByID(aCustomerID: integer): TDataset
?
You are bound to the architecture your application was designed around. Don't try to fight against it. Let the data aware controls do what they are good at, data synchronization. If your controls are already bound to their data sources using the dfm there shouldn't be a problem.
What you do need to refactor is any event handlers you have attached to your controls. I suggest you take a look at the Supervising Controller pattern. I've found example implementations for:
While there are a few examples of UI architectural patterns in Delphi those that are geared toward desktop applications tend to be about the Passive View rather than Supervising Controller. So here is my take on it.
You'll want to start with defining at least one interface for each form in your application. I say at least one because some forms are complex and may need to be broken into multiple interfaces.
IProductView = interface
end;
Then have your form implement it.
TProductForm = class(TForm, IProductView)
...
end;
Next you'll need a presenter/controller. This will handle everything except data synchronization.
TProductPresenter = class
private
FView: IProductView;
public
constructor Create(AView:IProductView);
end;
Create an private field in your form class and create/free the presenter when the form is created/freed. Whether you use the form's constructor/destructor or the onCreate/onDestroy events doesn't matter much.
TProductForm = class(TForm, IProductView)
private
FPresenter: TProductPresenter;
public
constructor Create;
...
end;
implementation
TProductForm.Create
begin
FPresenter := TProductPresenter.Create(self);
end;
Now when you need the form or one of its controls to respond to an event delegate responsibility to the presenter. Lets assume you need to check that the product name uses proper capitalization.
TProductForm.NameDBEditChange(Sender: TObject);
begin
FPresenter.ValidateName;
end;
Rather than pass the control or its text property as an argument you expose the data as a property on the interface...
IProductView = interface
function GetName:string;
procedure SetName(Value: string);
property Name: string read GetName write SetName;
...and implement GetName
and SetName
on the form.
TProductForm.GetName: string;
begin
Result := NameDBEdit.Text;
end;
TProductForm.SetName(Value: string);
begin
NameDBEdit.Text := Value;
end;
Its important to expose the data in the simplest form possible. You don't want the presenter depending on the product name being stored in a TDBEdit. The presenter should only see what you explicitly allow it to see through the interface. The main benefit of this is you can modify the form as much as you want(or replace it entirely) and as long as it adheres to the interface no changes will need to be made to the presenter.
Now that all your business logic has been moved to your presenter it will resemble a god class. Your next step will be to refactor that logic into appropriate classes broken up by responsibility. When you reach this point you're in a much better position to attempt an architectural redesign (if your still considering it).
"Wow! That looks like a lot of work!" you might say. You'd be right (but then you knew it would be a lot of work before you got started). It doesn't have to be done all at once. None of these steps is changing the behavior of the logic just where it takes place.
If there's no good design and no real OOP in the code then considering it's complexity you should first start by creating a design describing it's current functionality. Yes, that means you'll be busy writing a lot of documentation at first. But it allows you to split up the whole project into logical/functional parts which you could use to focus on once this documentation is finished. You could then refactor each part separately, or possibly rewrite those parts even.
Projects this complex aren't always practical to refactor. You should return to the original design (thus create it since you have none) and then look at your code and consider what is faster: refactoring or rewriting...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With