5

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.

Domus
  • 1,263
  • 7
  • 23
  • Bad question? Downvoter care to elaborate? – Domus Dec 05 '17 at 14:19
  • Nothing stops you from accessing a protected member if you must, it's just a bit awkward in the latest Delphi versions. – LU RD Dec 05 '17 at 14:22
  • @LURD I don't want to access a protected member forcibly or with workarounds, just wanted to know if in this particular case, the compiler should allow it, or what negative impact it would have. – Domus Dec 05 '17 at 14:25
  • For good or bad, the compiler architects decided the rules. Your question is not a good fit here, since it invites speculation. – LU RD Dec 05 '17 at 14:31
  • This is a classic opinion based question and should be closed. If you want a discussion, ask on the Google+ delphi devs page. – David Heffernan Dec 05 '17 at 14:40
  • 6
    FWIW Stackoverflow is full of similar questions for other languages (like C++ and C# and they are not being closed for whatever reason) with incredibly good answers - valid question and possible to answer without being opinion based. – Stefan Glienke Dec 05 '17 at 14:55
  • 1
    @StefanGlienke I doubt that's true. But I can also see that there is a technical answer to be written here, which you have done, which renders the opinion based part of the question moot. – David Heffernan Dec 05 '17 at 15:17
  • 2
    You argue that the question is "primarily opinion-based" which I argued it is not but close it as "unclear what you're asking"? It is totally clear what he is asking. – Stefan Glienke Dec 05 '17 at 18:37
  • @stefan there are 5 close votes, you can't know which reason I personally selected. – David Heffernan Dec 05 '17 at 21:30
  • That's something I don't get. Three of those voters have nothing to do with Delphi but want clarification wrt the question? Will clarification give them knowledge into the subject matter? – Domus Dec 05 '17 at 21:58
  • @DavidHeffernan I saw the votes when it was 3 and all of them were "unclear what you're asking" making it the majority of required votes. – Stefan Glienke Dec 05 '17 at 22:22
  • @domus The close voters aren't looking for subject matter knowledge. They are voting to maintain the site. – David Heffernan Dec 05 '17 at 22:39
  • @stefan Personally I think that a case could be made for unclear, too broad or opinion based. – David Heffernan Dec 05 '17 at 22:43
  • 1
    @DavidHeffernan You mean like on this question: https://stackoverflow.com/q/46012021/587106 ? – Stefan Glienke Dec 05 '17 at 22:47
  • @Stefan I would agree – David Heffernan Dec 05 '17 at 22:49
  • @DavidHeffernan I appreciate that they are voting to maintain the site, but how do they judge the completeness/clarity of a question in a subject matter that is alien to them? – Domus Dec 05 '17 at 22:49
  • @Domus I'm not sure you can judge their subject matter, but even so I think it is perfectly possible to cast such votes without detailed knowledge of Delphi itself. You don't need to know Pascal syntax to understand the nature of this question. – David Heffernan Dec 05 '17 at 22:51
  • @Domus Delphi is not the only tag only the question. So people with no interest in Delphi could be drawn to your question based on other interests; and could quite understandably evaluate your question on its non-Delphi merits (with probably less than a quarter of your question being Delphi-specific; _it's not the only OO language to implement **protected** scope_). – Disillusioned Dec 06 '17 at 00:02
  • @CraigYoung So the lesson is to use as few tags as possible in order to not attract any suspicious individuals. *I'm kidding!* These Chinese++ and Javascript lads are of course welcome to cast their judgement on other-language topics. I'm off to molest some Logo rascals. (kidding again) – Domus Dec 06 '17 at 00:34
  • 3
    @Domus: you may be kidding, but that's at least 50% true. The tags define how broad an answer you're looking for. By tagging with [tag:class] [tag:compiler-construction] [tag:protected] you're opening the question to *how should any object oriented language be designed* (could be technical if you're looking for technical tradeoffs, but leans towards opinion) and therefore to the consequential *is this Delphi design choice a good one* (pure opinion). – torek Dec 06 '17 at 16:22
  • 1
    @torek - Personally, I would have expected the use of tags to narrow down the context of the question? More tags = more specific? – Lars Fosdal Dec 06 '17 at 21:44
  • @LarsFosdal: that's not an unreasonable theory, but in practice tags seem to get "or"ed rather than "and"ed: you get the eyeballs of anyone with an interest in any one of these things, rather than all of these things combined. – torek Dec 06 '17 at 22:15
  • @torek I always use them with an "and" interpretation. But you're right, humans don't function like compilers (to stay on-topic). – Domus Dec 06 '17 at 23:38

2 Answers2

10

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;
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • Thanks for your answer, Stefan. I don't want access to methods of other instances, though. I am specifically limiting the access to variables that are identical to Self *and* are used *inside* a descendant class' method. I probably didn't make myself clear, or I'm misreading your answer. I realize that what I'm trying to get at is only useful in method chaining. – Domus Dec 05 '17 at 15:01
  • @Domus, if it duck typing yoau are after, look [Duck typing in Delphi 2007?](https://stackoverflow.com/a/9501885/576719) – LU RD Dec 05 '17 at 15:12
  • @LURD Not exactly, but thanks for that reference! Had read about duck typing a decade or so ago, but it was a nice refresher! – Domus Dec 05 '17 at 15:16
  • @Domus As I wrote in my second paragraph - it would define an exception to OOP rules just for one specific value which the compiler could only find with static code analysis - i.e. finding out that DoSomething returns Self. It just sees the return type and applies standard OOP rules / static typing to it. – Stefan Glienke Dec 05 '17 at 16:11
  • @StefanGlienke I'll accept it as not possible for the compiler. Of course, I'm sure it *is* possible to do, just not worth the effort. Thanks. – Domus Dec 05 '17 at 16:15
  • Stefan says that it is possible, but argues that it is not worth the downsodes – David Heffernan Dec 05 '17 at 17:28
  • @StefanGlienke, I don't think I tested something similar before... But in that context, I believe you could also redeclare `TChild.DoSomething` as `Result := TParentAccess(inherited DoSomething)`, and do the same with `TParentAccess`. Unless my morning coffee isn't working yet, that would get rid of all the typecasting. – Ken Bourassa Dec 06 '17 at 14:26
  • @KenBourassa Nice, added that and another possibility that just came to my mind to the answer. – Stefan Glienke Dec 06 '17 at 15:21
  • @StefanGlienke, I edited to make it closer to what I had in mind. Feel free to revert if you don't like it... Just occured to me we could also remove `TChild.DoSomething`and just do `TChild = class(TParentAccess)` – Ken Bourassa Dec 06 '17 at 19:59
  • I did *not* want to a) expose the TParentAccess cracker class in the interface part of the unit or b) modify the inheritance chain. – Stefan Glienke Dec 06 '17 at 20:04
  • can still have a `TChild.TParentAccess.DoSomething` method. – Ken Bourassa Dec 06 '17 at 20:46
  • Left as an exercise for the reader - I would favor the helper anyway. Implement exactly one method, be done. – Stefan Glienke Dec 06 '17 at 20:57
4

Well, the child might be identical, but it might not.

Consider a slight expansion to your example.

TParent = class
protected
  function DoSomething: TParent;
end;

and unit 2

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

TChild2 = class(TParent)
public
  procedure DoAnotherThing;
  function DoSomething: Child2;
end;

implementation

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething
end;

procedure TChild2.DoAnotherThing;
var
  p : TParent;
begin
  p := DoSomething;
  p.DoSomething;
end;

Now the value returned by DoSomething certainly is a TParent. It might be a TChild or indeed a TChild2 or even something that you have very little knowledge of (other than that it is descended from TParent).

If the returned value is really a TChild, then certainly access is no problem - it is knows how to handle its own data type, but if not then it has no right to access the protected functions of the other object. If the returned value is a TChild you can do what you want, like this

procedure TChild.DoAnotherThing;
var
  p : TParent;
begin
  p := DoSomething;
  if p is TChild then (p as TChild).DoSomething;
end;
Dsm
  • 5,870
  • 20
  • 24