Как я могу вызвать защищенные методы с помощью помощника класса?
Предположим, у нас есть классы с методом, который потенциально очень полезен, но недоступен из-за защищенной области:
unit Sealed;
interface
type
TGeneral = class(TObject)
{ this method is useful, but not available }
protected procedure Useful; virtual;
end;
TSpecific1 = class(TGeneral)
{ some descendants override `Useful` method }
protected procedure Useful; override;
end;
TSpecific2 = class(TGeneral)
{ and some dont, but inherit `Useful`ness from the parent }
end;
Я знаю два старых способа достижения такого метода, оба связаны с наследованием и типизацией. Оба подхода должны работать одинаково с базовым вариантом № 1 и расширенным полиморфным вариантом № 2.
program CallingSite;
uses Sealed;
function GetInstance: TGeneral;
begin
{ !PSEUDO! makes compiler happy about the rest of code }
// depending on use case supposed to return an instance of `TGeneral`
// or any of its descendants - `TSpecific1`, `TSpecific2`
end;
type
{ this makes a current module a "friend" for `TGeneral` }
TFriend = class(TGeneral)
end;
procedure Case1;
var
{ holds an instance of `TGeneral` }
General: TGeneral;
begin
General := GetInstance;
{ protected method is available for "friend" via static cast }
TFriend(General).Useful; // compiles!
end;
type
TIntroducer = class(TGeneral)
{ this "reintroduces" `Useful` method to public scope }
public procedure Useful; override;
// this approach ought to work even with strict protected methods
// !!! but I THINK it is UNSAFE to use on virtual and/or dynamic methods
end;
procedure TIntroducer.Useful;
begin
{ and calls `Useful` via wrapper }
inherited;
end;
procedure Case2;
var
{ polymorphic instance of any `TGeneral`'s descendant }
Specific: TGeneral;
begin
Specific := GetInstance;
{ protected method is callable via public wrapper, static cast again }
TIntroducer(Specific).Useful; // compiles!
end;
Я хотел бы знать:
- Как достичь того же результата, используя силу помощников класса?
- Можно ли также вызвать закрытый метод с помощником класса?
- будут ли какие-либо различия между случаем № 1 и случаем № 2 из-за того, что вспомогательный класс расширяет область действия класса, а не внутреннее представление?
- Как вызвать оригинальный метод из вновь введенного в помощник класса без риска рекурсии?
Также, пожалуйста, прокомментируйте замечание о TIntroducer
небезопасность.
1 ответ
Вы можете использовать помощника так:
unit Unit2;
interface
type
TGeneral = class(TObject)
protected procedure Useful; virtual;
end;
TSpecific2 = class(TGeneral)
end;
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
end;
implementation
procedure TGeneral.Useful;
begin
WriteLn('general');
end;
procedure TSpecificHelper.ExposedUseful;
begin
Useful;
end;
end.
Они могут даже быть объявлены в отдельных единицах, например:
unit Unit2;
interface
type
TGeneral = class(TObject)
protected procedure Useful; virtual;
end;
implementation
procedure TGeneral.Useful;
begin
WriteLn('general');
end;
end.
и отдельно
unit Unit3;
interface
uses
Unit2;
type
TSpecific2 = class(TGeneral)
end;
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
end;
implementation
procedure TSpecificHelper.ExposedUseful;
begin
Useful;
end;
end.
И чтобы проверить:
program Project1;
{$APPTYPE CONSOLE}
uses
//Unit2, // either or
Unit3;
var
foo : TSpecific2;
begin
foo := TSpecific2.Create;
foo.ExposedUseful;
Readln;
end.
Закрытые члены могут быть представлены аналогичным образом, если вместо этого вы сделаете помощника для базового класса. Однако, если в другом отряде, приведение требуется. Например:
// in Unit2
TGeneral = class(TObject)
private
procedure AlsoUseful;
protected
procedure Useful; virtual;
end;
//in Unit3
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
procedure ExposedAlsoUseful;
end;
// ...
implementation
procedure TSpecificHelper.ExposedAlsoUseful;
begin
TGeneral(self).AlsoUseful;
end;
Что касается полиморфизма, вы можете просто проверить это сами. Помощник будет применяться к любому потомковому классу, из которого ваш экземпляр получен:
TSpecific1 = class(TGeneral)
protected
procedure Useful; override;
end;
// ...
procedure TSpecific1.Useful;
begin
WriteLn('specific 1');
end;
где
TSpecific2 = class(TSpecific1)
end;
Будет производить выход specific 1
когда вызывается с помощником для базового класса выше.
Заметка
Начиная с Delphi 10.1 Berlin, помощники класса больше не могут получить доступ к строго защищенным, строгим частным или частным членам. Эта "особенность" на самом деле была ошибкой компилятора, которую Embarcadero теперь исправила в Берлине.
Доступ к простым защищенным членам с помощниками остается возможным.