среда, 14 октября 2015 г.

#853. Access to private class members using records

Original in Russian: http://programmingmindstream.blogspot.ru/2015/09/1159-record.html

By no means always class members paradigm private/protected/public works as we would like it to work.

In fact, class may have “ordinary” users, “advanced” users and “experts”  .

Sometimes we want to provide each class of users with its "own level of access" to design class methods.

I devoted much thought to the ways how to do it.

Sure, interfaces may be used. I would not tell how for I believe you know it well.

But (!) interfaces are the overhead to AddRef/Release.

Sometimes we try to avoid this overhead.

We try to make something similar to interfaces that has no ARC. I dare say, "the protocols”.

Here are the links about the “protocols”:

Protocols vs interfaces. (in Russian)
"Makeshift" protocols. (in Russian)
Objective-C and Delphi.
Wide use of interfaces “in general” and InterlockedIncrement/InterlockedDecrement in particular…
(in Russian)

I don't know how "methods are called by name" in Objective-C, but that's how I would do it … (in Russian)

These are all “cows a in vacuum”.

How can we achieve it in practice?

I have been thinking it over and over again and came up with the following staff.

Nothing extraordinary. We simply make “facade" records that have access to the “object’s intestine”.

This is similar to Enumerators that are also implemented by records:

Generics, "mixins", interfaces and enumerators - just the code (in Russian)
With reference to my mate's words, "thoughts about syntax sugar" (in Russian)

Something as follows:

https://bitbucket.org/lulinalex/mindstream/src/b550da2431d733e50aab7b5bb3c4dcca7f3f68aa/Examples/Protocols/Protocols.dpr?at=B284&fileviewer=file-view-default

program Protocols;
 
{$APPTYPE CONSOLE}
 
{$R *.res}
 
uses
  System.SysUtils;
 
type
 TmyClass = class
  public
   // Here come the protocols for “advanced” user:
   type
    Advanced1 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForAdvancedUser1;
      procedure ForAdvancedUser2;
    end;//Advanced1
 
    Advanced2 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForAdvancedUser1;
    end;//Advanced2
 
    Advanced3 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForAdvancedUser2;
    end;//Advanced3
 
   // Here come the protocols for “experts”:
   type
    Expert1 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForExpertUser1;
      procedure ForExpertUser2;
    end;//Expert1
 
    Expert2 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForExpertUser1;
    end;//Expert2
 
    Expert3 = record
     private
      f_Provider : TmyClass;
     public
      constructor Create(aProvider: TmyClass);
      procedure ForExpertUser2;
    end;//Expert3
 
  private
   procedure ForAdvancedUser1;
   procedure ForAdvancedUser2;
 
   procedure ForExpertUser1;
   procedure ForExpertUser2;
  public
   procedure ForRegularUser1;
   procedure ForRegularUser2;
  public
   // Here come the methods to get the “protocols”
   function AsA1: Advanced1;
   function AsA2: Advanced2;
   function AsA3: Advanced3;
 
   function AsE1: Expert1;
   function AsE2: Expert2;
   function AsE3: Expert3;
 end;//TmyClass
 
// TmyClass.Advanced1
 
constructor TmyClass.Advanced1.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Advanced1.ForAdvancedUser1;
begin
 f_Provider.ForAdvancedUser1;
end;
 
procedure TmyClass.Advanced1.ForAdvancedUser2;
begin
 f_Provider.ForAdvancedUser2;
end;
 
// TmyClass.Expert1
 
constructor TmyClass.Expert1.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Expert1.ForExpertUser1;
begin
 f_Provider.ForExpertUser1;
end;
 
procedure TmyClass.Expert1.ForExpertUser2;
begin
 f_Provider.ForExpertUser2;
end;
 
// TmyClass.Expert2
 
constructor TmyClass.Expert2.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Expert2.ForExpertUser1;
begin
 f_Provider.ForExpertUser1;
end;
 
// TmyClass.Expert3
 
constructor TmyClass.Expert3.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Expert3.ForExpertUser2;
begin
 f_Provider.ForExpertUser2;
end;
 
// TmyClass.Advanced2
 
constructor TmyClass.Advanced2.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Advanced2.ForAdvancedUser1;
begin
 f_Provider.ForAdvancedUser1;
end;
 
// TmyClass.Advanced3
 
constructor TmyClass.Advanced3.Create(aProvider: TmyClass);
begin
 f_Provider := aProvider;
end;
 
procedure TmyClass.Advanced3.ForAdvancedUser2;
begin
 f_Provider.ForAdvancedUser2;
end;
 
// TmyClass
 
procedure TmyClass.ForAdvancedUser1;
begin
  WriteLn('ForAdvancedUser1');
end;
 
procedure TmyClass.ForAdvancedUser2;
begin
  WriteLn('ForAdvancedUser2');
end;
 
procedure TmyClass.ForExpertUser1;
begin
  WriteLn('ForExpertUser1');
end;
 
procedure TmyClass.ForExpertUser2;
begin
  WriteLn('ForExpertUser2');
end;
 
procedure TmyClass.ForRegularUser1;
begin
  WriteLn('ForRegularUser1');
end;
 
procedure TmyClass.ForRegularUser2;
begin
  WriteLn('ForRegularUser2');
end;
 
function TmyClass.AsA1: Advanced1;
begin
  Result := Advanced1.Create(Self);
end;
 
function TmyClass.AsA2: Advanced2;
begin
  Result := Advanced2.Create(Self);
end;
 
function TmyClass.AsA3: Advanced3;
begin
  Result := Advanced3.Create(Self);
end;
 
function TmyClass.AsE1: Expert1;
begin
  Result := Expert1.Create(Self);
end;
 
function TmyClass.AsE2: Expert2;
begin
  Result := Expert2.Create(Self);
end;
 
function TmyClass.AsE3: Expert3;
begin
  Result := Expert3.Create(Self);
end;
 
var
 l_C : TmyClass;
begin
  try
    l_C := TmyClass.Create;
    try
      l_C.ForRegularUser1;
      l_C.ForRegularUser2;
 
      l_C.AsA1.ForAdvancedUser1;
      l_C.AsA1.ForAdvancedUser2;
 
      l_C.AsA2.ForAdvancedUser1;
 
      l_C.AsA3.ForAdvancedUser2;
 
      l_C.AsE1.ForExpertUser1;
      l_C.AsE1.ForExpertUser2;
 
      l_C.AsE2.ForExpertUser1;
 
      l_C.AsE3.ForExpertUser2;
    finally
      FreeAndNil(l_C);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

That is all...

Of course, it is mundane and not wise to repeat it “for each class”.

However, it is useful in case of a “complex” class that has more than one "responsibility".

I do know about KISS and SRP.

(+) I do also know about RTTI and helpers.

(+)(+) Sure, I do know about God-object, too.




Комментариев нет:

Отправить комментарий