Terry Thompson
Terry Thompson

Reputation: 533

Constructor Dependency Injection alternatives

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.

Upvotes: 2

Views: 756

Answers (2)

ceha
ceha

Reputation: 50

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.

Upvotes: -1

Stefan Glienke
Stefan Glienke

Reputation: 21713

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:

Upvotes: 4

Related Questions