Search This Blog

Showing posts with label Tips & Tricks. Show all posts
Showing posts with label Tips & Tricks. Show all posts

Tuesday, July 18, 2023

Demystifying Partial Classes in Delphi

In programming, partial classes offer a powerful tool for code organization and separation. They allow developers to split the definition of a single class across multiple files, making it easier to manage large and complex codebases.

Understanding Partial Classes

In modern programming languages like C#, a partial class is a class that is split into multiple parts (files) but behaves as if it were defined in a single file. 
Each part contains a section of the class's definition, such as properties, methods, or events. 
These files are combined at compile time to create a unified class definition.


Splitting Responsibilities

One of the significant advantages of partial classes is the ability to split the implementation of a class across multiple places(files) based on responsibilities. 
For example, you can have one part dedicated to UI-related code, another for database interactions, and another for business logic. 
This separation helps keep code organized and maintainable.

Collaboration and Teamwork

Partial classes facilitate teamwork by allowing multiple developers to work on different aspects of a class simultaneously. Each developer can focus on a specific section, reducing the chances of merge conflicts and enabling parallel development.

Code Reusability:

Partial classes also promote code reusability. You can share common sections among different classes, allowing multiple classes to access shared code while keeping their own specific implementation separate.

Enhanced Maintainability:

By dividing a class into modular sections(files), partial classes make maintenance and refactoring more manageable. It is easier to locate and modify specific parts of a class without affecting other parts, reducing the risk of introducing bugs inadvertently.

Limitations:

While partial classes offer numerous benefits, it is crucial to be aware of their limitations. 
Modern languages impose certain rules for using partial classes. 
For instance, all parts of a partial class must have the same visibility (private, protected, etc.), and you cannot split properties or fields across files mostly.

Does Delphi support partial classes?

Well, not yet!

We should wait for Embarcadero to implement such a great ability in Delphi.

But!

Actually, there is something in Delphi (coming from the Pascal age) that is called include, 
using the include directive you can break or split your code somehow, it's not the same as a partial class in C# but still useful.

let me show you this ability with a piece of code:

  TMyClass = partial class
    procedure P1(AValue: string);
    procedure P2;
  end;
  
  procedure TMyClass.P1(AValue: string);
  begin
    {$I External.inc}
  end;

  procedure TMyClass.P2;
  begin
    ShowMessage('P2');
  end;  

How does the "TMyClass.P1" method work?
The answer is hidden in another file which is literally a text file and could be with any extension!
In my case, there is one line in this file without respecting the Pascal structure.

  ShowMessage(AValue);

I hope this wonderful feature will be added to Delphi soon.

Thursday, July 13, 2023

Implementing Interfaces by Delegation in Delphi!

Implementing Interfaces by Delegation

Is it possible at all?😮

The implements directive allows you to delegate the implementation of an interface to a property in the implementing class. For example:


  property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

Declares a property called MyInterface that implements the interface IMyInterface.

The implements directive must be the last specifier in the property declaration and can list more than one interface, separated by commas. The delegate property:

  • Must be of a class or interface type.
  • Cannot be an array property or have an index specifier.
  • Must have a read specifier. If the property uses a read method, that method must use the default register calling convention and cannot be dynamic (though it can be virtual) or specify the message directive.

The class you use to implement the delegated interface should derive from System.TAggregatedObject.

Delegating to an Interface-Type Property

If the delegate property is of an interface type, that interface, or an interface from which it derives, must occur in the ancestor list of the class where the property is declared. The delegate property must return an object whose class completely implements the interface specified by the implements directive, and which does so without method resolution clauses. For example:

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyClass = class(TObject, IMyInterface)
    FMyInterface: IMyInterface;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;
var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyInterface := ...// some object whose class implements IMyInterface
  MyInterface := MyClass;
  MyInterface.P1;
end;

Delegating to a Class-Type Property

If the delegate property is of a class type, that class and its ancestors are searched for methods implementing the specified interface before the enclosing class and its ancestors are searched. Thus it is possible to implement some methods in the class specified by the property, and others in the class where the property is declared. Method resolution clauses can be used in the usual way to resolve ambiguities or specify a particular method. An interface cannot be implemented by more than one class-type property. For example:

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyImplClass = class
    procedure P1;
    procedure P2;
  end;
  TExternalClass = class(TInterfacedObject, IMyInterface)
  private
    FMyImplClass: TMyImplClass;
  public  
    procedure IMyInterface.P1 = MyP1AlternativeProcedure;
    procedure MyP1AlternativeProcedure;
    
    property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
  end;
  
  procedure TMyImplClass.P1;
  begin
    Writeln('P1 called!');
  end;
  
  procedure TMyImplClass.P2;
  begin
    Writeln('P2 called!');
  end;
  
  procedure TExternalClass.MyP1AlternativeProcedure;
  begin
    Writeln('MyP1AlternativeProcedure called!');
  end;

