Несколько интерфейсов, функция Supports() и подсчет ссылок

Если я правильно понимаю, это нормально:

type
  IMyInterface = interface['{60E314E4-9FA9-4E29-A09A-01B91F2F27C7}']  
    procedure MyMethod;
  end;

type
  TMyIClass = class(TInterfacedObject, IMyInterface)
  public
    procedure MyMethod;  // Forget the implementations in this example
  end;

var
   lMyIClass: IMyInterface;
   lSupports: Boolean;
begin
   lMyIClass := TMyIClass.Create;
   lSupports := Supports(lMyIClass,IMyInterface);
   Memo1.Lines.Add('lMyIClass supports IMyInterface: ' + BoolToStr(lSupports,true));
   if lSupports then DoSomethingWith(lMyIClass);

Теперь у меня есть класс, реализующий несколько интерфейсов:

type
   IFirstInterface = interface['{4646BD44-FDBC-4E26-A497-D9E48F7EFCF9}']
     procedure SomeMethod1;
   end;

   ISecondInterface = interface['{B4473616-CF1F-4E88-9EAE-1AAF1B01A331}']
     procedure SomeMethod2;
   end;

   TMyClass = class(TInterfacedObject, IFirstInterface, ISecondInterface)
     procedure SomeMethod1;
     procedure SomeMethod2;
   end;

Я могу вызвать другой перегруженный Support(), возвращающий интерфейс, и что-то с ним сделать):

var
   MyClass1,MyClass2 : TMyClass;
   i1: IFirstInterface;
   i2: ISecondInterface;
   bSupports: Boolean;
begin
    Memo1.Clear;
    MyClass1 := TMyClass.Create;

    bSupports := Supports(MyClass1,IFirstInterface,i1);  
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass1 supports IFirstInterface');
        DoSomethingWith(i1);
    end
    else
        Memo1.Lines.Add('MyClass1 does not support IFirstInterface');

    bSupports := Supports(MyClass1,ISecondInterface,i2);    
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass1 supports ISecondInterface');
        DoSomethingElseWith(i2);
    end
    else
        Memo1.Lines.Add('MyClass1 does not support ISecondInterface');

    MyClass1 := nil;
    i1 := nil;
    i2 := nil;
    MyClass2 := TMyClass.Create;

    bSupports := Supports(MyClass2,IFirstInterface,i1);

    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass2 supports IFirstInterface');
        DoSomethingWith(i1);
    end
    else
        Memo1.Lines.Add('MyClass2 does not support IFirstInterface');

    bSupports := Supports(MyClass2,ISecondInterface,i2);
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass2 supports ISecondInterface');
        DoSomethingElseWith(i2);
    end
    else
        Memo1.Lines.Add('MyClass2 does not support ISecondInterface');

У меня есть три вопроса по этому поводу:

  1. MyClass1, MyClass2 теперь являются объектными типами, а не интерфейсными типами, как в простом примере. Это нормально?

  2. Должен ли я Free () или «ноль» MyClass1 или, может быть, даже оставить его в покое?

  3. После обработки 2. требуются ли два оператора ix:= nil в отношении счетчиков ссылок?


person Jan Doggen    schedule 09.08.2013    source источник
comment
(1) Да, (2) вы создали его и не использовали addRef; и т. д., так что да Free это, (3) Да, вы используете их как интерфейсные объекты. Теперь, если вы присвоили lMyClass1 интерфейсу, то не освобождайте его; вы просто положили его в землю пересчета   -  person Petesh    schedule 09.08.2013
comment
Все, что вы сделали, прекрасно... На что вы должны обратить внимание. DoSomethingWith(MyClass2 as IFirstInterface); Вы бы смешивали модели... Поскольку Ref подсчитывает ваш MyClass2 на 1, а затем, когда DoSomethingWith завершается, Ref отсчитывает обратно на 0... и если счетчик ref = 0, то объект free для вас.   -  person House of Dexter    schedule 09.08.2013
comment
следует читать завершено, Ref отсчитывает обратно на 1   -  person House of Dexter    schedule 09.08.2013
comment
Вещи, которые вы должны знать... Вам не нужно использовать Support, если вы знаете, что ваш объект поддерживает этот интерфейс. Вы можете просто написать i1 := MyClass2. Компилятор позаботится о том, чтобы интерфейс поддерживался вашим объектом.   -  person House of Dexter    schedule 09.08.2013
comment
Вы смешиваете ссылки на классы и ссылки на интерфейсы. Это большое нет-нет. Вам не нужно использовать Поддержки таким образом. Просто объявите все ваши переменные как интерфейсы, и компилятор сообщит вам, правильно ли вы разложили вещи или нет. Не усложняйте себе жизнь.   -  person Nick Hodges    schedule 09.08.2013
comment
@Nick Насколько мне известно на момент написания статьи, единственное, что я мог сделать в примере с несколькими интерфейсами, — это использовать объект ;-) Рад, что Роб указал в своем ответе, что я могу использовать локальную переменную IUnknown, потому что да, я хочу чтобы держать вещи отдельно! Спасибо.   -  person Jan Doggen    schedule 09.08.2013
comment
Этот пост превращается в учебник Что (не) делать с интерфейсами ;-)   -  person Jan Doggen    schedule 10.08.2013


