wrong class design need a refactoring - delphi

On a complex application I found a small mistake in respect to class design :
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type AboutMe= class
Name : String
end;
type AboutMe_more = class(AboutMe)
gender : String;
Birth : TDate;
end;
type Aboutme_complete = class (AboutMe_more)
adresss : String;
salery : Real;
name : String; /// name is already available a parent class
end;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var _Aboutme_complete : Aboutme_complete ;
begin
_Aboutme_complete := Aboutme_complete.Create;
_Aboutme_complete.name := 'a coding error -> need refactoring ';
end;
end.
In the class "AboutMe_more", by mistake the field name has been added.
I wonder what kind of error might happen if I remove that obsolete name field?
The trouble behind that question ( ~ 100 K Lines of code and no systematic test)

There are two separate variables named name in the type Aboutme_complete. There is one introduced in AboutMe and then a second introduced in Aboutme_complete which hides the first.
For methods of AboutMe, the member name refers to the first variable.
For methods of Aboutme_complete, the member name refers to the second variable.
Removing the variable name from Aboutme_complete will likely change behaviour. That's because there would now be just a single variable, and methods of all the classes would be referring to the same variable. So, as the code stands, methods in Aboutme_complete refer to Aboutme_complete.name, but if you made the change the code would then refer to AboutMe.name.
Whether this breaks your code depends on what the code is. Only you can know the answer to that because only you have that code.

Related

Inheritance in Delphi : Does each of Delphi Subclasses have its own unique copy of the Superclass Private Fields?

Suppose we have a Delphi SuperClass (TSuper) and we inherit a group of Subclasses from it ( TSub1 , TSub2 ).
This Superclass (TSuper) has only one Private Field ( FField ).
The Subclasses ( TSub1, TSub2 ) do not have their concrete real Fields at all as shown in the code below (this is for testing!), instead they inherit the Private field of their ancestor.
unit Unit1;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Type
TSuper = class
private
FField: integer;
public
constructor Create;
Property Int: integer read FField write FField;
end;
TSubOne = class(TSuper)
public
constructor Create;
end;
TSubTwo = class(TSuper)
public
constructor Create;
end;
var
Form1: TForm1;
vSuper: TSuper;
vOne: TSubOne;
vTwo: TSubTwo;
implementation
{$R *.dfm}
{ TSuper }
constructor TSuper.Create;
begin
self.FField := 0;
end;
{ TSub }
constructor TSubOne.Create;
begin
self.FField := 1;
end;
{ TSubTwo }
constructor TSubTwo.Create;
begin
self.FField := 2;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
vSuper := TSuper.Create;
vOne := TSubOne.Create;
vTwo := TSubTwo.Create;
ShowMessage('Field of Super Class = ' + inttostr(vSuper.Int));
ShowMessage('Field of Sub Class One = ' + inttostr(vOne.Int));
ShowMessage('Field of Sub Class Two = ' + inttostr(vTwo.Int));
end;
end.
When we run the code above, we notice that if we assign different values for each of the 3 classes Field (TSuper, TSub1, and TSub2 ), they will return the correct value for each.
From this, it seems that Delphi will create a hidden , unique ( different ) copies of the Superclass's private field within each of its Subclasses.
Is this correct? can we really depend on this feature? or it is only a temporary feature in an ideal situation that will work differently under any circumstances!
where can I found the explanation of this behavior in the Documentation?
Thank you.
Does each of Delphi Subclasses have its own unique copy of the Superclass Private Fields?
Yes, each object instance of a subclass has his own set of all fields defined in the entire class hierarchy.
If you need data that is common for all instance of a class, you need to define it as a class property using the keyword class.
For more details, have a look at Delphi documentation.

Spring4D field injection does not work in a TForm instance [duplicate]

This question already has an answer here:
How to initialize main application form in Spring4D GlobalContainer?
(1 answer)
Closed 2 years ago.
I would like to use the Inject attribute of Spring4D release 1.1 just like in the sample code below. It seems the Inject attribute has no effect because the fMyResource field value is NIL in the button click handler method. In my original code the type registration got place in the dpr file before Application.CreateForm(TForm1, Form1);. I just modify it to make the code more concise. What should I do to make the field injection work?
unit FieldInjectionTest;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Spring.Container.Common;
type
IMyResource = interface
['{6BD6421E-F57F-41BD-A6E4-347B2BE20A3C}']
procedure foo;
end;
TMyResource = class ( TInterfacedObject, IMyResource )
public
procedure foo; virtual;
end;
TForm1 = class ( TForm )
button1 : TButton;
procedure Button1Click( sender : TObject );
private
[Inject]
fMyResource : IMyResource;
end;
implementation
uses
Spring.Container;
procedure TMyResource.foo;
begin
//...
end;
procedure TForm1.Button1Click( sender : TObject );
begin
// fMyResource is NIL
fMyResource.foo;
end;
initialization
globalContainer.registerType<TMyResource>.implements<IMyResource>;
globalContainer.build;
end.
#whorsdaddy's comment help me to understand : The [Inject] attribute works just in container managed objects. It is not so surprising when I rethink it.