var
  LvMyExternalClass: TMyClass;
  LvMyInterface: IMyInterface;
begin
  LvMyExternalClass := TExternalClass.Create;
  LvMyExternalClass.FMyImplClass := TMyImplClass.Create;
  
  LvMyInterface := LvMyExternalClass;
  LvMyInterface.P1;  // calls TMyClass.MyP1AlternativeProcedure;
  LvMyInterface.P2;  // calls TImplClass.P2;
  Readln;
end;

You can remove the following lines from TExternalClass and it's still implementing the interface correctly and compilable!

    procedure IMyInterface.P1 = MyP1AlternativeProcedure;
    procedure MyP1AlternativeProcedure; // and its implementation!

Here is another console-type application sample to make it more clear:


program InterfaceDelegationDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  // Interface definition
  IFirstInterface = interface
    procedure FirstMethod;
  end;

  ISecondInterface = interface
    procedure SecondMethod;
  end;

  // Implementation of the first interface
  TFirstInterfaceImplementation = class(TInterfacedObject, IFirstInterface)
    procedure FirstMethod;
  end;

  // Implementation of the second interface using delegation
  TSecondInterfaceImplementation = class(TInterfacedObject, ISecondInterface)
  private
    FFirstInterfaceImpl: IFirstInterface;
  public
    constructor Create(AFirstInterfaceImpl: IFirstInterface);
    procedure SecondMethod;
  end;

{ TFirstInterfaceImplementation }

procedure TFirstInterfaceImplementation.FirstMethod;
begin
  Writeln('FirstMethod called');
end;

{ TSecondInterfaceImplementation }

constructor TSecondInterfaceImplementation.Create(AFirstInterfaceImpl: IFirstInterface);
begin
  FFirstInterfaceImpl := AFirstInterfaceImpl;
end;

procedure TSecondInterfaceImplementation.SecondMethod;
begin
  Writeln('SecondMethod called');
  FFirstInterfaceImpl.FirstMethod; // Delegating the method call to the first interface implementation
end;

var
  FirstInterfaceImpl: IFirstInterface;
  SecondInterfaceImpl: ISecondInterface;
begin
  FirstInterfaceImpl := TFirstInterfaceImplementation.Create;
  SecondInterfaceImpl := TSecondInterfaceImplementation.Create(FirstInterfaceImpl);

  SecondInterfaceImpl.SecondMethod;
  Readln;
end.


In this code, we have two interfaces, IFirstInterface and ISecondInterface. The TFirstInterfaceImplementation class implements the first interface, and the TSecondInterfaceImplementation class implements the second interface using delegation.

The TSecondInterfaceImplementation class takes an instance of IFirstInterface as a constructor parameter, which represents the first interface implementation. It stores this instance in the FFirstInterfaceImpl field.

When the SecondMethod of TSecondInterfaceImplementation is called, it first prints a message indicating that the method has been called. Then, it delegates the method call to the FirstMethod of the first interface implementation by calling FFirstInterfaceImpl.FirstMethod.

By using this delegation approach, you can separate the implementation of each interface and reuse existing implementations by passing them as parameters to other interface implementations.

Tuesday, July 11, 2023

Anonymouse methods in Delphi

In Delphi, the term "anonymous methods" refers to a programming feature that allows you to define and use inline functions without explicitly declaring them as separate named functions or procedures. Anonymous methods are also known as anonymous functions or lambda expressions.

Anonymous methods provide a convenient way to write encapsulated blocks of code that can be passed as parameters to other routines or assigned to variables. They are commonly used in event handling, multithreading, and callback scenarios.

Here's an example of how anonymous methods can be used in Delphi:


program AnonymousMethodsDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TMathOperation = reference to function(a, b: Integer): Integer;

function PerformMathOperation(a, b: Integer; mathOp: TMathOperation): Integer;
begin
  Result := mathOp(a, b);
end;

procedure ExecuteAnonymousMethodDemo;
var
  addResult: Integer;
  subtractResult: Integer;
begin
  // Example: Performing addition using an anonymous method
  addResult := PerformMathOperation(10, 5,
    function(a, b: Integer): Integer
    begin
      Result := a + b;
    end
  );

  // Example: Performing subtraction using an anonymous method
  subtractResult := PerformMathOperation(10, 5,
    function(a, b: Integer): Integer
    begin
      Result := a - b;
    end
  );

  WriteLn('Addition Result: ', addResult);
  WriteLn('Subtraction Result: ', subtractResult);
end;

begin
  try
    ExecuteAnonymousMethodDemo;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

In the above example, we define a TMathOperation type which represents an anonymous method that takes two integers as parameters and returns an integer. The PerformMathOperation function accepts two integers and a TMathOperation instance and executes the specified math operation.

The ExecuteAnonymousMethodDemo procedure demonstrates two examples of using anonymous methods. It performs addition and subtraction operations by passing the required logic as an anonymous method to the PerformMathOperation function.

When executed, the program outputs the results of the addition and subtraction operations using anonymous methods.

Note that Delphi doesn't have native support for anonymous functions, but it supports anonymous methods through the reference to syntax as shown in the code.

Monday, July 10, 2023

Garbage collector in Delphi?

The short answer is no!

But...

Before talking about garbage collection we need to talk about another term in programming languages like C that is called a smart pointer, so what is a smart pointer?

SMART POINTER

In software programming languages like C or Delphi, a smart pointer is a programming construct or class that acts as a wrapper around a regular pointer. It provides additional functionality and automates memory management to help prevent common issues such as memory leaks, dangling pointers, and accessing deallocated memory.

The primary purpose of a smart pointer is to ensure proper memory deallocation and ownership semantics. When you allocate memory dynamically using functions like malloc() in C or New() in Delphi, it's important to release the memory when it's no longer needed to avoid memory leaks. Smart pointers handle this automatically by deallocating the memory when the smart pointer goes out of scope or is explicitly released.

Smart pointers typically use reference counting or garbage collection techniques to manage memory. Reference counting keeps track of how many references point to a particular memory location, and when the reference count reaches zero, the memory is deallocated. Garbage collection involves periodically identifying and reclaiming memory that is no longer accessible.

There are different types of smart pointers available, such as unique_ptr, shared_ptr, and weak_ptr (in C++), or interfaces like IInterface and records (in Delphi).
Each type has its own set of features and ownership semantics, allowing programmers to choose the appropriate smart pointer based on their specific requirements.

By using smart pointers, programmers can reduce manual memory management errors and make their code more robust, reliable, and less prone to memory-related bugs.

Smart pointer implementation Sample


In Delphi, the IInterface serves as the base for implementing smart pointers. You can define your own custom interface that extends IInterface and includes additional methods specific to your smart pointer implementation.

Here's a simple example of a custom smart pointer implementation using reference counting:

type
  TMySmartPtr = class(TInterfacedObject)
  private
    FData: TObject;
  public
    constructor Create(AData: TObject);
    destructor Destroy; override;
    function GetData: TObject;
  end;

constructor TMySmartPtr.Create(AData: TObject);
begin
  inherited Create;
  FData := AData;
end;

destructor TMySmartPtr.Destroy;
begin
  FData.Free; // Release the owned object
  FData := nil;
  inherited Destroy;
end;

function TMySmartPtr.GetData: TObject;
begin
  Result := FData;
end;


In this example, TMySmartPtr is a custom smart pointer class that manages the lifetime of an owned object (FData). The object is freed when the smart pointer goes out of scope or is explicitly destroyed.

You can use this smart pointer as follows:

var
  MyPtr: TMySmartPtr;
begin
  MyPtr := TMySmartPtr.Create(TObject.Create);
  // Use the smart pointer and its underlying object
  // ...
  // no need to free the created Object!
  // Smart pointer goes out of scope, object is automatically freed
end;


In this code snippet, an instance of TMySmartPtr is created and assigned a newly created TObject. When MyPtr goes out of scope or is set to nil, the smart pointer's destructor is called, which in turn frees the owned object.

Delphi also provides interfaces like IInterfaceList and TContainedObject that offer more advanced smart pointer capabilities, such as managing ownership hierarchies or implementing weak references.

Remember that this is a simplified example, and in real-world scenarios, you may need to consider additional factors like thread safety, circular references, and other edge cases. It's always recommended to thoroughly test and review your smart pointer implementation to ensure correctness and efficiency.

Is this sample the best approach in this regard?
Probably NO!😆

So what? 

Here is my smart pointer repository that collected 7 different versions of smart pointers implementations, please have a look and find the best approach based on your taste!

Please have a look at real-world samples here: