Constructor Dependency Injection alternatives










2















I am working solo on an employee timekeeping project which uses a database to store time sheet entries. I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes. For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;



Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor. I use a domain object to create the required interfaces and inject them into the class being created.
Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object.



The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships. These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.



Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes. Here is a sample of code from one of my modules for time sheet entries.



unit LevelPay.DbModel.TimesheetEntry;

interface

uses
Data.Db
, FireDAC.Comp.DataSet
, MyLib.Model.Interfaces
, LevelPay.Model.Types
, LevelPay.Model.Constants
, LevelPay.Model.Interfaces
, LevelPay.DbModel.AppModel
;

type
TDbCustomTimesheetEntry = class(
TDbAppModel<ITimesheetEntry>,
ITimesheetEntry
)
strict private
FCopyFunc: TCopyFunc<ITimesheetEntry>;
procedure ClearFilter;
procedure FilterEntries(const ADate: TDate);
strict protected
FTimesheet: ITimesheet;
FID: TField;
FEmployeeID: TField;
FPayPeriodEndDate: TField;
FFiscalYearEndDate: TField;
FFiscalYearStartDate: TField;
FRowNbr: TField;
FEntryTypeID: TField;
FDateIn: TField;
FTimein: TField;
FDateOut: TField;
FTimeOut: TField;
FCreatedBy: TField;
FCreatedTimestamp: TField;
FLastModifiedBy: TField;
FLastModifiedTimestamp: TField;
FNote: TField;
FClockable: TField;
FClockableHours: TField;
FDayOfWeek: TField;
FDifference: TField;
FEmployeeName: TField;
FEntryTypeCaption: TField;
FTimeElapsed: TField;
FDateIndex: TFDIndex;
FTimeScheduled: TField;
FScheduledTimeIn: TField;
FScheduledTimeOut: TField;
FWeekOf: TField;
function GetID: TIdentifier;
function GetModel: ITimesheetEntry; override;
function GetClockable: Boolean;
function GetClockableHours: THours;
function GetDateIn: TDate;
function GetDateOut: TDate;
function GetDifference: THours;
function GetEmployeeID: TIdentifier;
function GetEmployeeName: string;
function GetPayPeriodEndDate: TDate;
function GetFiscalYearStartDate: TDate;
function GetFiscalYearEndDate: TDate;
function GetEntryTypeID: TIdentifier;
function GetEntryTypeCaption: TCaption;
function GetPlaceholder: Boolean;
function GetRowNbr: TRowNbr;
function GetScheduledTimeIn: TTime;
function GetScheduledTimeOut: TTime;
function GetTimeElapsed: THours;
function GetTimein: TTime;
function GetTimeOut: TTime;
function GetTimeScheduled: THours;
function GetWeekOf: TDate;
function GetWeekDay: string;
function GetCreatedBy: TUserName;
function GetCreatedTimestamp: TDateTime;
function GetLastModifiedBy: TUserName;
function GetLastModifiedTimestamp: TDateTime;
function GetNote: AnsiString;
function GetEntry: ITimesheetEntry;
function GetTimesheet: ITimesheet;
function GetHasEntries: Boolean;
function Find(AModel: ITimesheetEntry): Boolean; override;
procedure DoUpdate(AModel: ITimesheetEntry); override;
procedure Load; virtual;
procedure CreateFields; override;
procedure CreateCalcFields; override;
procedure CreateIndexes; override;
procedure FormatFields; override;
procedure OnCalcFields(Dataset: TDataset); override;
procedure OnNewRecord(Dataset: TDataset); override;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ACreateFields: Boolean
); reintroduce;
property ID: TIdentifier read GetID;
property EmployeeID: TIdentifier read GetEmployeeID;
property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
property EntryTypeID: TIdentifier read GetEntryTypeID;
property EntryTypeCaption: TCaption read GetEntryTypeCaption;
property RowNbr: TRowNbr read GetRowNbr;
property Clockable: Boolean read GetClockable;
property ClockableHours: THours read GetClockableHours;
property DateIn: TDate read GetDateIn;
property EmployeeName: string read GetEmployeeName;
property ScheduledTimeIn: TTime read GetScheduledTimeIn;
property ScheduledTimeOut: TTime read GetScheduledTimeOut;
property TimeIn: TTime read GetTimein;
property DateOut: TDate read GetDateOut;
property TimeOut: TTime read GetTimeOut;
property TimeElapsed: THours read GetTimeElapsed;
property Placeholder: Boolean read GetPlaceholder;
property TimeScheduled: THours read GetTimeScheduled;
property Difference: THours read GetDifference;
property WeekDay: string read GetWeekDay;
property WeekOf: TDate read GetWeekOf;
property CreatedBy: TUserName read GetCreatedBy;
property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
property LastModifiedBy: TUserName read GetLastModifiedBy;
property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
property Note: AnsiString read GetNote;
property Timesheet: ITimesheet read GetTimesheet;
end;

TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
strict private
FLoadTimesheetEntries: TLoadTimesheetProc;
FElectionList: ILevelPayElectionList;
FPositionList: IHourlyPositionList;
strict protected
procedure BeforePost(Dataset: TDataset); override;
procedure Load; override;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
AProc: TLoadTimesheetProc;
AElectionList: ILevelPayElectionList;
APositionList: IHourlyPositionList;
ACreateFields: Boolean = True
); reintroduce;
end;

TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
strict private
FPlaceholderID: TIdentifier;
FClosureList: ISchoolClosureList;
procedure EntryTypeIDOnChange(Sender: TField);
strict protected
procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
procedure CreateFields; override;
procedure DoAdd(AModel: ITimesheetEntry); override;
property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
AClosureList: ISchoolClosureList;
ACreateFields: Boolean
); reintroduce;
end;

TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
strict private
FClone: TFDDataset;
FSource: ISourceEntryList;
function GetNextRowNbr: TRowNbr;
strict protected
function WorkweekList: IWorkweekList;
procedure Clear;
procedure Load; override;
public
procedure Add(AModel: ITimesheetEntry); //replaces inherited add
procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
procedure Update(OldModel, NewModel: ITimesheetEntry);
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ASourceFunc: TSourceListFunc;
AClosureList: ISchoolClosureList;
ACreateFields: Boolean
); reintroduce;
end;

implementation

uses
System.SysUtils
, System.Classes
, System.Variants
, System.DateUtils
, FireDAC.Comp.Client
, DateTimeHelper
, LevelPay.Model.Helpers
;

TCustomShift

procedure TDbCustomTimesheetEntry.ClearFilter;
begin
CancelRange;
end;

constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet; ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACreateFields);
FCopyFunc := ACopyFunc;
FTimesheet := ATimesheet;
end;

procedure TDbCustomTimesheetEntry.CreateCalcFields;
begin
inherited;
FClockable := CreateCalcBooleanField(k_Clockable);
FClockableHours := CreateCalcFloatField(k_ClockableHours);
FDayOfWeek := CreateCalcStringField(k_WeekDay, 13);
FDifference := CreateCalcFloatField(k_Difference);
FEmployeeName := CreateCalcStringField(k_EmployeeName, 40);
FEntryTypeCaption := CreateCalcStringField(k_EntryTypeCaption, 20);
FFiscalYearEndDate := CreateCalcDateTimeField(k_FiscalYearEndDate);
FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
FScheduledTimeIn := CreateCalcDateTimeField(k_ScheduledTimeIn);
FScheduledTimeOut := CreateCalcDateTimeField(k_ScheduledTimeOut);
FTimeElapsed := CreateCalcFloatField(k_TimeElapsed);
FTimeScheduled := CreateCalcFloatField(k_TimeScheduled);
FWeekOf := CreateCalcDateTimeField(k_WeekOf);
end;

procedure TDbCustomTimesheetEntry.CreateFields;
begin
FID := CreateField(k_Id);
FEmployeeID := CreateField(k_EmployeeID);
FEntryTypeID := CreateField(k_EntryTypeID);
FRowNbr := CreateField(k_RowNbr);
FTimeIn := CreateField(k_TimeIn);
FTimeOut := CreateField(k_TimeOut);
FID := CreateField(k_ID);
FDateIn := CreateField(k_DateIn);
FDateOut := CreateField(k_DateOut);
FCreatedBy := CreateField(k_CreatedBy);
FCreatedTimestamp := CreateField(k_CreatedTimeStamp);
FLastModifiedBy := CreateField(k_LastModifiedBy);
FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
FNote := CreateField(k_Note);
FPayPeriodEndDate := CreateField(k_PayPeriodEndDate);

end;

procedure TDbCustomTimesheetEntry.CreateIndexes;
const
FIELD_LIST = k_DateIn + ';' + k_RowNbr;
begin
inherited;
FDateIndex := CreateIndex('ByDate', FIELD_LIST);
FDateIndex.Selected := True;
Dataset.IndexesActive := True;
end;

function TDbCustomTimesheetEntry.GetClockable: Boolean;
begin
Result := Rules.Clockable;
end;

function TDbCustomTimesheetEntry.GetClockableHours: THours;
begin
Result := Rules.ClockableHours;
end;

function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
begin
Result := FCreatedBy.AsUserName;
end;

function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
begin
Result := FCreatedTimestamp.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetDateIn: TDate;
begin
Result := FDateIn.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetDateOut: TDate;
begin
Result := FDateOut.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetDifference: THours;
begin
Result := Rules.Difference;
end;

function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
begin
Result := FEmployeeID.AsIdentifier;
end;

function TDbCustomTimesheetEntry.GetEmployeeName: string;
begin
Result := Rules.EmployeeName;
end;

function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
begin
Result := FEntryTypeID.AsIdentifier;
end;

function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
begin
Result := FFiscalYearEndDate.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
begin
Result := FFiscalYearStartDate.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
begin
Result := RecordCount > 0;
end;

function TDbCustomTimesheetEntry.GetID: TIdentifier;
begin
Result := FID.AsInteger;
end;

function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
begin
Result := FLastModifiedBy.AsUserName;
end;

function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
begin
Result := FLastModifiedTimestamp.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
var
LResult: ITimesheetEntry;
begin
LResult := FCopyFunc(Self);
Result := LResult;
end;

function TDbCustomTimesheetEntry.GetNote: AnsiString;
begin
Result := FNote.AsAnsiString;
end;

function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
begin
Result := FPayPeriodEndDate.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
begin
Result := Rules.Placeholder;
end;

function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
begin
Result := Model;
end;

function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
begin
Result := Rules.EntryTypeCaption
end;

function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
begin
Result := FRowNbr.AsRowNbr;
end;

function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
begin
Result := Rules.ScheduledTimeIn;
end;

function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
begin
Result := Rules.ScheduledTimeOut;
end;

function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
begin
Result := Rules.TimeElapsed;
end;

function TDbCustomTimesheetEntry.GetTimein: TTime;
begin
Result := FTimeIn.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetTimeOut: TTime;
begin
Result := FTimeOut.AsDateTime;
end;

function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
begin
Result := Rules.TimeScheduled;
end;

function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
begin
Result := FTimesheet;
end;

function TDbCustomTimesheetEntry.GetWeekDay: string;
begin
Result := Rules.WeekDay;
end;

function TDbCustomTimesheetEntry.GetWeekOf: TDate;
begin
Result := Rules.WeekOf;
end;

procedure TDbCustomTimesheetEntry.Load;
begin
//Stub procedure
end;

procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
begin
inherited;
if not Assigned(Rules) then Exit;

FClockable.AsBoolean := GetClockable;
FClockableHours.AsHours := GetClockableHours;
FDayOfWeek.AsString := GetWeekDay;
FDifference.AsHours := GetDifference;
FEmployeeName.AsString := GetEmployeeName;
FEntryTypeCaption.AsCaption := GetEntryTypeCaption;
FTimeElapsed.AsHours := GetTimeElapsed;
FTimeScheduled.AsHours := GetTimeScheduled;
FScheduledTimeIn.AsDateTime := GetScheduledTimeIn;
FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
FWeekOf.AsDateTime := GetWeekOf;
end;

procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
begin
inherited;

FEmployeeID.AsIdentifier := FTimesheet.EmployeeID;
FFiscalYearEndDate.AsDateTime := FTimesheet.FiscalYearEndDate;
FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
FPayPeriodEndDate.AsDateTime := FTimesheet.PayPeriodEndDate;
end;

procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
begin
inherited;

FEmployeeID.AsIdentifier := AModel.EmployeeID;
FRowNbr.AsRowNbr := AModel.RowNbr;
FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
FDateIn.AsDateTime := AModel.DateIn;
FTimeIn.AsDateTime := AModel.TimeIn;
FDateOut.AsDateTime := AModel.DateOut;
FTimeOut.AsDateTime := AModel.TimeOut;
FNote.AsAnsiString := AModel.Note;
end;

procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
begin
FDateIndex.Selected := True;
SetRange([ADate], [ADate]);
end;

function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
begin
Result := Locate(k_ID, AModel.ID);
end;

procedure TDbCustomTimesheetEntry.FormatFields;
begin
inherited;
FTimeElapsed.OnGetText := HoursFieldGetText;
FClockableHours.OnGetText := HoursFieldGetText;
FDifference.OnGetText := HoursFieldGetText;
FTimeScheduled.OnGetText := HoursFieldGetText;

SetTimeFieldDisplayFormat(FTimeIn);
SetTimeFieldDisplayFormat(FTimeOut);
SetDateFieldDisplayFormat(FDateIn);
SetDateFieldDisplayFormat(FDateOut);
SetDateFieldDisplayFormat(FPayPeriodEndDate);
SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
end;

TDbDummyEntry

procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
var
LTimestamp: TDateTime;
begin
inherited;
LTimestamp := Now;
FLastModifiedBy.AsUserName := FTimesheet.User.UserName;
FLastModifiedTimestamp.AsDateTime := LTimestamp;
if State in [dsInsert] then
begin
FCreatedBy.AsUserName := FTimesheet.User.UserName;
FCreatedTimestamp.AsDateTime := LTimestamp;
end;
end;

constructor TDbSourceEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
ACopyFunc: TCopyFunc<ITimesheetEntry>; AProc: TLoadTimesheetProc;
AElectionList: ILevelPayElectionList; APositionList: IHourlyPositionList;
ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);

FLoadTimesheetEntries := AProc;
FElectionList := AElectionList;
FPositionList := APositionList;
end;

procedure TDbSourceEntry.Load;
var
LEmployeeID: TIdentifier;
LFirstEntryDate: TDate;
LLastEntryDate: TDate;
begin
LEmployeeID := FTimesheet.EmployeeID;
LFirstEntryDate := FTimesheet.FirstEntryDate;
LLastEntryDate := FTimesheet.LastEntryDate;
FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
end;


TDbDummyEntry

procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
var
LEntryTypeID: TIdentifier;
LClosure: ISchoolClosure;
LNote: AnsiString;
LRowNbr: TRowNbr;
begin
Dec(FPlaceholderID);
LNote := '';
LEntryTypeID := 0;
LRowNbr := ARowNbr;

if LRowNbr < 2 then //This is a first entry for the date
begin
if FClosureList.Find(ADate) then
begin
LClosure := FClosureList.Closure;
LEntryTypeID := LClosure.EntryTypeID;
LNote := AnsiString(LClosure.Caption);
end
else
begin
if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
LEntryTypeID := k_Regular
else
LEntryTypeID := 0;
end;
end;

Append;
FId.AsIdentifier := FPlaceholderID;
FRowNbr.AsRowNbr := LRowNbr;
FDateIn.AsDateTime := ADate;
FDateOut.AsDateTime := ADate;
FEntryTypeID.AsIdentifier := LEntryTypeID;
FNote.AsAnsiString := LNote;
Post;
end;

constructor TDbDummyEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
FClosureList := AClosureList;
end;

procedure TDbDummyEntry.CreateFields;
begin
inherited;
FId.ReadOnly := False;
FId.AutoGenerateValue := arNone;
FEntryTypeID.OnChange := EntryTypeIDOnChange;
end;

procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
begin
inherited;
FId.AsIdentifier := AModel.ID;
end;

procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
var
LDateIn: TDate;
LDateOut: TDate;
begin
if Sender.AsIdentifier = k_LandSchool then
begin
LDateIn := FDateIn.AsDateTime;
LDateOut := LDateIn + 1;
FDateOut.AsDateTime := LDateOut;
FTimeIn.AsDateTime := GetScheduledTimeOut;
FTimeOut.AsDateTime := GetScheduledTimeIn;
end;
end;

TDbTimesheetEntry

procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
var
LEntry: ITimesheetEntry;
begin
FSource.Add(AModel);
LEntry := FSource.Entry; //Get model with updated ID
FTimesheet.WorkweekList.AddEntry(LEntry);
inherited Add(LEntry);
end;

procedure TDbTimesheetEntry.Clear;
begin
PlaceholderID := 0;
Dataset.CancelUpdates;
DisableControls;
try
First;
while RecordCount > 0 do
Dataset.Delete;
finally
EnableControls;
end;
end;

constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
FSource := ASourceFunc(FTimesheet);

FClone := TFDMemTable.Create(ADataset);
FClone.CloneCursor(ADataset);
end;

procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
var
LDate: TDate;
LRowNbr: SmallInt;
LEntry: ITimesheetEntry;
begin
FSource.Delete(AModel);
WorkweekList.DeleteEntry(AModel);
DisableControls;
try
LDate := AModel.DateIn;
LRowNbr := AModel.RowNbr;
inherited Delete(AModel);
if (LRowNbr = 1) then
begin
AddPlacedholder(LDate, 0);
LEntry := Model; //Get placeholder
WorkweekList.AddEntry(LEntry);
end;
finally
EnableControls;
end;
end;

function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
begin
Result := 0;
FClone.SetRange([GetDateIn], [GetDateIn]);
while not FClone.Eof do
begin
Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
FClone.Next;
end;
FClone.CancelRange;
end;

procedure TDbTimesheetEntry.Load;
var
LDate: TDate;
LFirstEntryDate: TDate;
LCutoffDate: TDate;
LEntry: ITimesheetEntry;
LWeekOf: TDate;
LWorkweekList: IWorkweekList;
begin
LWorkweekList := FTimesheet.WorkweekList;
LFirstEntryDate := FTimesheet.FirstEntryDate;
LCutOffDate := FTimesheet.LastEntryDate;
LWeekOf := TDateTime(LFirstEntryDate).StartOfWeek;

LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
FSource.Load;
DisableControls;
try
Clear;

LDate := LWeekOf;
repeat
FSource.FilterEntries(LDate);
if FSource.HasEntries then
begin
for LEntry in FSource do
begin
if LDate >= LFirstEntryDate then
begin
inherited Add(LEntry); //this must call inherited add to adding to FSource
end;
LWorkweekList.AddEntry(LEntry);
end;
end
else
begin
if LDate >= LFirstEntryDate then
begin
AddPlacedholder(LDate, 0);
LEntry := GetModel; //This retrieves the placeholder
LWorkweekList.AddEntry(LEntry);
end;
end;
LDate := LDate + 1;
until LDate > LCutOffDate;
finally
FSource.ClearFilter;
EnableControls;
First;
end;
end;

procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
var
LEntry: ITimesheetEntry;
begin
DisableControls;
try
if Assigned(OldModel) then
begin
if OldModel.Placeholder then
begin
FSource.Add(NewModel);
end
else
begin
FSource.Update(NewModel);
end;
inherited Delete(OldModel);
WorkweekList.DeleteEntry(OldModel);
end
else
begin
FSource.Add(NewModel);
end;

LEntry := FSource.Entry; //Get entry with new ID
inherited Add(LEntry);
WorkweekList.AddEntry(LEntry);
finally
EnableControls;
end;
end;

function TDbTimesheetEntry.WorkweekList: IWorkweekList;
begin
Result := FTimesheet.WorkweekList;
end;

end.