TForm management in Spring4D

I have the following code:
Project.dpr
program Project2;
uses
madExcept,
madLinkDisAsm,
madListHardware,
madListProcesses,
madListModules,
Spring.Container,
Vcl.Forms,
uRegistrations in '..\Memory leak II\uRegistrations.pas',
Unit3 in 'Unit3.pas' {MainForm},
Unit4 in 'Unit4.pas' {SecondaryForm},
Unit5 in 'Unit5.pas';
{$R *.res}
begin
RegisterTypes(GlobalContainer);
Application.Initialize;
Application.MainFormOnTaskbar := True;
// MainForm:=TMainForm.Create(nil);
Application.CreateForm(TMainForm, MainForm);
MainForm.SecondaryForm := Globalcontainer.Resolve<ISecondaryForm>;
Application.Run;
end.
uRegistrations.pas that registers the interface
unit uRegistrations;
interface
uses
Spring.Container;
procedure RegisterTypes(Container: TContainer);
implementation
uses
Unit5,
Unit4;
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
end);
Container.Build;
end;
end.
Unit3.pas holding the main form
unit Unit3;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Unit5,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs;
type
TMainForm = class(TForm)
private
{ Private declarations }
FSecondaryForm: ISecondaryForm;
public
{ Public declarations }
property SecondaryForm: ISecondaryForm read FSecondaryForm write FSecondaryForm;
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
end.
Unit4.pas with the secondary form
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
unit5,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
public
{ Public declarations }
end;
//var
// SecondaryForm: TSecondaryForm;
implementation
{$R *.dfm}
end.
and finally Unit5.pas with the Interface declaration
{$M+}
unit Unit5;
interface
type
ISecondaryForm=interface
['{62D63E9A-A3AD-435B-8938-9528E70D78B1}']
end;
implementation
end.
It compiles and runs regularly but when I close the application i have three memory leaks.
allocation number: 8482 program up time: 721 ms type: Brush Handle
handle: $461027f5 style: BS_SOLID color: $f0f0f0
allocation number: 8318 program up time: 697 ms type: TSecondaryForm
address: $d51ac64 size: 924 access rights: read/write
allocation number: 8267 program up time: 693 ms type: Font Handle
handle: $1d0a28f1 face: Tahoma height: -11
Why does this happens and how can I solve it ?
EDIT
After the answer, I implemented the following solutions (the comments highlight the errors I got:
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
result.Owner:=Application.MainForm;//cannot assign to a read-only property
result.Parent:=Application; //incompatible types
result.Parent:=application.MainForm;//memory leak
end);
Container.Build;
end;
I have also tried to amend the OnClose method of TSecondaryForm in the following way:
procedure TSecondaryForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree; //memory leak
end;
but I got a memory leak.
What am I doing wrong with all the technique above ?
In the end I just made the two methods _AddRef and _Release manage the reference counting as suggested in the comments and I have no more memory leaks.
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
protected
FRefCount: Integer;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
{ Public declarations }
end;
function TSecondaryForm._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TSecondaryForm._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result=0 then
self.Free;
end
If you want a form (or any class that inherits from TComponent) to be handled by interface reference counting then you need to implement it yourself (look at System.TInterfacedObject as an example of how to do it).
You basically need to reimplement IInterface to the class you want to enable reference counting on:
type
TInterfacedForm = class(TForm, IInterface)
// look at System.TInterfacedObject
end;
If you are doing so, keep in mind that it then should not be handled by the owner mechanism. If you register it to the container and use its default creation mechanism it will pass nil to the owner as of Spring4D 1.2 - see Spring.Container.Resolvers.TComponentOwnerResolver). In any version before you need to explicitly create it with nil inside of DelegateTo.
If you are dealing with any controls over interfaces that are put onto other controls (like frames) via their parent property keep also in mind that in such case another memory management mechanism comes into play which might destroy such a component if its parent is getting destroyed - if you are just dealing with interfaced forms that is not a problem but I thought I mention it here for completeness.
TComponent descendants (like TForm) disable interfaces reference counting, hence nobody is freeing the secondary form. The memory model is owner based, that is, when the parent object that owns an object is freed, it frees all it's children.
So, you could either pass an owner to the form on the factory function (maybe Application, or Application.MainForm) and adhere to TComponent's memory model or add a hook on the OnClose event of the form and set Action to caFree. The former will destroy the form when the application is closed, and the latter will destroy it as soon as the secondary form is closed (as soon as possible)

TObjectList E2003 Undeclared identifier TObjectList<>

