3

I have read that interfaces are a good thing to decopule the code and Nick Hodges has written a good chapter on it. Reading that I have produced this code:

//interfaces
type
 ILocalization = interface
  ['{1D144BCE-7D79-4672-8FB5-235422F712EE}']
  function localize(const aWordId: string): string;
 end;

type
 IActions = interface
  ['{31E8B24F-0B17-41BC-A9E4-F93A8E7F6ECF}']
  procedure addWord(const aIndex, aWord: string);
  procedure removeWord(const aIndex: string);
 end;

//implementation
type
 TLocalization = class sealed (TInterfacedObject, ILocalization, IActions)
  private
   FTranslationList: TDictionary<string, string>;
  public
   constructor Create;
   destructor Destroy; override;
   //interface implementations
   function localize(const aWordId: string): string;
   procedure addWord(const aIndex, aWord: string);
   procedure removeWord(const aIndex: string);
 end;

I am planning to use this class to localize (translate) my delphi android apps.


When I am going to make an instance of the class I do the following.

var
 Italiano: TLocalization;
begin
 Italiano := TLocalization.Create;
end;

Since TLocalization should have inherited AddRef and Release I won't try finally this, but does this happen even if Italiano is a class type and not an interface type?

What I mean is this:

  • Italiano: TLocalization -> here I can use all the methods
  • Italiano: ILocalization -> here I can use only the localize function

Given the fact that (as I've already said) TLocalization inherits the AddRef and Release I don't have to free if I have understood correctly. It should be the same with ILocalization but does it have other benefits? I don't understand what are the differences of the 2 cases above

1 Answer 1

5
var
  Italiano: TLocalization;
begin
  Italiano := TLocalization.Create;
  // do stuff
end;

This opens you up to the pitfalls of mixing different lifetime models. As the code stands, AddRef has not been called, and so the reference count is 0. Consequently, you will leak this object.

So you might change the code to:

var
  Italiano: TLocalization;
begin
  Italiano := TLocalization.Create;
  try
    // do stuff
  finally
    Italiano.Free;
  end;
end;

Now you don't leak.

Great. But what happens if you do take a reference?

var
  Italiano: TLocalization;
  Localization: ILocalization;
begin
  Italiano := TLocalization.Create;
  try
    Localization := Italiano as ILocalization;
    // do stuff
  finally
    Italiano.Free;
  end;
end;

Now AddRef is called when you assign to Localization. So the reference count goes to 1. When Localization goes out of scope, Release is called and the reference count returns to 0 and the instance is destroyed. Unfortunately, you are also explicitly destroying it. Objects need to be destroyed exactly once.

The cleanest and simplest rule to follow is not to mix lifetime models. If you have lifetime management by reference counting, do that only. Make sure that you always take a reference to the object when you create it, and let the compiler generate reference counting code to manage the lifetime.

One way to make sure that you follow that rule is to ensure that you only ever access the object through interface references. Like this:

var
  Italiano: ILocalization;
begin
  Italiano := TLocalization.Create;
  // do stuff
end;
Sign up to request clarification or add additional context in comments.

7 Comments

Ok I guess that now I got it. So let's look at the last piece of code you have written. If the 3 methods addWord, removeWord and localize were all toghether inside ILocalization I could have used entirely my class. But so far I cannot because ILocalization has refCount only for the localize method and not the rest, so I can't call the other 2. Am I correct?
No. The object implements both interfaces, and the object owns the reference count, not the interface. So if you want to access the other methods, use var Actions: IActions; .... Actions := Italiano as IActions;. Now you have two interface variables, behind which is the same single object, which now has a reference count of 2.
Ok so I can use the class in the "normal way" with the try finally or I have to do a type cast to safely use IActions or ILocalizations.
If you use the class in the normal way, then you can't take an interface reference, because that leads to leaks. That is the entire point of the third excerpt above. But why would you need to do that? The simple rule, is that once you start managing lifetime by reference counting, you do it exclusively that way. Don't mix lifetime models.
I thought that interfaces were useful ONLY because I could extend the functionality of a class creating a loosely decoupled code. From what you are telling me I misunderstood how to implement them. Now I see why mixing the models is wrong
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.