Example of Delphi refactoring involving data aware controls and datamodules with direct access to db tables
Asked Answered
I

2

7

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

?

Inimical answered 29/10, 2010 at 12:24 Comment(5)
Data-aware components link to TDataSources, not TDataSets. Did you mean those when speaking about the function?Topflight
Something I forgot to ask, are the queries and your project uses to get data (relatively) complex or simple? Same question for stored procedures (or whatever the project uses).Topflight
I mean the data is in the dataset, so tha already existing datasource can "point" to the function result.Inimical
it is a huge project, some queries are simple some returns complex resultsetsInimical
Don't bother about a (huge) DataModule per Form. Often that's even preferred.Fritzie
E
9

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.

Advantages

  • UI is now easy to modify
  • Business logic can more easily be tested in isolation
  • Can be implemented incrementally

Disadvantages

  • It is more work at first though this is eventually offset by more maintainable code later on.
  • Not suitable for all applications. For small projects the additional infrastructure may not be worth the effort.

Other references

Entopic answered 29/10, 2010 at 15:46 Comment(3)
+1 for suggesting MVC/MVP for Delphi, although you have to be careful with these kinds of silver bullets... While it would solve many problems, an inexperienced developer could make things worse when using these kinds of techniques before they completely understand them.Shudder
No silver bullets here. My suggestion was based on the information provided and the op's history of related questions. I agree that care should be taken with any design choice but I don't think a complete understanding is necessary to benefit from a programming technique. I've seen others deride so called "cargo cult" programming, a poor analogy IMHO. I'd rather see a novice developer use a "best practice" they don't fully understand than ignore all good programming practices.Entopic
Ok thanks a lot for the detailed answer. I am anyway in total doubt that I will be able to apply this approach since I really have a lot of non trivial forms and datamodules (50+) plus many simple forms (100+). With heavy refactoring it would be possible to reduce this complexity, but refactor without tests is not an option. I am reading Micheal Feathers boos on legacy code, but wow, to apply any of the ideas I should work for months for every single idea considering the lines of code (300000+). Of course I could start from some core place, but still I have doubts it will make sense.Inimical
S
1

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...

Shudder answered 29/10, 2010 at 13:11 Comment(2)
i see. The fact is that being also a huge project I fear that the benefits of refactoring can pay in a lot of time, months, if not years. Having said that rewriting is very appealing, because now the developement speed is super low due to bad design.probably there is a lower limit of quality under which refactoring is so difficult theredore rewriting is easier. Maybe I could consider reusing code in the new application, encapsulating it in some useful Objects/Packages.Inimical
About 6 years ago, I had to refactor or rewrite a Scheduler application. After examining the existing code, I decided to do what I described above: first document what the application does, then rebuild the whole thing with new code only. The reason why I didn't re-use any old code was simply because the old code had plenty of links to other old code, which would result in me including half the old project to the new code! Thus also lots of bugs and bad features from the old code. Maybe you can re-use some very small portions but I would advise to just rewrite all of it!Shudder

© 2022 - 2024 — McMap. All rights reserved.