Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should a descendant class' method's variable that is identical to Self, have access to its ancestor's protected methods?

This question arose from an issue that surfaced when using method chaining (fluent interface), and I suppose that's one of the only reasons it might be an issue at all.

To illustrate, I'll use an example using method chaining:

In unit A:

TParent = class
protected
  function DoSomething: TParent;
end;

In unit B:

TChild = class(TParent)
public
  procedure DoAnotherThing;
end;

implementation

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething
end;

I want to keep the DoSomething procedure protected and visible only to class descendants.

This won't compile, throwing a

cannot access protected symbol TParent.DoSomething

because DoSomething returns a TParent and the subsequent DoSomething call is issued from a TParent object in another unit (so the protection kicks in and the function is inaccessible). (thanks, David Heffernan, for the explanation)

To reduce it to its bare essence, something like TParent(Self).DoSomething is not possible inside the TChild class.

My question:

since the compiler does know that a copy of the Self parameter is being accessed from within the child class, would there be instances in which the ability to access the ancestor's protected methods breaks encapsulation? I'm only talking about dereferencing the typecasted Self from inside a descendant's class method. I'm aware that outside of this class, that parameter should not have access to the ancestor's protected methods (in another unit), of course.

Again, in short: when a variable, that is identical to the Self parameter, is dereferenced INSIDE one of its own class methods, would it be unsafe for the compiler to allow it access to its parent's protected methods (just like the Self parameter itself)?

It's a pretty theoretical question, but I'd be interested if it would have any negative impact on compiled code or encapsulation, if the compiler would allow this.

Thanks.

like image 462
Domus Avatar asked Dec 05 '17 14:12

Domus


1 Answers

protected means that you have access to those methods in your own instance. It does not mean that you have access to those methods on any other instances which are of a type you are deriving from.

The reason you can call DoSomething in TChild is because only Self has access to protected members of its ancestors.

The fact that in this particular case the result of the DoSomething method equals Self cannot be evaluated by the compiler (except by some static code analysis which I doubt any OOP language compiler out there does).

C++ solves this with being able to make TChild a friend class of TParent and thus giving it access to those methods.

In Delphi you get that feature if you put both classes into the same unit. If you want to keep both classes in their own units you can still use that feature by declaring a "cracker class". Just make a class that inherits from TParent and put that into the same unit as TChild. Then you can cast the result of DoSomething to that class and access the protected methods of TParent.

type
  TChild = class(TParent)
  public
    procedure DoAnotherThing;
  end;

implementation

type
  TParentAccess = class(TParent);

procedure TChild.DoAnotherThing;
begin
  TParentAccess(DoSomething).DoSomething;
end;

Update 6.12.2017:

You can also add a method to your TChild class to mimic the "friend status" (thanks Ken Bourassa).

type
  TChild = class(TParent)
  private
    type
      TParentAccess = class(TParent);
    function DoSomething: TParentAccess; inline;
  public
    procedure DoAnotherThing;
  end;

implementation

function TChild.DoSomething: TParentAccess;
begin
  Result := TParentAccess(inherited DoSomething);
end;

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething;
end;

A third possibility would be using a class helper to make the method accessible. This has the benefit of easy reusability as you just need to add the helper unit to any unit where you have a child of TParent and need access.

type
  TParentHelper = class helper for TParent
  public
    function DoSomething: TParent; inline;
  end;

implementation

function TParentHelper.DoSomething: TParent;
begin
  Result := inherited DoSomething;
end;
like image 85
Stefan Glienke Avatar answered Nov 07 '22 20:11

Stefan Glienke