Ответы (2)


Общий совет — никогда не смешивайте ссылки на объекты со ссылками на интерфейсы. Это означает, что если вам нужно создать экземпляр класса и использовать любой из его интерфейсов, лучше не ссылаться на него через объектно-ссылочный тип. Вы нарушили этот совет, изменив свои переменные на тип TMyClass вместо типа интерфейса. Вместо этого объявите их как переменные интерфейса; Я бы использовал IUnknown.

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

Если MyClass1 является ссылкой на объект, то вы не должны не вызывать для него Free после того, как вы присвоили его переменной интерфейса. Вот часть вашего кода, аннотированная счетчиком ссылок на объект:

MyClass1 := TMyClass.Create;  // initialized to 0

bSupports := Supports(MyClass1,IFirstInterface,i1); // incremented to 1
if bSupports then 
begin
    Memo1.Lines.Add('MyClass1 supports IFirstInterface');
    DoSomethingWith(i1);
end
else
    Memo1.Lines.Add('MyClass1 does not support IFirstInterface');

bSupports := Supports(MyClass1,ISecondInterface,i2); // incremented to 2
if bSupports then 
begin
    Memo1.Lines.Add('MyClass1 supports ISecondInterface');
    DoSomethingElseWith(i2);
end
else
    Memo1.Lines.Add('MyClass1 does not support ISecondInterface');

MyClass1 := nil; // still 2
i1 := nil; // decremented to 1
i2 := nil; // decremented to 0; the object gets destroyed

Если бы вы в любой момент вызвали MyClass1.Free, ваша программа рухнула бы. Самостоятельное освобождение объекта не изменит значения в i1 или i2, поэтому автоматически вставленный компилятором код подсчета ссылок все равно будет выполняться. Он попытается уменьшить количество ссылок на уже освобожденный объект, что явно нехорошо.

Но предположим, что вы дождались после очистки i1 и i2, как в этом коде:

i1 := nil;
i2 := nil;
MyClass1.Free;

Это все еще неправильно. Очистка переменных устанавливает счетчик ссылок на 0, поэтому объект уничтожается при присвоении i2; значение в MyClass1 недопустимо, поэтому вы также не должны вызывать Free для него.

Самое безопасное, что можно сделать после того, как вы присвоили ссылку на объект ссылке на интерфейс, — это немедленно очистить ссылку на объект. Тогда у вас не будет соблазна его использовать.

Обычно нет необходимости очищать переменную интерфейса. Он автоматически очищается в конце своего срока службы (что для локальных переменных происходит, когда они выходят за пределы области действия в конце функции). Кроме того, если вы вызовете Supports и передадите уже назначенную ссылку на интерфейс, он либо получит ссылку на интерфейс для нового объекта, либо будет очищен; он не будет продолжать удерживать свое предыдущее значение.

То есть, когда вы вызываете Supports(MyClass2,IFirstInterface,i1);, нет необходимости сначала очищать i1. Вызов Supports либо заполнит i1 ссылкой на IFirstInterface для объекта, на который ссылается MyClass2, либо сохранит nil в i1.

person Rob Kennedy    schedule 09.08.2013
comment
МойКласс1 := ноль; // еще 2 i1 := nil; // уменьшается до 1 i2 := nil; // уменьшается до 0; объект уничтожается MyClass1.Free; это нормально ... потому что он установил для объекта значение nil ... и Free проверяет, является ли Self нулем, прежде чем вызывать деструктор ... Я согласен с вами в принципе ... если вы хотите, чтобы интерфейсы придерживались интерфейса ... но это времена, когда вы хотите работать с объектом, и вы не хотите беспокоиться об очистке кода... поэтому вы можете создать объект... передать его интерфейсу... и затем знать, что он будет уничтожен когда ссылка исчезнет. - person House of Dexter; 10.08.2013
comment
Это правда, @House, но я никогда не предполагал, что первая строка (очистка MyClass1) будет сохранена в трех строках, которые я показываю в своем ответе. Вызов Free для MyClass1 после присвоения ему nil был бы бессмысленным в всех случаях, независимо от того, что происходит с i1 и i2, так зачем вообще думать об этом? Это ничего не добавляет к обсуждению. Если вы не хотите беспокоиться об очистке кода, вам следует удалить код очистки, потому что это только усугубит ситуацию. Когда объект принадлежит интерфейсным переменным, вообще не используйте для него методы управления объектами, такие как Free. - person Rob Kennedy; 10.08.2013
comment
Я согласен... но он ничего не делает, если его оставить... что важно... так это то, что как только вы получите интерфейс... вы больше не отвечаете за его освобождение... вопрос факт, как я показываю в моем примере кода... вы можете легко смешивать обе модели, если понимаете, что делаете... очевидно, разработчики ожидали, что модели смешаны, иначе зачем нам TObject.GetInterface. - person House of Dexter; 10.08.2013

В принципе... Я согласен со всем, что все говорят о смешивании интерфейсной и объектной моделей, и не смешивайте их...

Это простой способ навлечь на себя неприятности и заниматься ночным отслеживанием странных GPF...

Но... (всегда есть но)...

Вы можете сделать это ... если вы понимаете Gotcha's ...

  • Если вы создадите TInterfacedObject и передадите его в ссылку на переменную. Нужно ли вам освобождать его или нет, зависит от того, получите ли вы от него интерфейс.
  • Как только вы получаете от него интерфейс... начинается подсчет ссылок... и вы больше не отвечаете за его освобождение. Это верно для любого объекта, происходящего от TInterfacedObject.
  • «Как» увеличивает счетчик ссылок вашего интерфейса до тех пор, пока «как» находится в области действия. Это означает, что если у вас нет другой ссылки на интерфейс... ваш объект уходит до свидания, как только "как" выходит из области видимости.

Оформить заказ Button4Click и Button5Click...Button4Click делает это с помощью интерфейса...Button5Click делает это с помощью обоих.

  IReqBase = Interface(IInterface)
  ['{B71BD1C3-CE4C-438A-8090-DA6AACF0B3C4}']
    procedure FillWithTemplateData;
  end;

  IReqLogIn = Interface(IInterface)
  ['{133D2DFF-670C-4942-A81C-D18CBE825A93}']
    procedure SetupPassword(aUserName, aPassword: string);
  end;

  type
   TWebAct = (ttlogin,
              ttsignin);

  TForm59 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    CheckBox1: TCheckBox;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
  private
    { Private declarations }
    FReqList: Array of IReqBase;
    procedure CreateBaseList;
    procedure ClearBaseList;
  public
    { Public declarations }
  end;

  TJSONStructure = class(TInterfacedObject);

  TReqBaseClass = class of TReqBase;

  TReqBase = class(TJSONStructure, IReqBase)
  private
     token: Int64;
  protected
    class function ReqClass: TReqBaseClass; virtual; abstract;
  public
     Constructor Create; virtual;
     procedure FillWithTemplateData; virtual;
     class function ReqBase: IReqBase;
  end;

  TReqLogin = class(TReqBase, IReqLogIn)
  private
    Fusername,
    Fpassword: String;
    Fmodule  : Integer;
  protected
    class function ReqClass: TReqBaseClass; override;
  public
     Constructor Create; override;
     Destructor Destroy; override;
     procedure SetupPassword(aUserName, aPassword: string);
     procedure FillWithTemplateData; override;
  end;

  TReqSignIn = class(TReqBase)
  private
    Fusername,
    Fpassword: String;
    Fmodule  : Integer;
  protected
    class function ReqClass: TReqBaseClass; override;
  public
     Constructor Create; override;
     Destructor Destroy; override;
     procedure FillWithTemplateData; override;
  end;


var
  Form59: TForm59;

implementation

{$R *.dfm}

procedure TForm59.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;

  IReqBase(FReqList[integer(CheckBox1.Checked)]).FillWithTemplateData;
end;

procedure TForm59.Button2Click(Sender: TObject);
begin
  CreateBaseList;
end;

procedure TForm59.Button3Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    TReqSignIn.ReqBase.FillWithTemplateData
  else
    TReqLogin.ReqBase.FillWithTemplateData;
end;

procedure TForm59.Button4Click(Sender: TObject);
var
  a_IReqBase1: IReqBase;
  a_IReqBase2: IReqBase;
  a_IReqLogIn: IReqLogIn;
begin
  a_IReqBase1 := TReqSignIn.Create;
  a_IReqBase2 := TReqLogin.Create;

  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');

  a_IReqBase1.FillWithTemplateData;
  a_IReqBase2.FillWithTemplateData;
  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;

procedure TForm59.Button5Click(Sender: TObject);
var
  a_ReqBase1: TReqSignIn;
  a_ReqBase2: TReqLogin;
  a_IReqBase1: IReqBase;
  a_IReqBase2: IReqBase;
  a_IReqLogIn: IReqLogIn;
begin
  a_ReqBase1 := TReqSignIn.Create;
  a_ReqBase2 := TReqLogin.Create;

  a_IReqBase1 :=  a_ReqBase1;
  a_IReqBase2 :=  a_ReqBase2;

  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  //note we are working with the object...
  a_ReqBase2.SetupPassword('houseofdexter', 'dexter');

  a_IReqBase1.FillWithTemplateData;
  a_IReqBase2.FillWithTemplateData;
  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;

procedure TForm59.ClearBaseList;
begin
  SetLength(FReqList, 0);
end;

procedure TForm59.CreateBaseList;
begin
  if High(FReqList) = Ord(High(TWebAct)) +1 then
    ClearBaseList;

  SetLength(FReqList, Ord(High(TWebAct)) + 1 );
  FReqList[ord(ttlogin)] := TReqLogin.ReqBase;
  FReqList[ord(ttsignin)] := TReqSignIn.ReqBase;
end;

procedure TForm59.FormCreate(Sender: TObject);
begin
  CreateBaseList;
end;

procedure TForm59.FormDestroy(Sender: TObject);
begin
  ClearBaseList;
end;

{ TReqLogin }

constructor TReqLogin.Create;
begin
  inherited;
  FUserName := 'Rick';
  FPassword := 'Test';
  Fmodule := 100;
end;


destructor TReqLogin.Destroy;
begin
  Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
end;

procedure TReqLogin.FillWithTemplateData;
begin
  inherited;
  Form59.Memo1.Lines.Add(Fusername);
  Form59.Memo1.Lines.Add(FPassword);
  Form59.Memo1.Lines.Add(IntToStr(FModule));
end;

class function TReqLogin.ReqClass: TReqBaseClass;
begin
  Result := TReqLogin;
end;

procedure TReqLogin.SetupPassword(aUserName, aPassword: string);
begin
  Fusername := aUserName;
  Fpassword := aPassword;
end;

{ TReqBase }

constructor TReqBase.Create;
begin
  inherited;
  Form59.Memo1.Lines.Add('Created: ' +ClassName);
  Token := -1;
end;

procedure TReqBase.FillWithTemplateData;
begin
  Form59.Memo1.Lines.Add(Self.ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  Form59.Memo1.Lines.Add(IntToStr(Token));
end;

class function TReqBase.ReqBase: IReqBase;
begin
  Result := ReqClass.Create;
end;

{ TReqSignIn }

constructor TReqSignIn.Create;
begin
  inherited;
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  FUserName := 'Peterson';
  FPassword := 'TestPW';
  Fmodule := 101;
end;

destructor TReqSignIn.Destroy;
begin
  Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  inherited;
end;

procedure TReqSignIn.FillWithTemplateData;
begin
  inherited;
  Form59.Memo1.Lines.Add(Fusername);
  Form59.Memo1.Lines.Add(FPassword);
  Form59.Memo1.Lines.Add(IntToStr(FModule));
end;

class function TReqSignIn.ReqClass: TReqBaseClass;
begin
  Result := TReqSignIn;
end;

end.
person House of Dexter    schedule 09.08.2013