share|improve this question


























    2















    I am working solo on an employee timekeeping project which uses a database to store time sheet entries. I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes. For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;



    Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor. I use a domain object to create the required interfaces and inject them into the class being created.
    Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object.



    The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships. These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.



    Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes. Here is a sample of code from one of my modules for time sheet entries.



    unit LevelPay.DbModel.TimesheetEntry;

    interface

    uses
    Data.Db
    , FireDAC.Comp.DataSet
    , MyLib.Model.Interfaces
    , LevelPay.Model.Types
    , LevelPay.Model.Constants
    , LevelPay.Model.Interfaces
    , LevelPay.DbModel.AppModel
    ;

    type
    TDbCustomTimesheetEntry = class(
    TDbAppModel<ITimesheetEntry>,
    ITimesheetEntry
    )
    strict private
    FCopyFunc: TCopyFunc<ITimesheetEntry>;
    procedure ClearFilter;
    procedure FilterEntries(const ADate: TDate);
    strict protected
    FTimesheet: ITimesheet;
    FID: TField;
    FEmployeeID: TField;
    FPayPeriodEndDate: TField;
    FFiscalYearEndDate: TField;
    FFiscalYearStartDate: TField;
    FRowNbr: TField;
    FEntryTypeID: TField;
    FDateIn: TField;
    FTimein: TField;
    FDateOut: TField;
    FTimeOut: TField;
    FCreatedBy: TField;
    FCreatedTimestamp: TField;
    FLastModifiedBy: TField;
    FLastModifiedTimestamp: TField;
    FNote: TField;
    FClockable: TField;
    FClockableHours: TField;
    FDayOfWeek: TField;
    FDifference: TField;
    FEmployeeName: TField;
    FEntryTypeCaption: TField;
    FTimeElapsed: TField;
    FDateIndex: TFDIndex;
    FTimeScheduled: TField;
    FScheduledTimeIn: TField;
    FScheduledTimeOut: TField;
    FWeekOf: TField;
    function GetID: TIdentifier;
    function GetModel: ITimesheetEntry; override;
    function GetClockable: Boolean;
    function GetClockableHours: THours;
    function GetDateIn: TDate;
    function GetDateOut: TDate;
    function GetDifference: THours;
    function GetEmployeeID: TIdentifier;
    function GetEmployeeName: string;
    function GetPayPeriodEndDate: TDate;
    function GetFiscalYearStartDate: TDate;
    function GetFiscalYearEndDate: TDate;
    function GetEntryTypeID: TIdentifier;
    function GetEntryTypeCaption: TCaption;
    function GetPlaceholder: Boolean;
    function GetRowNbr: TRowNbr;
    function GetScheduledTimeIn: TTime;
    function GetScheduledTimeOut: TTime;
    function GetTimeElapsed: THours;
    function GetTimein: TTime;
    function GetTimeOut: TTime;
    function GetTimeScheduled: THours;
    function GetWeekOf: TDate;
    function GetWeekDay: string;
    function GetCreatedBy: TUserName;
    function GetCreatedTimestamp: TDateTime;
    function GetLastModifiedBy: TUserName;
    function GetLastModifiedTimestamp: TDateTime;
    function GetNote: AnsiString;
    function GetEntry: ITimesheetEntry;
    function GetTimesheet: ITimesheet;
    function GetHasEntries: Boolean;
    function Find(AModel: ITimesheetEntry): Boolean; override;
    procedure DoUpdate(AModel: ITimesheetEntry); override;
    procedure Load; virtual;
    procedure CreateFields; override;
    procedure CreateCalcFields; override;
    procedure CreateIndexes; override;
    procedure FormatFields; override;
    procedure OnCalcFields(Dataset: TDataset); override;
    procedure OnNewRecord(Dataset: TDataset); override;
    public
    constructor Create(
    ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>;
    ACopyFunc: TCopyFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet;
    ACreateFields: Boolean
    ); reintroduce;
    property ID: TIdentifier read GetID;
    property EmployeeID: TIdentifier read GetEmployeeID;
    property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
    property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
    property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
    property EntryTypeID: TIdentifier read GetEntryTypeID;
    property EntryTypeCaption: TCaption read GetEntryTypeCaption;
    property RowNbr: TRowNbr read GetRowNbr;
    property Clockable: Boolean read GetClockable;
    property ClockableHours: THours read GetClockableHours;
    property DateIn: TDate read GetDateIn;
    property EmployeeName: string read GetEmployeeName;
    property ScheduledTimeIn: TTime read GetScheduledTimeIn;
    property ScheduledTimeOut: TTime read GetScheduledTimeOut;
    property TimeIn: TTime read GetTimein;
    property DateOut: TDate read GetDateOut;
    property TimeOut: TTime read GetTimeOut;
    property TimeElapsed: THours read GetTimeElapsed;
    property Placeholder: Boolean read GetPlaceholder;
    property TimeScheduled: THours read GetTimeScheduled;
    property Difference: THours read GetDifference;
    property WeekDay: string read GetWeekDay;
    property WeekOf: TDate read GetWeekOf;
    property CreatedBy: TUserName read GetCreatedBy;
    property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
    property LastModifiedBy: TUserName read GetLastModifiedBy;
    property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
    property Note: AnsiString read GetNote;
    property Timesheet: ITimesheet read GetTimesheet;
    end;

    TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
    strict private
    FLoadTimesheetEntries: TLoadTimesheetProc;
    FElectionList: ILevelPayElectionList;
    FPositionList: IHourlyPositionList;
    strict protected
    procedure BeforePost(Dataset: TDataset); override;
    procedure Load; override;
    public
    constructor Create(
    ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet;
    ACopyFunc: TCopyFunc<ITimesheetEntry>;
    AProc: TLoadTimesheetProc;
    AElectionList: ILevelPayElectionList;
    APositionList: IHourlyPositionList;
    ACreateFields: Boolean = True
    ); reintroduce;
    end;

    TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
    strict private
    FPlaceholderID: TIdentifier;
    FClosureList: ISchoolClosureList;
    procedure EntryTypeIDOnChange(Sender: TField);
    strict protected
    procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
    procedure CreateFields; override;
    procedure DoAdd(AModel: ITimesheetEntry); override;
    property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
    public
    constructor Create(
    ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>;
    ACopyFunc: TCopyFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet;
    AClosureList: ISchoolClosureList;
    ACreateFields: Boolean
    ); reintroduce;
    end;

    TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
    strict private
    FClone: TFDDataset;
    FSource: ISourceEntryList;
    function GetNextRowNbr: TRowNbr;
    strict protected
    function WorkweekList: IWorkweekList;
    procedure Clear;
    procedure Load; override;
    public
    procedure Add(AModel: ITimesheetEntry); //replaces inherited add
    procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
    procedure Update(OldModel, NewModel: ITimesheetEntry);
    constructor Create(
    ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>;
    ACopyFunc: TCopyFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet;
    ASourceFunc: TSourceListFunc;
    AClosureList: ISchoolClosureList;
    ACreateFields: Boolean
    ); reintroduce;
    end;

    implementation

    uses
    System.SysUtils
    , System.Classes
    , System.Variants
    , System.DateUtils
    , FireDAC.Comp.Client
    , DateTimeHelper
    , LevelPay.Model.Helpers
    ;

    TCustomShift

    procedure TDbCustomTimesheetEntry.ClearFilter;
    begin
    CancelRange;
    end;

    constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet; ACreateFields: Boolean);
    begin
    inherited Create(ADataset, AModelFunc, ACreateFields);
    FCopyFunc := ACopyFunc;
    FTimesheet := ATimesheet;
    end;

    procedure TDbCustomTimesheetEntry.CreateCalcFields;
    begin
    inherited;
    FClockable := CreateCalcBooleanField(k_Clockable);
    FClockableHours := CreateCalcFloatField(k_ClockableHours);
    FDayOfWeek := CreateCalcStringField(k_WeekDay, 13);
    FDifference := CreateCalcFloatField(k_Difference);
    FEmployeeName := CreateCalcStringField(k_EmployeeName, 40);
    FEntryTypeCaption := CreateCalcStringField(k_EntryTypeCaption, 20);
    FFiscalYearEndDate := CreateCalcDateTimeField(k_FiscalYearEndDate);
    FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
    FScheduledTimeIn := CreateCalcDateTimeField(k_ScheduledTimeIn);
    FScheduledTimeOut := CreateCalcDateTimeField(k_ScheduledTimeOut);
    FTimeElapsed := CreateCalcFloatField(k_TimeElapsed);
    FTimeScheduled := CreateCalcFloatField(k_TimeScheduled);
    FWeekOf := CreateCalcDateTimeField(k_WeekOf);
    end;

    procedure TDbCustomTimesheetEntry.CreateFields;
    begin
    FID := CreateField(k_Id);
    FEmployeeID := CreateField(k_EmployeeID);
    FEntryTypeID := CreateField(k_EntryTypeID);
    FRowNbr := CreateField(k_RowNbr);
    FTimeIn := CreateField(k_TimeIn);
    FTimeOut := CreateField(k_TimeOut);
    FID := CreateField(k_ID);
    FDateIn := CreateField(k_DateIn);
    FDateOut := CreateField(k_DateOut);
    FCreatedBy := CreateField(k_CreatedBy);
    FCreatedTimestamp := CreateField(k_CreatedTimeStamp);
    FLastModifiedBy := CreateField(k_LastModifiedBy);
    FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
    FNote := CreateField(k_Note);
    FPayPeriodEndDate := CreateField(k_PayPeriodEndDate);

    end;

    procedure TDbCustomTimesheetEntry.CreateIndexes;
    const
    FIELD_LIST = k_DateIn + ';' + k_RowNbr;
    begin
    inherited;
    FDateIndex := CreateIndex('ByDate', FIELD_LIST);
    FDateIndex.Selected := True;
    Dataset.IndexesActive := True;
    end;

    function TDbCustomTimesheetEntry.GetClockable: Boolean;
    begin
    Result := Rules.Clockable;
    end;

    function TDbCustomTimesheetEntry.GetClockableHours: THours;
    begin
    Result := Rules.ClockableHours;
    end;

    function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
    begin
    Result := FCreatedBy.AsUserName;
    end;

    function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
    begin
    Result := FCreatedTimestamp.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetDateIn: TDate;
    begin
    Result := FDateIn.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetDateOut: TDate;
    begin
    Result := FDateOut.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetDifference: THours;
    begin
    Result := Rules.Difference;
    end;

    function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
    begin
    Result := FEmployeeID.AsIdentifier;
    end;

    function TDbCustomTimesheetEntry.GetEmployeeName: string;
    begin
    Result := Rules.EmployeeName;
    end;

    function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
    begin
    Result := FEntryTypeID.AsIdentifier;
    end;

    function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
    begin
    Result := FFiscalYearEndDate.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
    begin
    Result := FFiscalYearStartDate.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
    begin
    Result := RecordCount > 0;
    end;

    function TDbCustomTimesheetEntry.GetID: TIdentifier;
    begin
    Result := FID.AsInteger;
    end;

    function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
    begin
    Result := FLastModifiedBy.AsUserName;
    end;

    function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
    begin
    Result := FLastModifiedTimestamp.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
    var
    LResult: ITimesheetEntry;
    begin
    LResult := FCopyFunc(Self);
    Result := LResult;
    end;

    function TDbCustomTimesheetEntry.GetNote: AnsiString;
    begin
    Result := FNote.AsAnsiString;
    end;

    function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
    begin
    Result := FPayPeriodEndDate.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
    begin
    Result := Rules.Placeholder;
    end;

    function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
    begin
    Result := Model;
    end;

    function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
    begin
    Result := Rules.EntryTypeCaption
    end;

    function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
    begin
    Result := FRowNbr.AsRowNbr;
    end;

    function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
    begin
    Result := Rules.ScheduledTimeIn;
    end;

    function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
    begin
    Result := Rules.ScheduledTimeOut;
    end;

    function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
    begin
    Result := Rules.TimeElapsed;
    end;

    function TDbCustomTimesheetEntry.GetTimein: TTime;
    begin
    Result := FTimeIn.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetTimeOut: TTime;
    begin
    Result := FTimeOut.AsDateTime;
    end;

    function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
    begin
    Result := Rules.TimeScheduled;
    end;

    function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
    begin
    Result := FTimesheet;
    end;

    function TDbCustomTimesheetEntry.GetWeekDay: string;
    begin
    Result := Rules.WeekDay;
    end;

    function TDbCustomTimesheetEntry.GetWeekOf: TDate;
    begin
    Result := Rules.WeekOf;
    end;

    procedure TDbCustomTimesheetEntry.Load;
    begin
    //Stub procedure
    end;

    procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
    begin
    inherited;
    if not Assigned(Rules) then Exit;

    FClockable.AsBoolean := GetClockable;
    FClockableHours.AsHours := GetClockableHours;
    FDayOfWeek.AsString := GetWeekDay;
    FDifference.AsHours := GetDifference;
    FEmployeeName.AsString := GetEmployeeName;
    FEntryTypeCaption.AsCaption := GetEntryTypeCaption;
    FTimeElapsed.AsHours := GetTimeElapsed;
    FTimeScheduled.AsHours := GetTimeScheduled;
    FScheduledTimeIn.AsDateTime := GetScheduledTimeIn;
    FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
    FWeekOf.AsDateTime := GetWeekOf;
    end;

    procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
    begin
    inherited;

    FEmployeeID.AsIdentifier := FTimesheet.EmployeeID;
    FFiscalYearEndDate.AsDateTime := FTimesheet.FiscalYearEndDate;
    FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
    FPayPeriodEndDate.AsDateTime := FTimesheet.PayPeriodEndDate;
    end;

    procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
    begin
    inherited;

    FEmployeeID.AsIdentifier := AModel.EmployeeID;
    FRowNbr.AsRowNbr := AModel.RowNbr;
    FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
    FDateIn.AsDateTime := AModel.DateIn;
    FTimeIn.AsDateTime := AModel.TimeIn;
    FDateOut.AsDateTime := AModel.DateOut;
    FTimeOut.AsDateTime := AModel.TimeOut;
    FNote.AsAnsiString := AModel.Note;
    end;

    procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
    begin
    FDateIndex.Selected := True;
    SetRange([ADate], [ADate]);
    end;

    function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
    begin
    Result := Locate(k_ID, AModel.ID);
    end;

    procedure TDbCustomTimesheetEntry.FormatFields;
    begin
    inherited;
    FTimeElapsed.OnGetText := HoursFieldGetText;
    FClockableHours.OnGetText := HoursFieldGetText;
    FDifference.OnGetText := HoursFieldGetText;
    FTimeScheduled.OnGetText := HoursFieldGetText;

    SetTimeFieldDisplayFormat(FTimeIn);
    SetTimeFieldDisplayFormat(FTimeOut);
    SetDateFieldDisplayFormat(FDateIn);
    SetDateFieldDisplayFormat(FDateOut);
    SetDateFieldDisplayFormat(FPayPeriodEndDate);
    SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
    SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
    end;

    TDbDummyEntry

    procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
    var
    LTimestamp: TDateTime;
    begin
    inherited;
    LTimestamp := Now;
    FLastModifiedBy.AsUserName := FTimesheet.User.UserName;
    FLastModifiedTimestamp.AsDateTime := LTimestamp;
    if State in [dsInsert] then
    begin
    FCreatedBy.AsUserName := FTimesheet.User.UserName;
    FCreatedTimestamp.AsDateTime := LTimestamp;
    end;
    end;

    constructor TDbSourceEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
    ACopyFunc: TCopyFunc<ITimesheetEntry>; AProc: TLoadTimesheetProc;
    AElectionList: ILevelPayElectionList; APositionList: IHourlyPositionList;
    ACreateFields: Boolean);
    begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);

    FLoadTimesheetEntries := AProc;
    FElectionList := AElectionList;
    FPositionList := APositionList;
    end;

    procedure TDbSourceEntry.Load;
    var
    LEmployeeID: TIdentifier;
    LFirstEntryDate: TDate;
    LLastEntryDate: TDate;
    begin
    LEmployeeID := FTimesheet.EmployeeID;
    LFirstEntryDate := FTimesheet.FirstEntryDate;
    LLastEntryDate := FTimesheet.LastEntryDate;
    FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
    end;


    TDbDummyEntry

    procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
    var
    LEntryTypeID: TIdentifier;
    LClosure: ISchoolClosure;
    LNote: AnsiString;
    LRowNbr: TRowNbr;
    begin
    Dec(FPlaceholderID);
    LNote := '';
    LEntryTypeID := 0;
    LRowNbr := ARowNbr;

    if LRowNbr < 2 then //This is a first entry for the date
    begin
    if FClosureList.Find(ADate) then
    begin
    LClosure := FClosureList.Closure;
    LEntryTypeID := LClosure.EntryTypeID;
    LNote := AnsiString(LClosure.Caption);
    end
    else
    begin
    if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
    LEntryTypeID := k_Regular
    else
    LEntryTypeID := 0;
    end;
    end;

    Append;
    FId.AsIdentifier := FPlaceholderID;
    FRowNbr.AsRowNbr := LRowNbr;
    FDateIn.AsDateTime := ADate;
    FDateOut.AsDateTime := ADate;
    FEntryTypeID.AsIdentifier := LEntryTypeID;
    FNote.AsAnsiString := LNote;
    Post;
    end;

    constructor TDbDummyEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
    ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
    begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
    FClosureList := AClosureList;
    end;

    procedure TDbDummyEntry.CreateFields;
    begin
    inherited;
    FId.ReadOnly := False;
    FId.AutoGenerateValue := arNone;
    FEntryTypeID.OnChange := EntryTypeIDOnChange;
    end;

    procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
    begin
    inherited;
    FId.AsIdentifier := AModel.ID;
    end;

    procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
    var
    LDateIn: TDate;
    LDateOut: TDate;
    begin
    if Sender.AsIdentifier = k_LandSchool then
    begin
    LDateIn := FDateIn.AsDateTime;
    LDateOut := LDateIn + 1;
    FDateOut.AsDateTime := LDateOut;
    FTimeIn.AsDateTime := GetScheduledTimeOut;
    FTimeOut.AsDateTime := GetScheduledTimeIn;
    end;
    end;

    TDbTimesheetEntry

    procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
    var
    LEntry: ITimesheetEntry;
    begin
    FSource.Add(AModel);
    LEntry := FSource.Entry; //Get model with updated ID
    FTimesheet.WorkweekList.AddEntry(LEntry);
    inherited Add(LEntry);
    end;

    procedure TDbTimesheetEntry.Clear;
    begin
    PlaceholderID := 0;
    Dataset.CancelUpdates;
    DisableControls;
    try
    First;
    while RecordCount > 0 do
    Dataset.Delete;
    finally
    EnableControls;
    end;
    end;

    constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
    AModelFunc: TModelFunc<ITimesheetEntry>;
    ACopyFunc: TCopyFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
    ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
    ACreateFields: Boolean);
    begin
    inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
    FSource := ASourceFunc(FTimesheet);

    FClone := TFDMemTable.Create(ADataset);
    FClone.CloneCursor(ADataset);
    end;

    procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
    var
    LDate: TDate;
    LRowNbr: SmallInt;
    LEntry: ITimesheetEntry;
    begin
    FSource.Delete(AModel);
    WorkweekList.DeleteEntry(AModel);
    DisableControls;
    try
    LDate := AModel.DateIn;
    LRowNbr := AModel.RowNbr;
    inherited Delete(AModel);
    if (LRowNbr = 1) then
    begin
    AddPlacedholder(LDate, 0);
    LEntry := Model; //Get placeholder
    WorkweekList.AddEntry(LEntry);
    end;
    finally
    EnableControls;
    end;
    end;

    function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
    begin
    Result := 0;
    FClone.SetRange([GetDateIn], [GetDateIn]);
    while not FClone.Eof do
    begin
    Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
    FClone.Next;
    end;
    FClone.CancelRange;
    end;

    procedure TDbTimesheetEntry.Load;
    var
    LDate: TDate;
    LFirstEntryDate: TDate;
    LCutoffDate: TDate;
    LEntry: ITimesheetEntry;
    LWeekOf: TDate;
    LWorkweekList: IWorkweekList;
    begin
    LWorkweekList := FTimesheet.WorkweekList;
    LFirstEntryDate := FTimesheet.FirstEntryDate;
    LCutOffDate := FTimesheet.LastEntryDate;
    LWeekOf := TDateTime(LFirstEntryDate).StartOfWeek;

    LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
    FSource.Load;
    DisableControls;
    try
    Clear;

    LDate := LWeekOf;
    repeat
    FSource.FilterEntries(LDate);
    if FSource.HasEntries then
    begin
    for LEntry in FSource do
    begin
    if LDate >= LFirstEntryDate then
    begin
    inherited Add(LEntry); //this must call inherited add to adding to FSource
    end;
    LWorkweekList.AddEntry(LEntry);
    end;
    end
    else
    begin
    if LDate >= LFirstEntryDate then
    begin
    AddPlacedholder(LDate, 0);
    LEntry := GetModel; //This retrieves the placeholder
    LWorkweekList.AddEntry(LEntry);
    end;
    end;
    LDate := LDate + 1;
    until LDate > LCutOffDate;
    finally
    FSource.ClearFilter;
    EnableControls;
    First;
    end;
    end;

    procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
    var
    LEntry: ITimesheetEntry;
    begin
    DisableControls;
    try
    if Assigned(OldModel) then
    begin
    if OldModel.Placeholder then
    begin
    FSource.Add(NewModel);
    end
    else
    begin
    FSource.Update(NewModel);
    end;
    inherited Delete(OldModel);
    WorkweekList.DeleteEntry(OldModel);
    end
    else
    begin
    FSource.Add(NewModel);
    end;

    LEntry := FSource.Entry; //Get entry with new ID
    inherited Add(LEntry);
    WorkweekList.AddEntry(LEntry);
    finally
    EnableControls;
    end;
    end;

    function TDbTimesheetEntry.WorkweekList: IWorkweekList;
    begin
    Result := FTimesheet.WorkweekList;
    end;

    end.









    share|improve this question
























      2












      2








      2








      I am working solo on an employee timekeeping project which uses a database to store time sheet entries. I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes. For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;



      Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor. I use a domain object to create the required interfaces and inject them into the class being created.
      Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object.



      The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships. These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.



      Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes. Here is a sample of code from one of my modules for time sheet entries.



      unit LevelPay.DbModel.TimesheetEntry;

      interface

      uses
      Data.Db
      , FireDAC.Comp.DataSet
      , MyLib.Model.Interfaces
      , LevelPay.Model.Types
      , LevelPay.Model.Constants
      , LevelPay.Model.Interfaces
      , LevelPay.DbModel.AppModel
      ;

      type
      TDbCustomTimesheetEntry = class(
      TDbAppModel<ITimesheetEntry>,
      ITimesheetEntry
      )
      strict private
      FCopyFunc: TCopyFunc<ITimesheetEntry>;
      procedure ClearFilter;
      procedure FilterEntries(const ADate: TDate);
      strict protected
      FTimesheet: ITimesheet;
      FID: TField;
      FEmployeeID: TField;
      FPayPeriodEndDate: TField;
      FFiscalYearEndDate: TField;
      FFiscalYearStartDate: TField;
      FRowNbr: TField;
      FEntryTypeID: TField;
      FDateIn: TField;
      FTimein: TField;
      FDateOut: TField;
      FTimeOut: TField;
      FCreatedBy: TField;
      FCreatedTimestamp: TField;
      FLastModifiedBy: TField;
      FLastModifiedTimestamp: TField;
      FNote: TField;
      FClockable: TField;
      FClockableHours: TField;
      FDayOfWeek: TField;
      FDifference: TField;
      FEmployeeName: TField;
      FEntryTypeCaption: TField;
      FTimeElapsed: TField;
      FDateIndex: TFDIndex;
      FTimeScheduled: TField;
      FScheduledTimeIn: TField;
      FScheduledTimeOut: TField;
      FWeekOf: TField;
      function GetID: TIdentifier;
      function GetModel: ITimesheetEntry; override;
      function GetClockable: Boolean;
      function GetClockableHours: THours;
      function GetDateIn: TDate;
      function GetDateOut: TDate;
      function GetDifference: THours;
      function GetEmployeeID: TIdentifier;
      function GetEmployeeName: string;
      function GetPayPeriodEndDate: TDate;
      function GetFiscalYearStartDate: TDate;
      function GetFiscalYearEndDate: TDate;
      function GetEntryTypeID: TIdentifier;
      function GetEntryTypeCaption: TCaption;
      function GetPlaceholder: Boolean;
      function GetRowNbr: TRowNbr;
      function GetScheduledTimeIn: TTime;
      function GetScheduledTimeOut: TTime;
      function GetTimeElapsed: THours;
      function GetTimein: TTime;
      function GetTimeOut: TTime;
      function GetTimeScheduled: THours;
      function GetWeekOf: TDate;
      function GetWeekDay: string;
      function GetCreatedBy: TUserName;
      function GetCreatedTimestamp: TDateTime;
      function GetLastModifiedBy: TUserName;
      function GetLastModifiedTimestamp: TDateTime;
      function GetNote: AnsiString;
      function GetEntry: ITimesheetEntry;
      function GetTimesheet: ITimesheet;
      function GetHasEntries: Boolean;
      function Find(AModel: ITimesheetEntry): Boolean; override;
      procedure DoUpdate(AModel: ITimesheetEntry); override;
      procedure Load; virtual;
      procedure CreateFields; override;
      procedure CreateCalcFields; override;
      procedure CreateIndexes; override;
      procedure FormatFields; override;
      procedure OnCalcFields(Dataset: TDataset); override;
      procedure OnNewRecord(Dataset: TDataset); override;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ACreateFields: Boolean
      ); reintroduce;
      property ID: TIdentifier read GetID;
      property EmployeeID: TIdentifier read GetEmployeeID;
      property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
      property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
      property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
      property EntryTypeID: TIdentifier read GetEntryTypeID;
      property EntryTypeCaption: TCaption read GetEntryTypeCaption;
      property RowNbr: TRowNbr read GetRowNbr;
      property Clockable: Boolean read GetClockable;
      property ClockableHours: THours read GetClockableHours;
      property DateIn: TDate read GetDateIn;
      property EmployeeName: string read GetEmployeeName;
      property ScheduledTimeIn: TTime read GetScheduledTimeIn;
      property ScheduledTimeOut: TTime read GetScheduledTimeOut;
      property TimeIn: TTime read GetTimein;
      property DateOut: TDate read GetDateOut;
      property TimeOut: TTime read GetTimeOut;
      property TimeElapsed: THours read GetTimeElapsed;
      property Placeholder: Boolean read GetPlaceholder;
      property TimeScheduled: THours read GetTimeScheduled;
      property Difference: THours read GetDifference;
      property WeekDay: string read GetWeekDay;
      property WeekOf: TDate read GetWeekOf;
      property CreatedBy: TUserName read GetCreatedBy;
      property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
      property LastModifiedBy: TUserName read GetLastModifiedBy;
      property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
      property Note: AnsiString read GetNote;
      property Timesheet: ITimesheet read GetTimesheet;
      end;

      TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
      strict private
      FLoadTimesheetEntries: TLoadTimesheetProc;
      FElectionList: ILevelPayElectionList;
      FPositionList: IHourlyPositionList;
      strict protected
      procedure BeforePost(Dataset: TDataset); override;
      procedure Load; override;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      AProc: TLoadTimesheetProc;
      AElectionList: ILevelPayElectionList;
      APositionList: IHourlyPositionList;
      ACreateFields: Boolean = True
      ); reintroduce;
      end;

      TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
      strict private
      FPlaceholderID: TIdentifier;
      FClosureList: ISchoolClosureList;
      procedure EntryTypeIDOnChange(Sender: TField);
      strict protected
      procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
      procedure CreateFields; override;
      procedure DoAdd(AModel: ITimesheetEntry); override;
      property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      AClosureList: ISchoolClosureList;
      ACreateFields: Boolean
      ); reintroduce;
      end;

      TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
      strict private
      FClone: TFDDataset;
      FSource: ISourceEntryList;
      function GetNextRowNbr: TRowNbr;
      strict protected
      function WorkweekList: IWorkweekList;
      procedure Clear;
      procedure Load; override;
      public
      procedure Add(AModel: ITimesheetEntry); //replaces inherited add
      procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
      procedure Update(OldModel, NewModel: ITimesheetEntry);
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ASourceFunc: TSourceListFunc;
      AClosureList: ISchoolClosureList;
      ACreateFields: Boolean
      ); reintroduce;
      end;

      implementation

      uses
      System.SysUtils
      , System.Classes
      , System.Variants
      , System.DateUtils
      , FireDAC.Comp.Client
      , DateTimeHelper
      , LevelPay.Model.Helpers
      ;

      TCustomShift

      procedure TDbCustomTimesheetEntry.ClearFilter;
      begin
      CancelRange;
      end;

      constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet; ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACreateFields);
      FCopyFunc := ACopyFunc;
      FTimesheet := ATimesheet;
      end;

      procedure TDbCustomTimesheetEntry.CreateCalcFields;
      begin
      inherited;
      FClockable := CreateCalcBooleanField(k_Clockable);
      FClockableHours := CreateCalcFloatField(k_ClockableHours);
      FDayOfWeek := CreateCalcStringField(k_WeekDay, 13);
      FDifference := CreateCalcFloatField(k_Difference);
      FEmployeeName := CreateCalcStringField(k_EmployeeName, 40);
      FEntryTypeCaption := CreateCalcStringField(k_EntryTypeCaption, 20);
      FFiscalYearEndDate := CreateCalcDateTimeField(k_FiscalYearEndDate);
      FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
      FScheduledTimeIn := CreateCalcDateTimeField(k_ScheduledTimeIn);
      FScheduledTimeOut := CreateCalcDateTimeField(k_ScheduledTimeOut);
      FTimeElapsed := CreateCalcFloatField(k_TimeElapsed);
      FTimeScheduled := CreateCalcFloatField(k_TimeScheduled);
      FWeekOf := CreateCalcDateTimeField(k_WeekOf);
      end;

      procedure TDbCustomTimesheetEntry.CreateFields;
      begin
      FID := CreateField(k_Id);
      FEmployeeID := CreateField(k_EmployeeID);
      FEntryTypeID := CreateField(k_EntryTypeID);
      FRowNbr := CreateField(k_RowNbr);
      FTimeIn := CreateField(k_TimeIn);
      FTimeOut := CreateField(k_TimeOut);
      FID := CreateField(k_ID);
      FDateIn := CreateField(k_DateIn);
      FDateOut := CreateField(k_DateOut);
      FCreatedBy := CreateField(k_CreatedBy);
      FCreatedTimestamp := CreateField(k_CreatedTimeStamp);
      FLastModifiedBy := CreateField(k_LastModifiedBy);
      FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
      FNote := CreateField(k_Note);
      FPayPeriodEndDate := CreateField(k_PayPeriodEndDate);

      end;

      procedure TDbCustomTimesheetEntry.CreateIndexes;
      const
      FIELD_LIST = k_DateIn + ';' + k_RowNbr;
      begin
      inherited;
      FDateIndex := CreateIndex('ByDate', FIELD_LIST);
      FDateIndex.Selected := True;
      Dataset.IndexesActive := True;
      end;

      function TDbCustomTimesheetEntry.GetClockable: Boolean;
      begin
      Result := Rules.Clockable;
      end;

      function TDbCustomTimesheetEntry.GetClockableHours: THours;
      begin
      Result := Rules.ClockableHours;
      end;

      function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
      begin
      Result := FCreatedBy.AsUserName;
      end;

      function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
      begin
      Result := FCreatedTimestamp.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDateIn: TDate;
      begin
      Result := FDateIn.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDateOut: TDate;
      begin
      Result := FDateOut.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDifference: THours;
      begin
      Result := Rules.Difference;
      end;

      function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
      begin
      Result := FEmployeeID.AsIdentifier;
      end;

      function TDbCustomTimesheetEntry.GetEmployeeName: string;
      begin
      Result := Rules.EmployeeName;
      end;

      function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
      begin
      Result := FEntryTypeID.AsIdentifier;
      end;

      function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
      begin
      Result := FFiscalYearEndDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
      begin
      Result := FFiscalYearStartDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
      begin
      Result := RecordCount > 0;
      end;

      function TDbCustomTimesheetEntry.GetID: TIdentifier;
      begin
      Result := FID.AsInteger;
      end;

      function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
      begin
      Result := FLastModifiedBy.AsUserName;
      end;

      function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
      begin
      Result := FLastModifiedTimestamp.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
      var
      LResult: ITimesheetEntry;
      begin
      LResult := FCopyFunc(Self);
      Result := LResult;
      end;

      function TDbCustomTimesheetEntry.GetNote: AnsiString;
      begin
      Result := FNote.AsAnsiString;
      end;

      function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
      begin
      Result := FPayPeriodEndDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
      begin
      Result := Rules.Placeholder;
      end;

      function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
      begin
      Result := Model;
      end;

      function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
      begin
      Result := Rules.EntryTypeCaption
      end;

      function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
      begin
      Result := FRowNbr.AsRowNbr;
      end;

      function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
      begin
      Result := Rules.ScheduledTimeIn;
      end;

      function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
      begin
      Result := Rules.ScheduledTimeOut;
      end;

      function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
      begin
      Result := Rules.TimeElapsed;
      end;

      function TDbCustomTimesheetEntry.GetTimein: TTime;
      begin
      Result := FTimeIn.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetTimeOut: TTime;
      begin
      Result := FTimeOut.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
      begin
      Result := Rules.TimeScheduled;
      end;

      function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
      begin
      Result := FTimesheet;
      end;

      function TDbCustomTimesheetEntry.GetWeekDay: string;
      begin
      Result := Rules.WeekDay;
      end;

      function TDbCustomTimesheetEntry.GetWeekOf: TDate;
      begin
      Result := Rules.WeekOf;
      end;

      procedure TDbCustomTimesheetEntry.Load;
      begin
      //Stub procedure
      end;

      procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
      begin
      inherited;
      if not Assigned(Rules) then Exit;

      FClockable.AsBoolean := GetClockable;
      FClockableHours.AsHours := GetClockableHours;
      FDayOfWeek.AsString := GetWeekDay;
      FDifference.AsHours := GetDifference;
      FEmployeeName.AsString := GetEmployeeName;
      FEntryTypeCaption.AsCaption := GetEntryTypeCaption;
      FTimeElapsed.AsHours := GetTimeElapsed;
      FTimeScheduled.AsHours := GetTimeScheduled;
      FScheduledTimeIn.AsDateTime := GetScheduledTimeIn;
      FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
      FWeekOf.AsDateTime := GetWeekOf;
      end;

      procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
      begin
      inherited;

      FEmployeeID.AsIdentifier := FTimesheet.EmployeeID;
      FFiscalYearEndDate.AsDateTime := FTimesheet.FiscalYearEndDate;
      FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
      FPayPeriodEndDate.AsDateTime := FTimesheet.PayPeriodEndDate;
      end;

      procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
      begin
      inherited;

      FEmployeeID.AsIdentifier := AModel.EmployeeID;
      FRowNbr.AsRowNbr := AModel.RowNbr;
      FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
      FDateIn.AsDateTime := AModel.DateIn;
      FTimeIn.AsDateTime := AModel.TimeIn;
      FDateOut.AsDateTime := AModel.DateOut;
      FTimeOut.AsDateTime := AModel.TimeOut;
      FNote.AsAnsiString := AModel.Note;
      end;

      procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
      begin
      FDateIndex.Selected := True;
      SetRange([ADate], [ADate]);
      end;

      function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
      begin
      Result := Locate(k_ID, AModel.ID);
      end;

      procedure TDbCustomTimesheetEntry.FormatFields;
      begin
      inherited;
      FTimeElapsed.OnGetText := HoursFieldGetText;
      FClockableHours.OnGetText := HoursFieldGetText;
      FDifference.OnGetText := HoursFieldGetText;
      FTimeScheduled.OnGetText := HoursFieldGetText;

      SetTimeFieldDisplayFormat(FTimeIn);
      SetTimeFieldDisplayFormat(FTimeOut);
      SetDateFieldDisplayFormat(FDateIn);
      SetDateFieldDisplayFormat(FDateOut);
      SetDateFieldDisplayFormat(FPayPeriodEndDate);
      SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
      SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
      end;

      TDbDummyEntry

      procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
      var
      LTimestamp: TDateTime;
      begin
      inherited;
      LTimestamp := Now;
      FLastModifiedBy.AsUserName := FTimesheet.User.UserName;
      FLastModifiedTimestamp.AsDateTime := LTimestamp;
      if State in [dsInsert] then
      begin
      FCreatedBy.AsUserName := FTimesheet.User.UserName;
      FCreatedTimestamp.AsDateTime := LTimestamp;
      end;
      end;

      constructor TDbSourceEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
      ACopyFunc: TCopyFunc<ITimesheetEntry>; AProc: TLoadTimesheetProc;
      AElectionList: ILevelPayElectionList; APositionList: IHourlyPositionList;
      ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);

      FLoadTimesheetEntries := AProc;
      FElectionList := AElectionList;
      FPositionList := APositionList;
      end;

      procedure TDbSourceEntry.Load;
      var
      LEmployeeID: TIdentifier;
      LFirstEntryDate: TDate;
      LLastEntryDate: TDate;
      begin
      LEmployeeID := FTimesheet.EmployeeID;
      LFirstEntryDate := FTimesheet.FirstEntryDate;
      LLastEntryDate := FTimesheet.LastEntryDate;
      FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
      end;


      TDbDummyEntry

      procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
      var
      LEntryTypeID: TIdentifier;
      LClosure: ISchoolClosure;
      LNote: AnsiString;
      LRowNbr: TRowNbr;
      begin
      Dec(FPlaceholderID);
      LNote := '';
      LEntryTypeID := 0;
      LRowNbr := ARowNbr;

      if LRowNbr < 2 then //This is a first entry for the date
      begin
      if FClosureList.Find(ADate) then
      begin
      LClosure := FClosureList.Closure;
      LEntryTypeID := LClosure.EntryTypeID;
      LNote := AnsiString(LClosure.Caption);
      end
      else
      begin
      if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
      LEntryTypeID := k_Regular
      else
      LEntryTypeID := 0;
      end;
      end;

      Append;
      FId.AsIdentifier := FPlaceholderID;
      FRowNbr.AsRowNbr := LRowNbr;
      FDateIn.AsDateTime := ADate;
      FDateOut.AsDateTime := ADate;
      FEntryTypeID.AsIdentifier := LEntryTypeID;
      FNote.AsAnsiString := LNote;
      Post;
      end;

      constructor TDbDummyEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
      FClosureList := AClosureList;
      end;

      procedure TDbDummyEntry.CreateFields;
      begin
      inherited;
      FId.ReadOnly := False;
      FId.AutoGenerateValue := arNone;
      FEntryTypeID.OnChange := EntryTypeIDOnChange;
      end;

      procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
      begin
      inherited;
      FId.AsIdentifier := AModel.ID;
      end;

      procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
      var
      LDateIn: TDate;
      LDateOut: TDate;
      begin
      if Sender.AsIdentifier = k_LandSchool then
      begin
      LDateIn := FDateIn.AsDateTime;
      LDateOut := LDateIn + 1;
      FDateOut.AsDateTime := LDateOut;
      FTimeIn.AsDateTime := GetScheduledTimeOut;
      FTimeOut.AsDateTime := GetScheduledTimeIn;
      end;
      end;

      TDbTimesheetEntry

      procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
      var
      LEntry: ITimesheetEntry;
      begin
      FSource.Add(AModel);
      LEntry := FSource.Entry; //Get model with updated ID
      FTimesheet.WorkweekList.AddEntry(LEntry);
      inherited Add(LEntry);
      end;

      procedure TDbTimesheetEntry.Clear;
      begin
      PlaceholderID := 0;
      Dataset.CancelUpdates;
      DisableControls;
      try
      First;
      while RecordCount > 0 do
      Dataset.Delete;
      finally
      EnableControls;
      end;
      end;

      constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
      ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
      ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
      FSource := ASourceFunc(FTimesheet);

      FClone := TFDMemTable.Create(ADataset);
      FClone.CloneCursor(ADataset);
      end;

      procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
      var
      LDate: TDate;
      LRowNbr: SmallInt;
      LEntry: ITimesheetEntry;
      begin
      FSource.Delete(AModel);
      WorkweekList.DeleteEntry(AModel);
      DisableControls;
      try
      LDate := AModel.DateIn;
      LRowNbr := AModel.RowNbr;
      inherited Delete(AModel);
      if (LRowNbr = 1) then
      begin
      AddPlacedholder(LDate, 0);
      LEntry := Model; //Get placeholder
      WorkweekList.AddEntry(LEntry);
      end;
      finally
      EnableControls;
      end;
      end;

      function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
      begin
      Result := 0;
      FClone.SetRange([GetDateIn], [GetDateIn]);
      while not FClone.Eof do
      begin
      Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
      FClone.Next;
      end;
      FClone.CancelRange;
      end;

      procedure TDbTimesheetEntry.Load;
      var
      LDate: TDate;
      LFirstEntryDate: TDate;
      LCutoffDate: TDate;
      LEntry: ITimesheetEntry;
      LWeekOf: TDate;
      LWorkweekList: IWorkweekList;
      begin
      LWorkweekList := FTimesheet.WorkweekList;
      LFirstEntryDate := FTimesheet.FirstEntryDate;
      LCutOffDate := FTimesheet.LastEntryDate;
      LWeekOf := TDateTime(LFirstEntryDate).StartOfWeek;

      LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
      FSource.Load;
      DisableControls;
      try
      Clear;

      LDate := LWeekOf;
      repeat
      FSource.FilterEntries(LDate);
      if FSource.HasEntries then
      begin
      for LEntry in FSource do
      begin
      if LDate >= LFirstEntryDate then
      begin
      inherited Add(LEntry); //this must call inherited add to adding to FSource
      end;
      LWorkweekList.AddEntry(LEntry);
      end;
      end
      else
      begin
      if LDate >= LFirstEntryDate then
      begin
      AddPlacedholder(LDate, 0);
      LEntry := GetModel; //This retrieves the placeholder
      LWorkweekList.AddEntry(LEntry);
      end;
      end;
      LDate := LDate + 1;
      until LDate > LCutOffDate;
      finally
      FSource.ClearFilter;
      EnableControls;
      First;
      end;
      end;

      procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
      var
      LEntry: ITimesheetEntry;
      begin
      DisableControls;
      try
      if Assigned(OldModel) then
      begin
      if OldModel.Placeholder then
      begin
      FSource.Add(NewModel);
      end
      else
      begin
      FSource.Update(NewModel);
      end;
      inherited Delete(OldModel);
      WorkweekList.DeleteEntry(OldModel);
      end
      else
      begin
      FSource.Add(NewModel);
      end;

      LEntry := FSource.Entry; //Get entry with new ID
      inherited Add(LEntry);
      WorkweekList.AddEntry(LEntry);
      finally
      EnableControls;
      end;
      end;

      function TDbTimesheetEntry.WorkweekList: IWorkweekList;
      begin
      Result := FTimesheet.WorkweekList;
      end;

      end.









      share|improve this question














      I am working solo on an employee timekeeping project which uses a database to store time sheet entries. I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes. For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;



      Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor. I use a domain object to create the required interfaces and inject them into the class being created.
      Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object.



      The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships. These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.



      Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes. Here is a sample of code from one of my modules for time sheet entries.



      unit LevelPay.DbModel.TimesheetEntry;

      interface

      uses
      Data.Db
      , FireDAC.Comp.DataSet
      , MyLib.Model.Interfaces
      , LevelPay.Model.Types
      , LevelPay.Model.Constants
      , LevelPay.Model.Interfaces
      , LevelPay.DbModel.AppModel
      ;

      type
      TDbCustomTimesheetEntry = class(
      TDbAppModel<ITimesheetEntry>,
      ITimesheetEntry
      )
      strict private
      FCopyFunc: TCopyFunc<ITimesheetEntry>;
      procedure ClearFilter;
      procedure FilterEntries(const ADate: TDate);
      strict protected
      FTimesheet: ITimesheet;
      FID: TField;
      FEmployeeID: TField;
      FPayPeriodEndDate: TField;
      FFiscalYearEndDate: TField;
      FFiscalYearStartDate: TField;
      FRowNbr: TField;
      FEntryTypeID: TField;
      FDateIn: TField;
      FTimein: TField;
      FDateOut: TField;
      FTimeOut: TField;
      FCreatedBy: TField;
      FCreatedTimestamp: TField;
      FLastModifiedBy: TField;
      FLastModifiedTimestamp: TField;
      FNote: TField;
      FClockable: TField;
      FClockableHours: TField;
      FDayOfWeek: TField;
      FDifference: TField;
      FEmployeeName: TField;
      FEntryTypeCaption: TField;
      FTimeElapsed: TField;
      FDateIndex: TFDIndex;
      FTimeScheduled: TField;
      FScheduledTimeIn: TField;
      FScheduledTimeOut: TField;
      FWeekOf: TField;
      function GetID: TIdentifier;
      function GetModel: ITimesheetEntry; override;
      function GetClockable: Boolean;
      function GetClockableHours: THours;
      function GetDateIn: TDate;
      function GetDateOut: TDate;
      function GetDifference: THours;
      function GetEmployeeID: TIdentifier;
      function GetEmployeeName: string;
      function GetPayPeriodEndDate: TDate;
      function GetFiscalYearStartDate: TDate;
      function GetFiscalYearEndDate: TDate;
      function GetEntryTypeID: TIdentifier;
      function GetEntryTypeCaption: TCaption;
      function GetPlaceholder: Boolean;
      function GetRowNbr: TRowNbr;
      function GetScheduledTimeIn: TTime;
      function GetScheduledTimeOut: TTime;
      function GetTimeElapsed: THours;
      function GetTimein: TTime;
      function GetTimeOut: TTime;
      function GetTimeScheduled: THours;
      function GetWeekOf: TDate;
      function GetWeekDay: string;
      function GetCreatedBy: TUserName;
      function GetCreatedTimestamp: TDateTime;
      function GetLastModifiedBy: TUserName;
      function GetLastModifiedTimestamp: TDateTime;
      function GetNote: AnsiString;
      function GetEntry: ITimesheetEntry;
      function GetTimesheet: ITimesheet;
      function GetHasEntries: Boolean;
      function Find(AModel: ITimesheetEntry): Boolean; override;
      procedure DoUpdate(AModel: ITimesheetEntry); override;
      procedure Load; virtual;
      procedure CreateFields; override;
      procedure CreateCalcFields; override;
      procedure CreateIndexes; override;
      procedure FormatFields; override;
      procedure OnCalcFields(Dataset: TDataset); override;
      procedure OnNewRecord(Dataset: TDataset); override;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ACreateFields: Boolean
      ); reintroduce;
      property ID: TIdentifier read GetID;
      property EmployeeID: TIdentifier read GetEmployeeID;
      property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
      property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
      property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
      property EntryTypeID: TIdentifier read GetEntryTypeID;
      property EntryTypeCaption: TCaption read GetEntryTypeCaption;
      property RowNbr: TRowNbr read GetRowNbr;
      property Clockable: Boolean read GetClockable;
      property ClockableHours: THours read GetClockableHours;
      property DateIn: TDate read GetDateIn;
      property EmployeeName: string read GetEmployeeName;
      property ScheduledTimeIn: TTime read GetScheduledTimeIn;
      property ScheduledTimeOut: TTime read GetScheduledTimeOut;
      property TimeIn: TTime read GetTimein;
      property DateOut: TDate read GetDateOut;
      property TimeOut: TTime read GetTimeOut;
      property TimeElapsed: THours read GetTimeElapsed;
      property Placeholder: Boolean read GetPlaceholder;
      property TimeScheduled: THours read GetTimeScheduled;
      property Difference: THours read GetDifference;
      property WeekDay: string read GetWeekDay;
      property WeekOf: TDate read GetWeekOf;
      property CreatedBy: TUserName read GetCreatedBy;
      property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
      property LastModifiedBy: TUserName read GetLastModifiedBy;
      property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
      property Note: AnsiString read GetNote;
      property Timesheet: ITimesheet read GetTimesheet;
      end;

      TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
      strict private
      FLoadTimesheetEntries: TLoadTimesheetProc;
      FElectionList: ILevelPayElectionList;
      FPositionList: IHourlyPositionList;
      strict protected
      procedure BeforePost(Dataset: TDataset); override;
      procedure Load; override;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      AProc: TLoadTimesheetProc;
      AElectionList: ILevelPayElectionList;
      APositionList: IHourlyPositionList;
      ACreateFields: Boolean = True
      ); reintroduce;
      end;

      TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
      strict private
      FPlaceholderID: TIdentifier;
      FClosureList: ISchoolClosureList;
      procedure EntryTypeIDOnChange(Sender: TField);
      strict protected
      procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
      procedure CreateFields; override;
      procedure DoAdd(AModel: ITimesheetEntry); override;
      property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
      public
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      AClosureList: ISchoolClosureList;
      ACreateFields: Boolean
      ); reintroduce;
      end;

      TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
      strict private
      FClone: TFDDataset;
      FSource: ISourceEntryList;
      function GetNextRowNbr: TRowNbr;
      strict protected
      function WorkweekList: IWorkweekList;
      procedure Clear;
      procedure Load; override;
      public
      procedure Add(AModel: ITimesheetEntry); //replaces inherited add
      procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
      procedure Update(OldModel, NewModel: ITimesheetEntry);
      constructor Create(
      ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet;
      ASourceFunc: TSourceListFunc;
      AClosureList: ISchoolClosureList;
      ACreateFields: Boolean
      ); reintroduce;
      end;

      implementation

      uses
      System.SysUtils
      , System.Classes
      , System.Variants
      , System.DateUtils
      , FireDAC.Comp.Client
      , DateTimeHelper
      , LevelPay.Model.Helpers
      ;

      TCustomShift

      procedure TDbCustomTimesheetEntry.ClearFilter;
      begin
      CancelRange;
      end;

      constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet; ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACreateFields);
      FCopyFunc := ACopyFunc;
      FTimesheet := ATimesheet;
      end;

      procedure TDbCustomTimesheetEntry.CreateCalcFields;
      begin
      inherited;
      FClockable := CreateCalcBooleanField(k_Clockable);
      FClockableHours := CreateCalcFloatField(k_ClockableHours);
      FDayOfWeek := CreateCalcStringField(k_WeekDay, 13);
      FDifference := CreateCalcFloatField(k_Difference);
      FEmployeeName := CreateCalcStringField(k_EmployeeName, 40);
      FEntryTypeCaption := CreateCalcStringField(k_EntryTypeCaption, 20);
      FFiscalYearEndDate := CreateCalcDateTimeField(k_FiscalYearEndDate);
      FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
      FScheduledTimeIn := CreateCalcDateTimeField(k_ScheduledTimeIn);
      FScheduledTimeOut := CreateCalcDateTimeField(k_ScheduledTimeOut);
      FTimeElapsed := CreateCalcFloatField(k_TimeElapsed);
      FTimeScheduled := CreateCalcFloatField(k_TimeScheduled);
      FWeekOf := CreateCalcDateTimeField(k_WeekOf);
      end;

      procedure TDbCustomTimesheetEntry.CreateFields;
      begin
      FID := CreateField(k_Id);
      FEmployeeID := CreateField(k_EmployeeID);
      FEntryTypeID := CreateField(k_EntryTypeID);
      FRowNbr := CreateField(k_RowNbr);
      FTimeIn := CreateField(k_TimeIn);
      FTimeOut := CreateField(k_TimeOut);
      FID := CreateField(k_ID);
      FDateIn := CreateField(k_DateIn);
      FDateOut := CreateField(k_DateOut);
      FCreatedBy := CreateField(k_CreatedBy);
      FCreatedTimestamp := CreateField(k_CreatedTimeStamp);
      FLastModifiedBy := CreateField(k_LastModifiedBy);
      FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
      FNote := CreateField(k_Note);
      FPayPeriodEndDate := CreateField(k_PayPeriodEndDate);

      end;

      procedure TDbCustomTimesheetEntry.CreateIndexes;
      const
      FIELD_LIST = k_DateIn + ';' + k_RowNbr;
      begin
      inherited;
      FDateIndex := CreateIndex('ByDate', FIELD_LIST);
      FDateIndex.Selected := True;
      Dataset.IndexesActive := True;
      end;

      function TDbCustomTimesheetEntry.GetClockable: Boolean;
      begin
      Result := Rules.Clockable;
      end;

      function TDbCustomTimesheetEntry.GetClockableHours: THours;
      begin
      Result := Rules.ClockableHours;
      end;

      function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
      begin
      Result := FCreatedBy.AsUserName;
      end;

      function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
      begin
      Result := FCreatedTimestamp.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDateIn: TDate;
      begin
      Result := FDateIn.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDateOut: TDate;
      begin
      Result := FDateOut.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetDifference: THours;
      begin
      Result := Rules.Difference;
      end;

      function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
      begin
      Result := FEmployeeID.AsIdentifier;
      end;

      function TDbCustomTimesheetEntry.GetEmployeeName: string;
      begin
      Result := Rules.EmployeeName;
      end;

      function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
      begin
      Result := FEntryTypeID.AsIdentifier;
      end;

      function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
      begin
      Result := FFiscalYearEndDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
      begin
      Result := FFiscalYearStartDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
      begin
      Result := RecordCount > 0;
      end;

      function TDbCustomTimesheetEntry.GetID: TIdentifier;
      begin
      Result := FID.AsInteger;
      end;

      function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
      begin
      Result := FLastModifiedBy.AsUserName;
      end;

      function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
      begin
      Result := FLastModifiedTimestamp.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
      var
      LResult: ITimesheetEntry;
      begin
      LResult := FCopyFunc(Self);
      Result := LResult;
      end;

      function TDbCustomTimesheetEntry.GetNote: AnsiString;
      begin
      Result := FNote.AsAnsiString;
      end;

      function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
      begin
      Result := FPayPeriodEndDate.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
      begin
      Result := Rules.Placeholder;
      end;

      function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
      begin
      Result := Model;
      end;

      function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
      begin
      Result := Rules.EntryTypeCaption
      end;

      function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
      begin
      Result := FRowNbr.AsRowNbr;
      end;

      function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
      begin
      Result := Rules.ScheduledTimeIn;
      end;

      function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
      begin
      Result := Rules.ScheduledTimeOut;
      end;

      function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
      begin
      Result := Rules.TimeElapsed;
      end;

      function TDbCustomTimesheetEntry.GetTimein: TTime;
      begin
      Result := FTimeIn.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetTimeOut: TTime;
      begin
      Result := FTimeOut.AsDateTime;
      end;

      function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
      begin
      Result := Rules.TimeScheduled;
      end;

      function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
      begin
      Result := FTimesheet;
      end;

      function TDbCustomTimesheetEntry.GetWeekDay: string;
      begin
      Result := Rules.WeekDay;
      end;

      function TDbCustomTimesheetEntry.GetWeekOf: TDate;
      begin
      Result := Rules.WeekOf;
      end;

      procedure TDbCustomTimesheetEntry.Load;
      begin
      //Stub procedure
      end;

      procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
      begin
      inherited;
      if not Assigned(Rules) then Exit;

      FClockable.AsBoolean := GetClockable;
      FClockableHours.AsHours := GetClockableHours;
      FDayOfWeek.AsString := GetWeekDay;
      FDifference.AsHours := GetDifference;
      FEmployeeName.AsString := GetEmployeeName;
      FEntryTypeCaption.AsCaption := GetEntryTypeCaption;
      FTimeElapsed.AsHours := GetTimeElapsed;
      FTimeScheduled.AsHours := GetTimeScheduled;
      FScheduledTimeIn.AsDateTime := GetScheduledTimeIn;
      FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
      FWeekOf.AsDateTime := GetWeekOf;
      end;

      procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
      begin
      inherited;

      FEmployeeID.AsIdentifier := FTimesheet.EmployeeID;
      FFiscalYearEndDate.AsDateTime := FTimesheet.FiscalYearEndDate;
      FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
      FPayPeriodEndDate.AsDateTime := FTimesheet.PayPeriodEndDate;
      end;

      procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
      begin
      inherited;

      FEmployeeID.AsIdentifier := AModel.EmployeeID;
      FRowNbr.AsRowNbr := AModel.RowNbr;
      FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
      FDateIn.AsDateTime := AModel.DateIn;
      FTimeIn.AsDateTime := AModel.TimeIn;
      FDateOut.AsDateTime := AModel.DateOut;
      FTimeOut.AsDateTime := AModel.TimeOut;
      FNote.AsAnsiString := AModel.Note;
      end;

      procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
      begin
      FDateIndex.Selected := True;
      SetRange([ADate], [ADate]);
      end;

      function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
      begin
      Result := Locate(k_ID, AModel.ID);
      end;

      procedure TDbCustomTimesheetEntry.FormatFields;
      begin
      inherited;
      FTimeElapsed.OnGetText := HoursFieldGetText;
      FClockableHours.OnGetText := HoursFieldGetText;
      FDifference.OnGetText := HoursFieldGetText;
      FTimeScheduled.OnGetText := HoursFieldGetText;

      SetTimeFieldDisplayFormat(FTimeIn);
      SetTimeFieldDisplayFormat(FTimeOut);
      SetDateFieldDisplayFormat(FDateIn);
      SetDateFieldDisplayFormat(FDateOut);
      SetDateFieldDisplayFormat(FPayPeriodEndDate);
      SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
      SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
      end;

      TDbDummyEntry

      procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
      var
      LTimestamp: TDateTime;
      begin
      inherited;
      LTimestamp := Now;
      FLastModifiedBy.AsUserName := FTimesheet.User.UserName;
      FLastModifiedTimestamp.AsDateTime := LTimestamp;
      if State in [dsInsert] then
      begin
      FCreatedBy.AsUserName := FTimesheet.User.UserName;
      FCreatedTimestamp.AsDateTime := LTimestamp;
      end;
      end;

      constructor TDbSourceEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
      ACopyFunc: TCopyFunc<ITimesheetEntry>; AProc: TLoadTimesheetProc;
      AElectionList: ILevelPayElectionList; APositionList: IHourlyPositionList;
      ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);

      FLoadTimesheetEntries := AProc;
      FElectionList := AElectionList;
      FPositionList := APositionList;
      end;

      procedure TDbSourceEntry.Load;
      var
      LEmployeeID: TIdentifier;
      LFirstEntryDate: TDate;
      LLastEntryDate: TDate;
      begin
      LEmployeeID := FTimesheet.EmployeeID;
      LFirstEntryDate := FTimesheet.FirstEntryDate;
      LLastEntryDate := FTimesheet.LastEntryDate;
      FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
      end;


      TDbDummyEntry

      procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
      var
      LEntryTypeID: TIdentifier;
      LClosure: ISchoolClosure;
      LNote: AnsiString;
      LRowNbr: TRowNbr;
      begin
      Dec(FPlaceholderID);
      LNote := '';
      LEntryTypeID := 0;
      LRowNbr := ARowNbr;

      if LRowNbr < 2 then //This is a first entry for the date
      begin
      if FClosureList.Find(ADate) then
      begin
      LClosure := FClosureList.Closure;
      LEntryTypeID := LClosure.EntryTypeID;
      LNote := AnsiString(LClosure.Caption);
      end
      else
      begin
      if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
      LEntryTypeID := k_Regular
      else
      LEntryTypeID := 0;
      end;
      end;

      Append;
      FId.AsIdentifier := FPlaceholderID;
      FRowNbr.AsRowNbr := LRowNbr;
      FDateIn.AsDateTime := ADate;
      FDateOut.AsDateTime := ADate;
      FEntryTypeID.AsIdentifier := LEntryTypeID;
      FNote.AsAnsiString := LNote;
      Post;
      end;

      constructor TDbDummyEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
      ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
      FClosureList := AClosureList;
      end;

      procedure TDbDummyEntry.CreateFields;
      begin
      inherited;
      FId.ReadOnly := False;
      FId.AutoGenerateValue := arNone;
      FEntryTypeID.OnChange := EntryTypeIDOnChange;
      end;

      procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
      begin
      inherited;
      FId.AsIdentifier := AModel.ID;
      end;

      procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
      var
      LDateIn: TDate;
      LDateOut: TDate;
      begin
      if Sender.AsIdentifier = k_LandSchool then
      begin
      LDateIn := FDateIn.AsDateTime;
      LDateOut := LDateIn + 1;
      FDateOut.AsDateTime := LDateOut;
      FTimeIn.AsDateTime := GetScheduledTimeOut;
      FTimeOut.AsDateTime := GetScheduledTimeIn;
      end;
      end;

      TDbTimesheetEntry

      procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
      var
      LEntry: ITimesheetEntry;
      begin
      FSource.Add(AModel);
      LEntry := FSource.Entry; //Get model with updated ID
      FTimesheet.WorkweekList.AddEntry(LEntry);
      inherited Add(LEntry);
      end;

      procedure TDbTimesheetEntry.Clear;
      begin
      PlaceholderID := 0;
      Dataset.CancelUpdates;
      DisableControls;
      try
      First;
      while RecordCount > 0 do
      Dataset.Delete;
      finally
      EnableControls;
      end;
      end;

      constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
      AModelFunc: TModelFunc<ITimesheetEntry>;
      ACopyFunc: TCopyFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
      ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
      ACreateFields: Boolean);
      begin
      inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
      FSource := ASourceFunc(FTimesheet);

      FClone := TFDMemTable.Create(ADataset);
      FClone.CloneCursor(ADataset);
      end;

      procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
      var
      LDate: TDate;
      LRowNbr: SmallInt;
      LEntry: ITimesheetEntry;
      begin
      FSource.Delete(AModel);
      WorkweekList.DeleteEntry(AModel);
      DisableControls;
      try
      LDate := AModel.DateIn;
      LRowNbr := AModel.RowNbr;
      inherited Delete(AModel);
      if (LRowNbr = 1) then
      begin
      AddPlacedholder(LDate, 0);
      LEntry := Model; //Get placeholder
      WorkweekList.AddEntry(LEntry);
      end;
      finally
      EnableControls;
      end;
      end;

      function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
      begin
      Result := 0;
      FClone.SetRange([GetDateIn], [GetDateIn]);
      while not FClone.Eof do
      begin
      Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
      FClone.Next;
      end;
      FClone.CancelRange;
      end;

      procedure TDbTimesheetEntry.Load;
      var
      LDate: TDate;
      LFirstEntryDate: TDate;
      LCutoffDate: TDate;
      LEntry: ITimesheetEntry;
      LWeekOf: TDate;
      LWorkweekList: IWorkweekList;
      begin
      LWorkweekList := FTimesheet.WorkweekList;
      LFirstEntryDate := FTimesheet.FirstEntryDate;
      LCutOffDate := FTimesheet.LastEntryDate;
      LWeekOf := TDateTime(LFirstEntryDate).StartOfWeek;

      LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
      FSource.Load;
      DisableControls;
      try
      Clear;

      LDate := LWeekOf;
      repeat
      FSource.FilterEntries(LDate);
      if FSource.HasEntries then
      begin
      for LEntry in FSource do
      begin
      if LDate >= LFirstEntryDate then
      begin
      inherited Add(LEntry); //this must call inherited add to adding to FSource
      end;
      LWorkweekList.AddEntry(LEntry);
      end;
      end
      else
      begin
      if LDate >= LFirstEntryDate then
      begin
      AddPlacedholder(LDate, 0);
      LEntry := GetModel; //This retrieves the placeholder
      LWorkweekList.AddEntry(LEntry);
      end;
      end;
      LDate := LDate + 1;
      until LDate > LCutOffDate;
      finally
      FSource.ClearFilter;
      EnableControls;
      First;
      end;
      end;

      procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
      var
      LEntry: ITimesheetEntry;
      begin
      DisableControls;
      try
      if Assigned(OldModel) then
      begin
      if OldModel.Placeholder then
      begin
      FSource.Add(NewModel);
      end
      else
      begin
      FSource.Update(NewModel);
      end;
      inherited Delete(OldModel);
      WorkweekList.DeleteEntry(OldModel);
      end
      else
      begin
      FSource.Add(NewModel);
      end;

      LEntry := FSource.Entry; //Get entry with new ID
      inherited Add(LEntry);
      WorkweekList.AddEntry(LEntry);
      finally
      EnableControls;
      end;
      end;

      function TDbTimesheetEntry.WorkweekList: IWorkweekList;
      begin
      Result := FTimesheet.WorkweekList;
      end;

      end.






      delphi dependency-injection






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 13 '18 at 11:22









      joeb545joeb545

      1797




      1797






















          2 Answers
          2






          active

          oldest

          votes


















          4














          Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).



          If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.



          I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.



          Just two examples that I remember:



          • http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

          • http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/





          share|improve this answer























          • Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

            – joeb545
            Nov 13 '18 at 14:36



















          -1














          You have default property in root all TDataSet:



          property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;


          and you can use



          aTest: TDataSet; //any descant
          aValue := aTest['NameField'];


          Why complicate this with injections?

          Syntax purity?

          Like Paradox ObjectPAL from 1994.






          share|improve this answer

























          • One simple reason, unit testing! please read this QA

            – whosrdaddy
            Nov 13 '18 at 15:49











          • Poorly formatted, and fails to address the question that was asked

            – David Heffernan
            Nov 13 '18 at 16:34










          Your Answer






          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "1"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader:
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          ,
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );













          draft saved

          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53279936%2fconstructor-dependency-injection-alternatives%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          4














          Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).



          If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.



          I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.



          Just two examples that I remember:



          • http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

          • http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/





          share|improve this answer























          • Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

            – joeb545
            Nov 13 '18 at 14:36
















          4














          Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).



          If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.



          I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.



          Just two examples that I remember:



          • http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

          • http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/





          share|improve this answer























          • Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

            – joeb545
            Nov 13 '18 at 14:36














          4












          4








          4







          Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).



          If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.



          I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.



          Just two examples that I remember:



          • http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

          • http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/





          share|improve this answer













          Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).



          If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.



          I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.



          Just two examples that I remember:



          • http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

          • http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 13 '18 at 11:45









          Stefan GlienkeStefan Glienke

          16.2k13478




          16.2k13478












          • Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

            – joeb545
            Nov 13 '18 at 14:36


















          • Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

            – joeb545
            Nov 13 '18 at 14:36

















          Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

          – joeb545
          Nov 13 '18 at 14:36






          Thanks for the quick reply and the links. They were helpful. I was unsure how much separation of concerns was necessary and thought I had already struck a balance. For example, I had already refactored the rules for the calculated fields to a rules class. I can see that the classes I show in this post are trying to be a dataset wrapper, provide an interface to a single model object and also an interface to a list object. I will look at separating each of these responsibilities into separate classes and see what I get.

          – joeb545
          Nov 13 '18 at 14:36














          -1














          You have default property in root all TDataSet:



          property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;


          and you can use



          aTest: TDataSet; //any descant
          aValue := aTest['NameField'];


          Why complicate this with injections?

          Syntax purity?

          Like Paradox ObjectPAL from 1994.






          share|improve this answer

























          • One simple reason, unit testing! please read this QA

            – whosrdaddy
            Nov 13 '18 at 15:49











          • Poorly formatted, and fails to address the question that was asked

            – David Heffernan
            Nov 13 '18 at 16:34















          -1














          You have default property in root all TDataSet:



          property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;


          and you can use



          aTest: TDataSet; //any descant
          aValue := aTest['NameField'];


          Why complicate this with injections?

          Syntax purity?

          Like Paradox ObjectPAL from 1994.






          share|improve this answer

























          • One simple reason, unit testing! please read this QA

            – whosrdaddy
            Nov 13 '18 at 15:49











          • Poorly formatted, and fails to address the question that was asked

            – David Heffernan
            Nov 13 '18 at 16:34













          -1












          -1








          -1







          You have default property in root all TDataSet:



          property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;


          and you can use



          aTest: TDataSet; //any descant
          aValue := aTest['NameField'];


          Why complicate this with injections?

          Syntax purity?

          Like Paradox ObjectPAL from 1994.






          share|improve this answer















          You have default property in root all TDataSet:



          property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;


          and you can use



          aTest: TDataSet; //any descant
          aValue := aTest['NameField'];


          Why complicate this with injections?

          Syntax purity?

          Like Paradox ObjectPAL from 1994.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 13 '18 at 21:35









          zx485

          14.3k123047




          14.3k123047










          answered Nov 13 '18 at 14:16









          cehaceha

          353




          353












          • One simple reason, unit testing! please read this QA

            – whosrdaddy
            Nov 13 '18 at 15:49











          • Poorly formatted, and fails to address the question that was asked

            – David Heffernan
            Nov 13 '18 at 16:34

















          • One simple reason, unit testing! please read this QA

            – whosrdaddy
            Nov 13 '18 at 15:49











          • Poorly formatted, and fails to address the question that was asked

            – David Heffernan
            Nov 13 '18 at 16:34
















          One simple reason, unit testing! please read this QA

          – whosrdaddy
          Nov 13 '18 at 15:49





          One simple reason, unit testing! please read this QA

          – whosrdaddy
          Nov 13 '18 at 15:49













          Poorly formatted, and fails to address the question that was asked

          – David Heffernan
          Nov 13 '18 at 16:34





          Poorly formatted, and fails to address the question that was asked

          – David Heffernan
          Nov 13 '18 at 16:34

















          draft saved

          draft discarded
















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid


          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.

          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53279936%2fconstructor-dependency-injection-alternatives%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Use pre created SQLite database for Android project in kotlin

          Darth Vader #20

          Ondo