![]() |
Property für Anwender READONLY, intern nicht
Hallo zusammen,
ich habe gerade ein Problem bezüglich meiner selbst entwickelten Komponente. Die wichtigsten Klassen-Eigenschaften und -Methoden findet ihr anschließend. Zusammengefasst handelt es sich um einen TCP-Server mit einer Userliste. Der TCP-Server (Komponente) muss INTERN einige Dinge in der User-Klasse zuweisen (z.B. LastLogin). Auf der anderen Seite soll der Benutzer der Komponente "von außen" auf die UserListe zugreifen können. Hier soll jedoch eine Veränderung der Eigenschaften NICHT möglich sein. Um beim genannten Beispiel zu bleiben soll es also von außen NICHT möglich sein, die Property LastLogin zu schreiben sondern ausschließlich zu lesen. Meine Lösungsidee: Ich erzeuge im Getter eine "ReadOnly"-Version der entsprechenden TExtCientInfo und gebe diese dann über den Getter zurück. Dazu würde ich eine neue private Variable im Server (z.B. FReadOnlyClientInfo) erstellen und diese im Getter entsprechend bestücken und zurückgeben. Diesbezüglich wäre es vielleicht interessant zu wissen, dass auf diese Variable immer nur EIN Lesezugriff gleichzeitig erfolgen wird - mehrere Threads kommen sich dabei definitv nicht in die Quere. Ist dieser Ansatz korrekt? Anbei ein Auszug aus dem Klassendesign. Sollte noch was fehlen werde ich das natürlich nachreichen. TCP-Server:
Delphi-Quellcode:
Auszug aus TExtClientInfo
type TMyTCPServer = class(TComponent)
private function FGetClient(Idx : Integer) : TExtClientInfo; // Hier müsste eine ReadOnly-Version von TExtClientInfo zurückgegeben werden! [...] public [...] property Clients[Idx : Integer] : TExtClientInfo read FGetClient; [...] end; [...] function TMyTCPServer.FGetclient(Idx : Integer) : TExtClientInfo; begin Result := FClientList[Idx]; end;
Delphi-Quellcode:
Besten Dank für Eure Mühe mir zu helfen,
type TExtClientInfo = class(TBasicClientInfo)
private [...] public [...] // Die Basisinfos (ClientName, ID etc. finden sich in der Basisklasse) property Active : Boolean read FActive; // Die Properties müssen nach außen hin read-only sein... property LastPing : TDateTime read FLastPing write FSetLastPing; property LastLogin : TDateTime read FLastLogin write FSetLastLogin; property GUID : String read FGUID write FSetGUID; property LastActivity : TDateTime read FLastActivity write FSetLastActivity; [...] end; |
AW: Property für Anwender READONLY, intern nicht
Ich weiß nicht ob ich dich richtig verstanden habe, aber lass doch einfach den Setter
Delphi-Quellcode:
weg. Dann ist die Property ReadOnly.
write FSetLastLogin;
|
AW: Property für Anwender READONLY, intern nicht
Zitat:
Delphi-Quellcode:
Property hat. Dann kann man zwar das Ersetzen der Instanz verhindern, wenn man
TList<T>
Delphi-Quellcode:
nicht angibt, aber ein Aufruf von z.B.
write
Delphi-Quellcode:
ist dennoch möglich.
List.Delete(i)
@TE: Soweit ich weiß gibt es da in Delphi keine "saubere" Methode. Ein read-only Proxy Objekt wäre wohl auch mein Ansatz; auch wenn es unnötiger Overhead ist. Am elegantesten wäre wohl noch ein Interface, was du einmal von der "richtigen" Klasse und einmal vom Proxy implementierst. |
AW: Property für Anwender READONLY, intern nicht
Könntest du mit ReadOnly Interfaces arbeiten ?
|
AW: Property für Anwender READONLY, intern nicht
Zitat:
|
AW: Property für Anwender READONLY, intern nicht
Vielen Dank für eure Antworten. Die Sache mit dem Interface hört sich interessant an. Ich habe mich schon etwas mit Interfaces beschäftigt und verstehe sämtliche Turorials und kann sie auch entsprechend umsetzen.
Könnt ihr mir dennoch mit paar Zeilen (Pseudo)code zeigen wir man das umsetzt? Das ist mir momentan nicht ganz klar... ich meinte insbesondere die Trennung von ReadOnly und beschreibbar. Besten Dank bisher!!! |
AW: Property für Anwender READONLY, intern nicht
Zitat:
|
AW: Property für Anwender READONLY, intern nicht
Das geht nur wenn deine List-Klasse in der selben Unit überschrieben wird und deren Methoden wie Delete() umgebogen werden. Dann kannst du dort auf Protected- und Private-Deklarationen der List-Klasse auch aus der TCP-Klasse aus zugreifen. Außerhalb dieser Unit aber nicht.
Besser wäre es wohl aber, deiner Klasse eine indizierte Getter-Property zu verpassen. Allgemein halte ich das Konzept aber für irrig. Denn meiner Erfahrung nach kann man sich auf den Kopf stellen, wenn der Anwender etwas nicht darf sucht er sich seine Wege oder sucht sich eine andere Lösung. Fange Fehlersituationen durch ungeplante externe Löschungen intern durch Exceptions und/oder Events ab. |
AW: Property für Anwender READONLY, intern nicht
Ich meine das ungefähr so, nur über Interface zugreifen, und das ReadOnly-Interface bietet dann gar keine Schreibmethoden an.
Delphi-Quellcode:
Du kannst auch mal in den Spring4D Sourcen schauen, da gibt es auch ein paar schöne Beispiele zu ReadOnly Interfaces, z.B. ReadOnlyList.
ITcpServer = interface
// Full access .... ITcpServerReadOnly = interface // Restricted access .... TTcpServer = class(TTcpServerBase, ITcpServer, ITcpServerReadOnly) private ... Rollo |
AW: Property für Anwender READONLY, intern nicht
Man kann ein Interface nehmen, muss es aber in diesem Fall doch gar nicht.
Delphi-Quellcode:
oder hier als ausführliches Beispiel
TFoo = class
private FName: atring; protected function GetName: string; virtual; procedure SetName( const Value: string ); virtual; public property Name: string read GetName write SetName; end; TReadOnlyFoo = class(TFoo) private FFoo: TFoo; protected function GetName: string; override; procedure SetName( const Value: string ); override; public constructor Create( const AFoo: TFoo ); end; function TReadOnlyFoo.GetName: string; begin Result := FFoo.Name; end; procedure TReadOnlyFoo.SetName( const Value: string ); begin raise EInvalidOperation.Create( 'Readonly' ); end;
Delphi-Quellcode:
program ReadOnlyClassProp;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, ReadOnlyClassProp.Types in 'ReadOnlyClassProp.Types.pas'; procedure TestRun; var b: TBar; begin b := TBar.Create( ); try b.ExecuteAction( ); WriteLn( b.Foo.SomeValue ); try b.Foo.SomeValue := 'Test the readonly Setter'; except on E: EInvalidOperation do; // eat the expected exception end; WriteLn( b.Foo.SomeValue ); finally b.Free; end; end; begin try TestRun; except on E: Exception do WriteLn( E.ClassName, ': ', E.Message ); end; ReadLn; end.
Delphi-Quellcode:
Aber threadsafe ist das so noch nicht, das müsste man in
unit ReadOnlyClassProp.Types;
interface uses System.Classes, System.SysUtils; type TFoo = class strict private FSomeValue: string; strict protected function GetIsReadOnly: Boolean; virtual; function GetSomeValue: string; virtual; procedure SetSomeValue( const Value: string ); virtual; public property IsReadOnly: Boolean read GetIsReadOnly; property SomeValue: string read GetSomeValue write SetSomeValue; end; TReadOnlyFoo = class( TFoo ) strict private FFoo: TFoo; strict protected function GetIsReadOnly: Boolean; override; function GetSomeValue: string; override; procedure SetSomeValue( const Value: string ); override; public constructor Create( const AFoo: TFoo ); end; TBar = class private FFoo: TFoo; FInternalFoo: TFoo; procedure SetInternalFoo( const Value: TFoo ); protected property InternalFoo: TFoo read FInternalFoo write SetInternalFoo; public constructor Create; destructor Destroy; override; property Foo: TFoo read FFoo; procedure ExecuteAction( ); end; implementation { TFoo } function TFoo.GetIsReadOnly: Boolean; begin Result := False; end; function TFoo.GetSomeValue: string; begin Result := FSomeValue; end; procedure TFoo.SetSomeValue( const Value: string ); begin FSomeValue := Value; end; { TReadOnlyFoo } constructor TReadOnlyFoo.Create( const AFoo: TFoo ); begin inherited Create( ); if not Assigned( AFoo ) then raise EArgumentNilException.Create( 'AFoo' ); FFoo := AFoo; end; function TReadOnlyFoo.GetIsReadOnly: Boolean; begin Result := True; end; function TReadOnlyFoo.GetSomeValue: string; begin Result := FFoo.SomeValue; end; procedure TReadOnlyFoo.SetSomeValue( const Value: string ); begin raise EInvalidOperation.Create( 'Readonly' ); end; { TBar } constructor TBar.Create; begin inherited; InternalFoo := TFoo.Create; end; destructor TBar.Destroy; begin InternalFoo := nil; inherited; end; procedure TBar.ExecuteAction; begin InternalFoo.SomeValue := DateTimeToStr( Now( ) ); end; procedure TBar.SetInternalFoo( const Value: TFoo ); begin FreeAndNil( FInternalFoo ); FreeAndNil( FFoo ); if Value <> nil then begin FInternalFoo := Value; FFoo := TReadOnlyFoo.Create( Value ); end; end; end.
Delphi-Quellcode:
dann noch einweben.
TBar
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:55 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz