Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

## Расширение функционала классов в Delphi без изменения исходной иерархии: паттерны проектирования в действии

Delphi , Программа и Интерфейс , Интерфейс

Вопрос пользователя связан с расширением функционала классов в Delphi, не затрагивая при этом исходную иерархию классов. Пользователь хочет добавить новую функциональность, такую как сохранение объекта в текстовый формат, и сделать это в отдельном модуле, не изменяя исходные классы. В контексте обсуждения были предложены различные подходы, включая использование интерфейсов, классов-помощников, паттерна "Посетитель" и других.

Подтвержденный ответ

Исходя из предоставленного контекста, подтвержденный ответ заключается в использовании классов-помощников для расширения функционала классов без изменения их исходного кода. Пример использования классов-помощников показывает, как можно добавить новую функциональность, такую как сохранение в текстовый формат, не затрагивая при этом исходные классы.

Альтернативный ответ

В качестве альтернативного подхода можно рассмотреть использование паттерна "Делегирование интерфейса", который заключается в создании параллельной иерархии классов, наследующих от исходных классов и реализующих необходимые интерфейсы. Этот подход позволяет избежать множественного наследования, которое не поддерживается в Delphi.

Статья

Расширение функционала классов в Delphi без изменения исходной иерархии: паттерны проектирования в действии

Работая с объектно-ориентированным программированием в Delphi, разработчики часто сталкиваются с необходимостью расширения функционала существующих классов. В таких случаях важно соблюдать принцип открытости/закрытости (Open/Closed Principle), который гласит, что программные сущности должны быть открыты для расширения, но закрыты для модификации.

Использование классов-помощников

Один из способов расширения функционала классов без их модификации - использование классов-помощников. В Delphi класс-помощник представляет собой специальный класс, который расширяет функциональность существующего класса, не наследуя от него напрямую. Класс-помощник определяется с помощью ключевого слова class helper.

type
  TBaseClassHelper = class helper for TBaseClass
    function SaveToText: string;
  end;
type
  TDerivedClassHelper = class helper for TDerivedClass
    function SaveToText: string;
  end;

В реализации методов классов-помощников можно использовать ключевое слово inherited, чтобы вызвать методы базового класса:

function TDerivedClassHelper.SaveToText: string;
begin
  Result := inherited SaveToText;
  Result := Result + Self.Age + ' ' + Self.Address;
end;

Класс-помощник TBaseClassHelper можно использовать для реализации интерфейса ITextable, который будет использоваться для сохранения объекта в текст:

ITextable = interface
  function SaveToText: string;
end;

TBaseClass_Text = class(TBaseClass, ITextable)
  function SaveToText: string; override;
end;
function TBaseClass_Text.SaveToText: string;
begin
  Result := Self.ID + ' ' + Self.Name;
end;

Таким образом, можно расширять функционал для различных модулей без необходимости касания кода исходной иерархии классов.

Пример использования интерфейсов и классов-помощников
unit uClasses;
interface
type
  TBaseClass = class
    ID: Integer;
    Name: String;
  end;
  TDerivedClass = class(TBaseClass)
    Age: Integer;
    Address: String;
  end;
end
end.
unit uClasses_Text;
interface
uses uClasses;
type
  ITextable = interface
    function SaveToText: string;
  end;
  TBaseClassHelper = class helper for TBaseClass
    function SaveToText: string;
  end;
  TDerivedClassHelper = class helper for TDerivedClass
    function SaveToText: string;
  end;
end
end.
unit uClasses_Text.implementation;
interface
type
  TBaseClass_Text = TBaseClassHelper;
  TDerivedClass_Text = TDerivedClassHelper;
end
implementation
uses SysUtils;
type
  TBaseClassHelper = class helper for TBaseClass
  private
    function SaveToTextInternal: string;
  public
    function SaveToText: string; safecall;
  end;
  TDerivedClassHelper = class helper for TDerivedClass
  private
    function SaveToTextInternal: string;
  public
    function SaveToText: string; safecall;
  end;
  TBaseClass_Text = interface(ITextable)
    ['{796A845C-0349-4396-A62F-62C716891E0A}']
    function SaveToText: string;
  end;
  TDerivedClass_Text = interface(ITextable)
    ['{B64E595D-123D-49A0-AF53-8B13A09A61C9}']
    function SaveToText: string;
  end;
  TBaseClassWithText = class helper(TBaseClass, IBaseClassWithText)
  end;
  TDerivedClassWithText = class helper(TDerivedClass, IDerivedClassWithText)
  end;
  IBaseClassWithText = interface
  ['{9C35F2A5-91E4-4D59-A6C7-49619802969D}']
  function SaveToText: string;
  function SaveToTextInternal: string; virtual;
  end;
  IDerivedClassWithText = interface(IBaseClassWithText)
  ['{4915E253-0289-46C1-840D-4E845C67D11C}']
  function SaveToText: string; override;
  end;

var
  IBaseClassWithText_Active: Boolean;
  IDerivedClassWithText_Active: Boolean;

implementation

uses
  Classes, Typinfo;

procedure Register;
begin
  RegisterClassHelperTypeData(TBaseClassHelper, TBaseClass, TypeInfo(TBaseClass), nil, nil);
  RegisterClassHelperTypeData(TDerivedClassHelper, TDerivedClass, TypeInfo(TDerivedClass), nil, nil);
  IBaseClassWithText_Active := True;
  IDerivedClassWithText_Active := True;
end;

function TBaseClassHelper.SaveToTextInternal: string;
begin
  Result := Self.ID.ToString + ' ' + Self.Name;
end;

function TBaseClassHelper.SaveToText: string;
begin
  if not IBaseClassWithText_Active then
    Exit;
  Result := SaveToTextInternal;
end;

function TDerivedClassHelper.SaveToTextInternal: string;
begin
  Result := SaveToText(Self as IBaseClassWithText);
  Result := Result + ' ' + Self.Age.ToString + ' ' + Self.Address;
end;

function TDerivedClassHelper.SaveToText: string;
begin
  if not IDerivedClassWithText_Active then
    Exit;
  Result := SaveToTextInternal;
end;

function TBaseClassWithText.SaveToText: string;
begin
  Result := SaveToTextInternal(Self);
end;

function TDerivedClassWithText.SaveToText: string;
begin
  Result := SaveToTextInternal(Self);
end;

function TBaseClassWithText.SaveToTextInternal: string;
begin
  Result := SaveToText;
  Result := Result + ' {BaseClassWithText}';
end;

function TDerivedClassWithText.SaveToTextInternal: string; virtual;
begin
  Result := SaveToText;
  Result := Result + ' {DerivedClassWithText}';
end;

function IBaseClassWithText.SaveToText: string;
begin
  Result := SaveToTextInternal(Self);
end;

function IDerivedClassWithText.SaveToText: string; override;
begin
  Result := SaveToTextInternal(Self);
end;
{ TBaseClassWithText_SaveToTextInternal }
function TBaseClassWithText_SaveToTextInternal: string;
begin
  Result := '';
  if ITextable(Self) then
    Result := Self.SaveToText;
end;

function TDerivedClassWithText_SaveToTextInternal: string; virtual;
begin
  Result := '';
  if ITextable(Self) then
    Result := Self.SaveToText;
end;

function TBaseClassHelper.SaveToText: string; safecall;
begin
  Result := TBaseClassWithText_SaveToTextInternal(Self);
  if Result = '' then
    Result := SaveToTextInternal;
end;

function TDerivedClassHelper.SaveToText: string; safecall;
begin
  Result := TDerivedClassWithText_SaveToTextInternal(Self);
  if Result = '' then
    Result := SaveToTextInternal;
end;

function ITextable(Sender: TObject): Boolean;
var
  PT: PTypeInfo;
begin
  Result := False;
  PT := GetTypeData(TypeInfo(Sender));
  Result := (PT <> nil) and (PT^^.TypeDataKind = tkcInterface) and
    (PT^^.TypeDataIntf = @TypeInfo(ITextable));
end;

{ RegisterClassHelperTypeData }
procedure RegisterClassHelperTypeData(HelperType, BaseType: TClass; BaseTypeInfo: PTypeInfo; HelperTypeInfo, HelperTypeData: PTypeInfo);
var
  PT: PTypeInfo;
begin
  PT := HelperTypeInfo;
  if PT <> nil then
    HelperTypeData := PT^.TypeDataPtr;
  HelperTypeData^^.BaseType := GetTypeData(BaseTypeInfo);
  HelperTypeData^^.BaseTypeInfo := BaseTypeInfo;
  HelperTypeData^^.TypeDataKind := tkcClassHelper;
  HelperTypeData^^.TypeDataIntf := nil;
  HelperTypeData^^.TypeDataClass := HelperType;
  if (HelperTypeInfo <> nil) and (HelperTypeInfo^.TypeDataIntf <> nil) then
    HelperTypeData^.TypeDataIntf := HelperTypeInfo^.TypeDataIntf;
end;

uses
  System.SysUtils, System.Classes, Vcl.Controls, Vcl.Graphics, System.SysInitUtils;

function GetTypeData(const TypeInfo: PTypeInfo): Pointer;
var
  T: PTypeInfo;
begin
  Result := nil;
  if TypeInfo = nil then
    Exit;
  T := TypeInfo;
  repeat
    if T^.TypeDataPtr <> nil then
      Exit;
    T := T^.TypeDataParent;
  until T = nil;
  if T^.TypeDataPtr = nil then
  begin
    SetLength(T^.TypeDataPtr, SizeOf(TTypeDataRec));
    Result := @T^.TypeDataPtr[0];
  end;
end;

init
Register;

end.

Внимание! Приведенный пример кода является упрощенной демонстрацией и не включает полную реализацию класса-помощника, которая включает сложную систему регистрации и инициализации класса-помощника.

Расширение функционала с сохранением полиморфизма

Разработчикам, которые не хотят отказываться от изменения интерфейсов своих базовых классов, можно обратить внимание на более сложные паттерны проектирования, такие как паттерн "Посетитель" или паттерны, основанные на композиции и делегировании. В таком случае, можно использовать специализированные классы и структуры, которые обрабатывают визиты по объектам различных типов, что позволяет реализовать сложные операции сохранения, не затрагивая при этом структуру основных классов.

Эти подходы, с одной стороны, требуют большего количества кода и могут показаться излишне сложными для простых задач, но они позволяют соблюсти инкапсуляцию и полиморфизм в более сложных системах, где иерархии глубокие и разнообразные.

Заключение

Расширение функционала классов без изменения исходной иерархии в Delphi - задача не из легких, но с использованием современных паттернов проектирования и технологий, таких как классы-помощники, интерфейсы и паттерны композиции и делегирования, можно реализовать гибкие и масштабируемые решения, которые будут отвечать требованиям разработчика и не нарушать существующую структуру программного обеспечения.

Создано по материалам из источника по ссылке.

Пользователь хочет добавить новую функциональность в классы Delphi, такую как сохранение объекта в текстовый формат, не изменяя исходную иерархию классов, и желает сделать это в отдельном модуле. В обсуждении рассматривались различные методы, включая исп


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: Интерфейс ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-02-05 15:03:57/0.0043289661407471/0