Ich hatte genau den gleichen Gedanken in vor längerer Zeit auch [1]. Auch hier hatte ich die Idee das mit Attributen festzulegen.
Letzten Endes sind wir damit echt nicht glücklich geworden. Nicht nur hat das im eigentlichen Quelltext nichts zu suchen: Klasse X kümmert sich um eine bestimmte Aufgabe, gut ist. Welcher Benutzer zu welcher Uhrzeit bei welcher Mondkonstellation was aufrufen darf sind echt
cross-cutting concerns.
Vor allem schmälert das die Wiederverwendbarkeit - Vielleicht kannst du genau die gleiche Klasse in einer anderen Anwendung gebrauchen, aber entweder gibt es hier keins oder ein völlig anderes Rechtesystem.
Wir haben das so umgesetzt dass die ganze Rechte-Geschichte völlig simpel ist: Jede Aktion hat eine String-ID, und zu diesem String lässt sich nachschlagen welcher "Benutzerleven" das aufrufen darf. Darum kümmert sich derjenige der die Methode aufruft, in der Regel ist das ein Bestandteil der Oberfläche. Ganz vereinfacht:
Delphi-Quellcode:
Unit Permissions.IDs;
interface
const SPECIAL_JOB = '
SPECIAL_JOB';
specialJobButton.Enabled := rightsManager.canInvoke(currentUser, SPECIAL_JOB);
Möchtest du die Rechte-Validierung trotzdem direkt in die Klasse selbst packen hier ein paar Gedanken:
Namen der aktuellen Methode bestimmen
Die Sache hat sicher noch einen Haken, aber mit einem TVirtualMethodInterceptor aus System.Rtti könnte man das machen:
Delphi-Quellcode:
uses
System.SysUtils,
System.Rtti;
type
TMyObject = class
private var
interceptor: TVirtualMethodInterceptor;
private
procedure handleBefore(
Instance: TObject;
Method: TRttiMethod;
const Args: TArray<TValue>;
out DoInvoke: Boolean;
out Result: TValue
);
procedure handleAfter(
Instance: TObject;
Method: TRttiMethod;
const Args: TArray<TValue>;
var Result: TValue
);
protected var
currentMethodName: String;
public
constructor Create();
destructor Destroy(); override;
procedure IKnowMyName(); virtual;
procedure IDoNotKnowMyName();
end;
constructor TMyObject.Create();
begin
inherited Create();
interceptor := TVirtualMethodInterceptor.Create( ClassType() );
interceptor.OnBefore := handleBefore;
interceptor.OnAfter := handleAfter;
interceptor.Proxify(self);
end;
destructor TMyObject.Destroy();
begin
if Assigned(interceptor) then
begin
interceptor.Unproxify(self);
interceptor.Destroy();
end;
inherited Destroy();
end;
procedure TMyObject.handleBefore(
Instance: TObject;
Method: TRttiMethod;
const Args: TArray<TValue>;
out DoInvoke: Boolean;
out Result: TValue
);
begin
DoInvoke := True;
currentMethodName := Method.Name;
end;
procedure TMyObject.handleAfter(
Instance: TObject;
Method: TRttiMethod;
const Args: TArray<TValue>;
var Result: TValue
);
begin
currentMethodName := EmptyStr;
end;
procedure TMyObject.IKnowMyName();
begin
WriteLn( currentMethodName.QuotedString() );
end;
procedure TMyObject.IDoNotKnowMyName();
begin
WriteLn( currentMethodName.QuotedString() );
end;
Ebenfalls kannst du deine Methode mit dem
Exception werfen verwenden: Der TVirtualMethodInterceptor hat ein OnException-Event: Wenn deine spezielle
Exception geworfen wurde könnte er die schlucken und deine Methode tut einfach überhaupt nichts.
Analog hat das OnBefore-Event ja auch den Parameter
Method: TRttiMethod
. Es gibt
TRttiMethod.GetAttributes()
! Hier kannst du dir alle Attribute der aktuell aufgerufenen Methode anschauen. Damit kämst du auch ans Ziel.
Nur wie gesagt, ich persönlich würde es so nicht mehr machen. So etwas gehört nicht in die eigentliche Klasse, das "verschmutzt" nur den eigentlichen Code.
[1]
http://www.delphipraxis.net/175990-b...en-rechte.html