I've been introduced to TObjectList, and I would like to make use of it, except that I can't seem to get even the sample code from Embarcadero's web site to work for me. Here is my code:
unit Test03Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
{ Declare a new object type. }
TNewObject = class
private
FName: String;
public
constructor Create(const AName: String);
destructor Destroy(); override;
end;
{ TNewObject }
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TNewObject.Create(const AName: String);
begin
FName := AName;
end;
destructor TNewObject.Destroy;
begin
{ Show a message whenever an object is destroyed. }
MessageDlg('Object "' + FName + '" was destroyed!', mtInformation, [mbOK], 0);
inherited;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
List: TObjectList<TNewObject>;
Obj: TNewObject;
begin
{ Create a new List. }
{ The OwnsObjects property is set by default to true -- the list will free the owned objects automatically. }
List := TObjectList<TNewObject>.Create();
{ Add some items to the List. }
List.Add(TNewObject.Create('One'));
List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
Obj := TNewObject.Create('Three');
List.Add(Obj);
{
Remove an instance of the TNewObject class. Destructor
is called for the owned objects, because you have set the OwnsObjects
to true.
}
List.Delete(0);
List.Extract(Obj);
{ Destroy the List completely--more message boxes will be shown. }
List.Free;
end;
end.
When trying to compile this, I get the error [DCC Error] Test03Unit1.pas(51): E2003 Undeclared identifier: 'TObjectList<>'. Line 51 is the line which says:
List: TObjectList<TNewObject>;
I have never seen < > used in the Pascal language before, so this is completely new territory for me. Doing a google search for "Delphi and < >" doesn't seem to tell me what I need to know about this. From other examples I can find on the internet it seems to be the correct way to use it.
Using Delphi XE2.
What am I doing wrong?
You must add System.Generics.Collections to your uses clause. That is the unit which declares TObjectList<T>.
I've added documentation links to the answer. When you cannot find a class, look for it in the documentation. That will tell you which unit you need to use.
As well as TObjectList<T> there is TList<T>. It makes sense to use TObjectList<T> when you want the list to own its members. Otherwise, there is no benefit in using TObjectList<T> and you may as well use TList<T>. As well as the built-in Delphi generic containers, there are many excellent third-party containers that are often superior. For instance, the Delphi Spring Framework has a fine collection of containers.

Weird class instance sizing using class members that are arrays of generic types

Below is a very simple bit of code that mimics a class structure in some code I have (the form just contains a single button attached to the click event). I am using Delphi XE and XE II and see nasty crashes when destroying objects in my production code that this class is based on. These crashes only occur if I allow the members of the array item in TMyClass to be initialised in the Clear() method.
Unfortunately the crash is not repeatable so far in this sample code, but it does mimic some very weird behaviour with instance sizing that I suspect might be the cause of the problem.
If I place a break point in the TMyClass.Clear function and look at the InstanceSize member of the class reports 1288. Which is odd, as the size of the array member alone is really 12kb. I can change the type being provided from TRecord2 to Integer and I get the same InstanceSize result. If I remove the array entirely from the class I get an Instance size of ~264 bytes, which seems over the top for a class that contains only a single 128 byte record instance. This indicates that the compiler has allocated 1024 bytes to the array storage for the V (of type TT) member in TMyClass.
I am suspicious that the weird instance size is causing bad things to happen when I write 12kb of data into an object that the compiler thinks is 1kb in size.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TRecord = record A : Array[1..128] of byte; end;
TRecord2 = packed record S : Single; T : TDateTime; end;
TBase = class(TObject)
public
R : TRecord;
end;
TBase2<T> = class(TBase)
public
type
TT = packed array [0..31, 0..31] of T;
var
V : TT;
R2 : TRecord;
end;
TMyClass = class(TBase2<TRecord2>)
public
procedure Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
O : TMyClass;
begin
O := TMyClass.Create;
O.Clear;
O.Free;
end;
{ TMyClass }
procedure TMyClass.Clear;
var
i, j : integer;
begin
for i := 0 to 31 do
for j := 0 to 31 do
begin
V[I, J].S := 0;
V[I, J].T := 0;
end;
end;
end.
It's a bug, as is shown by this minimal reproduction:
program InstanceSizeBug;
{$APPTYPE CONSOLE}
type
TMyClass<T> = class
V: array [0..255] of T;
end;
TMyClassSingle = class(TMyClass<Single>);
begin
Writeln(TMyClass<Single>.InstanceSize);
Writeln(TMyClassSingle.InstanceSize);
Readln;
end.
Output:
1032
264
Note that the same behaviour can be observed in Delphi 2010, the only other Delphi version i have to hand.
Trace through under the debugger and you find yourself in _GetMem with the Size parameter equal to 264 so clearly not enough memory is being allocated.
And just in case there is any doubt, this version fails with an AV.
program InstanceSizeBug;
{$APPTYPE CONSOLE}
type
TMyClass<T> = class
V: array [1..256*256] of T;
end;
TMyClassSingle = class(TMyClass<Single>);
begin
TMyClassSingle.Create.V[256*256] := 0;
end.
I have submitted this to Quality Central, issue #100561.
As a workaround I suggest you use a dynamic array which you allocate using SetLength in the constructor. I rather imagine that it is the presence of a fixed sized generic array that is making the compiler misbehave.

Resources