Page 1 of 2 1 2 LastLast
Results 1 to 15 of 16

Thread: Interfaces, classes, propertys, getters en setters en het gebruik daarvan

  1. #1
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861

    Interfaces, classes, propertys, getters en setters en het gebruik daarvan

    Hi,

    Ik heb bijv. een interface met propertys. In de interface is het verplicht dan de getters en de setters te declareren.
    Code:
    type
      IMyObject = interface(IInterface)
        ['{E4411BCD-5212-40A0-81D4-D4A7027909FF}']
    
        function  GetName: string;
        function  GetWinCtrl: TWinControl;
        function  GetIsValid: boolean;
    
        procedure SetName( const AValue: string );
        procedure SetWinCtrl( const AValue: TWinControl );
        procedure SetIsValid( const AValue: boolean );
    
        property  Name: string read GetName write SetName;
        property  WinCtrl: TWinControl read GetWinCtrl write SetWinCtrl;
        property  IsValid: boolean read GetIsValid write SetIsValid;
      end;
    
    implementation
    
    end.
    De class met deze interface implementeert de getters en setters in het strict private of strict protected gedeelte:

    Code:
    type
      TMyObject = class( TInterfacedObject, IMyObject )
      strict protected
        FName: string;
        FWinCtrl: TWinControl;
        FIsValid: boolean;
      strict protected
        function GetName: string;
        function GetWinCtrl: TWinControl;
        function GetIsValid: boolean;
    
        procedure SetName( const AValue: string );
        procedure SetWinCtrl( const AValue: TWinControl );
        procedure SetIsValid( const AValue: boolean );
      public
        constructor Create( AwinControl: TWinControl );
        destructor  Destroy; override;
    
        property Name: string read GetName write SetName;
        property WinCtrl: TWinControl read GetWinCtrl write SetWinCtrl;
        property IsValid: boolean read GetIsValid write SetIsValid;
      end;
    Bij het gebruik van deze class definieer ik deze als interface i.v.m. reference counting.

    Code:
    procedure TFormTest.SetMyObjectIsValid( ADict: TDictionary<string, IMyObject>; AWinControl: TWinControl;  ABoolean: boolean );
    var
       LMyObj: IMyObject;     // als interface 
    begin
       LMyObj := TMyObject.Create( AWinControl );
    
       LMyObj.IsValid := ABoolean;
       ..
    end;
    Code completion bied de 3 property's aan + de 3 standaard functies inzake interfaces.

    Maar dit kan ook :

    Code:
    LMyObj.SetIsValid( ABoolean );
    Ik wil niet de fout maken dat deze laatste code gebruikt wordt. Hoe kan ik voorkomen dat de setters en getters direct benaderbaar zijn ? Dat komt natuurlijk door de scope in de interface, daar kun je die niet private, protected of public zetten. Hoe gaan jullie daar mee om ? Geen propertys in interfaces ?
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  2. #2
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Ik vermoed dat dat helaas niet mogelijk is.

  3. #3
    Definieer je interface(s) en class(es) in een aparte unit. Gebruik die unit in je unit met de test form.

  4. #4
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Had ik gedaan. Aparte unit voor de interface, aparte unit voor de class, aparte unit voor het form.

    Om in een Form
    Code:
    var
       LMyObj: IMyObject;     // als interface
    te kunnen declareren, is het noodzakelijk in desbetreffend Form bij de uses
    Code:
      , uIntTMyObject         // interface                                           
      , uClassTMyObject      // class
    in op te nemen, waardoor de Setters en Getters van de interface in het Form ter beschikking komen.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  5. #5
    Dat is niet mogelijk. Juist omdat interfaces verwijzen naar functionaliteit en niet naar concrete data, moet je altijd zichtbare getters en setters hebben. Alles in de interface is public, en data members (fields) maken of gebruiken is überhaupt niet mogelijk. Het is dus ook niet 'fout' om die methods te gebruiken.
    Sterker nog, het feit dat je properties kan definieren is VZIW ongebruikelijk, en is vooral een Delphi sausje over de Windows (COM) definitie van interfaces.

    Je zou er dus eventueel voor kunnen kiezen om überhaupt geen properties aan de interface toe te voegen. Je gebruikt dan altijd de getter of setter, en dat is dan dus in ieder geval consistent.

    Overigens is het wellicht verstandig om die properties ook niet toe te voegen aan je class. De interface properties verwijzen al naar de getters en setters, en de class hoeft alleen die methods te implementeren. Door public properties toe te voegen, werk je alleen maar in de hand dat je de class zowel via het class type als via de interface gaat benaderen, en dat is vragen om problemen.
    Last edited by GolezTrol; 11-Mar-20 at 12:15.
    1+1=b

  6. #6
    Als je LMyObj als IMyObject definieert dan zul je altijd de setters/getters zien. LMyObj altijd een TMyObject is, definiteer hem dan als TMyObject. Dan hoeft de Interface unit niet in te sluiten.

    Zelf stel ik voor:
    Interface voor IMyObject
    TMyObjectBase als implementatie voor IMyObject met abstracte implementaties voor de Interface methoden.

    En al je "werk" classes afleiden van TMyObjectBase. In je form unit heb je enkel je class unit nodig en je LMyObj definieren als TMyObjectBase.

    Zoals GolezTrol aangeeft, zie ik zelf ook niks "fouts" aan het direct aan kunnen roepen van de getters/setter. Maar als je dat perse niet wilt, dan denk ik dat mijn voorstel een alternatief is.
    Last edited by havezet; 11-Mar-20 at 12:23.

  7. #7
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Quote Originally Posted by GolezTrol View Post
    Overigens is het wellicht verstandig om die properties ook niet toe te voegen aan je class. De interface properties verwijzen al naar de getters en setters, en de class hoeft alleen die methods te implementeren. Door public properties toe te voegen, werk je alleen maar in de hand dat je de class zowel via het class type als via de interface gaat benaderen, en dat is vragen om problemen.
    Dat las ik ook in het boek Object Pascal Handbook van Marco Cantú, pag 320 Interface Properties.
    "When you implement an interface with a property, all you have to implement is the actual access methods, as the property is transparent and not available in the class itself."

    En toch snap ik het niet. Als ik nl. géén propertys in een interface declareer, maar wél in de class welke de interface meekrijgt, en creëer een instantie van de class als interface, dan heb ik niet de beschikking over de propertys van de class. Ik kan de propertys van de class alleen benaderen als ik een instantie maak type class, niet als type interface. Maar daarmee lijkt mij het nut van interfaces deels overbodig. In ieder geval de reference counting en dien je dus zelf je objecten op te ruimen.

    Als ik dus een property alléén in een class declareer en heb een instantie van het type interface van de class dan kan onderstaande code niet:

    Code:
    var
       LMyObj: IMyObject;     // als interface 
    begin
       LMyObj := TMyObject.Create( AWinControl );
    
       LMyObj.IsValid := ABoolean; <- property IsValid bestaat niet in interface wel in class dus hier niet benaderbaar omdat de instantie van de class als interface is gedeclareerd.
       ..
    end;
    Als ik Nick Hodges moet geloven zou je alles (zo veel mogelijk) met interfaces moeten bouwen. Anderen roepen weer dat getters en setters "evil" zijn.

    Ik zit dus in dubio: "wanneer volg ik de juiste methodiek" ?
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  8. #8
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Quote Originally Posted by havezet View Post
    Zelf stel ik voor:
    Interface voor IMyObject
    TMyObjectBase als implementatie voor IMyObject met abstracte implementaties voor de Interface methoden.

    En al je "werk" classes afleiden van TMyObjectBase. In je form unit heb je enkel je class unit nodig en je LMyObj definieren als TMyObjectBase.
    Oké, zal dat eens proberen. Vraag me af of die "werk" classes dan vanzelf opgeruimd worden.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  9. #9
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Ik gebruik hoogst zelden interfaces. Het hangt natuurlijk af van het systeem dat je bouwt. Als er een zeer hoge mate van abstractie (nodig) is kan het nuttig en krachtig zijn.
    In de meeste gevallen wordt code alleen maar onleesbaarder en langzamer. "Waar o waar wordt nou toch die interface geimplementeerd?" is altijd mijn eerste vraag wanneer ik code lees.
    (maar goed een interface ja/nee discussie hoeven we niet te starten).
    In jouw geval lijkt een abstract class ook zeer bruikbaar. Ben je meteen van dat gettersetter scope probleem af omdat je ze protected declareert.
    Het feit dat er een WinControl inzit maakt het meteen al meer een klasse dan een interface, denk ik.

  10. #10
    Quote Originally Posted by Hans Brenkman View Post
    En toch snap ik het niet. Als ik nl. géén propertys in een interface declareer, maar wél in de class welke de interface meekrijgt, en creëer een instantie van de class als interface, dan heb ik niet de beschikking over de propertys van de class.
    Klopt. Het zijn properties van de interface. Je class is verborgen achter de interface. Je weet als het goed is niet eens welk type class je hebt. Je _mag_ properties toevoegen in Interfaces, en die worden dan gewoon netjes doorgestuurd naar de getter en setter, maar zoals gezegd is dat alleen een sausje; het gaat nog steeds om die methods.

    Quote Originally Posted by Hans Brenkman View Post
    Als ik Nick Hodges moet geloven zou je alles (zo veel mogelijk) met interfaces moeten bouwen. Anderen roepen weer dat getters en setters "evil" zijn.
    Geen idee wie roept dat getters en setters evil zijn, maar volgens mij is die mening onjuist of op z'n zachtst gezegd verouderd.
    Ik zou het vooral omdraaien. Je wilt iets doen met het object (al dan niet via de interface), en dat kan prima via een expliciete setter zoals SetName. De vraag is dus of je per se properties nodig hebt om te bereiken wat je wilt. Om de transitie makkelijker te maken ondersteunt Delphi in ieder geval interface properties, maar dus alleen als aanvulling op, niet ter vervanging van de methods.

    En ja, het is de vraag of het nodig is om interfaces te gebruiken. Een voordeel is dat je je code testbaarder kunt maken, omdat je makkelijker externe afhankelijkheden kunt mocken. Dat kan ook met een afgeleide class, maar dat is gewoonlijk meer werk, en wordt er ook niet makkelijker op. Het gaat dan overigens vooral om functionaliteit. Een stukje data, iets wat praktisch een record zou kunnen zijn, hoef je niet in een interface te stoppen. Natuurlijk is er een hoop grijs gebied tussen. Of het in jouw geval handig is, valt moeilijk te zeggen op basis van die paar regels...

    En ja, interfaces zijn trager, scheelt een paar % dus bij een miljard aanroepen, zoals in onderstaande testcode, ben je zo 1/10 seconde extra kwijt. Natuurlijk ben je ook tijd kwijt met het maken van object instanties, methodcalls en meer van zulks. Als dat de afweging moet zijn, dan kan je maar beter alles lineair in je dpr schrijven, heel veel pointers en buffers gebruiken, en de laatste structuur vervangen door goto's.
    Ik denk dat het normaal gesproken geen issue zou moeten zijn, maar uiteraard kan het in sommige gevallen een afweging zijn.

    Delphi Code:
    1. type
    2.   IX = interface
    3.     ['{1192D2C5-A04D-47D6-8AF6-13A1521C5D90}']
    4.     procedure Inc(var x: Integer);
    5.   end;
    6.  
    7.   TX = class(TInterfacedObject, IX)
    8.     procedure Inc(var x: Integer);
    9.   end;
    10.  
    11. procedure TForm3.FormCreate(Sender: TObject);
    12. var
    13.   Stopwatch: TStopwatch;
    14.   i, j, x: Integer;
    15.   obj: TX;
    16.   intf: IX;
    17.   msg: String;
    18. begin
    19.   msg := '';
    20.   for j := 1 to 10 do
    21.   begin
    22.     obj := TX.Create;
    23.     Stopwatch := TStopwatch.StartNew;
    24.     x := 0;
    25.     for i := 0 to 100000000 do
    26.       obj.Inc(x);
    27.  
    28.     msg := Msg + 'Object: ' + Stopwatch.ElapsedMilliseconds.ToString + 'ms' + sLineBreak;
    29.  
    30.     Stopwatch := TStopwatch.StartNew;
    31.     intf := obj;
    32.     x := 0;
    33.     for i := 0 to 100000000 do
    34.       intf.Inc(x);
    35.  
    36.     msg := Msg + 'Interface: ' + Stopwatch.ElapsedMilliseconds.ToString + 'ms' + sLineBreak;
    37.   end;
    38.  
    39.   ShowMessage(msg);
    40. end;
    1+1=b

  11. #11
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Ha cool Golez! ik zal hem een keertje draaien :-)
    Inderdaad maar 1,5 maal zo langzaam. Dat valt mee. Had 3 methods gemaakt.
    Last edited by Anoniem; 11-Mar-20 at 20:21.

  12. #12
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Ik realiseer me nu ook dat als je een class maakt met meer dan één interface bijv.

    Code:
    type
       TBatMobiel = class(TInterfacedObject, ICanDrive, ICanFly)
    nooit een instantie van een class kunt maken die gedefinieerd is als beide interfaces dus moet je wel gewoon de instantie van de class als de class definiëren en het object zelf ook vrijgeven

    Code:
    var
      LMobiel: TBatMobiel
    begin
      LMobiel := TBatMobiel.Create;
      try
        ...
      finally
        LMobiel.Free;
      end;
    end;
    óf een interface maken die samengesteld is uit 2 interfaces zodat je de class TBatMobiel als class(TObject, IBatMobiel) kunt maken. In een dergelijke situatie ben je dus wel genoodzaakt propertys in de class te declareren en niet (alleen) in de interfaces, anders heb je die propertys niet beschikbaar. Volgens mij hebben interfaces dan weinig zin anders dan een blauwdruk voor je verplichte code. Ik blijf het moeilijk vinden de noodzaak van interfaces in te zien.

    Nick Hodges:

    http://www.nickhodges.com/page/Why-Y...eferences.aspx

    Why Do You Want To Use Interfaces?
    The what and the how are the easy part. It’s not tough to figure out how this all works. It’s the why that seems to be the sticking point for many – I know it was for me for a long time. So here’s the real meat of the article – Why in the heck would you want to use these crazy things?

    Well here’s why.
    Ultimately, there is one bottom line reason why you should use interfaces in Delphi: They provide a very thin – but very powerful -- abstraction to your code. That’s why. Everything below is really an expansion on that one idea.

    As I’ve said before, and I’ll say again: A good developer codes against abstractions, and not implementations. Interfaces are a great way to create abstractions. If you want a thorough discussion on why this is a good idea, I suggest reading Erich Gamma on the topic – but I’ll talk a bit about it here.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  13. #13
    Reader
    Join Date
    May 2002
    Location
    Holland
    Posts
    3,382
    Allemaal onzin :-)
    A good programmer is very concrete.

    En ik heb wat interface freak code gezien dit leven!
    Code:
    namespace Herbruikbaar.Nogwat.SchitterendeInterface;
    {
      public interface IExcelItem
      {
      }
    }
    staat dan in een los bestand. Lang leve de abstractie.

    In veel gevallen is de interface alleen maar gemaakt omdat iemand denkt dat je interfaces moet gebruiken.
    In jouw batmobiel voorbeeld zul je dan misschien gebruik moeten maken van "if Supports(ICanFly)" en zo. Je kunt nog steeds met interfaces werken.
    Interfaces kunnen natuurlijk superkrachtig zijn. Dat zal ik nooit ontkennen.

  14. #14
    Je zal misschien niet zo vaak beide interfaces van een class willen gebruiken, maar het kan wel, met de `as` typecast (en o.a. de Supports function).

    Bouwend op die twee eigenschappen kan je meerdere voertuigen maken die een of beide interfaced implementeren:

    Delphi Code:
    1. type
    2.   ICanDrive = interface
    3.     ['{BB724817-844E-4B63-997E-F4DAA99DB0E3}']
    4.     procedure Accellerate;
    5.   end;
    6.  
    7.   ICanFly = interface
    8.     ['{A25A8D0C-A65C-4CB5-9175-5FBED6A40C3A}']
    9.     procedure Rise;
    10.   end;
    11.  
    12.   TCar = class(TInterfacedObject, ICanDrive)
    13.     procedure Accellerate;
    14.   end;
    15.  
    16.   TChopper = class(TInterfacedObject, ICanFly)
    17.     procedure Rise;
    18.   end;
    19.  
    20.   TBatMobiel = class(TInterfacedObject, ICanDrive, ICanFly)
    21.     procedure Accellerate;
    22.     procedure Rise;
    23.   end;
    24.  
    25. { TCar }
    26.  
    27. procedure TCar.Accellerate;
    28. begin
    29.   ShowMessage('vroem, tuut');
    30. end;
    31.  
    32. { TChopper }
    33.  
    34. procedure TChopper.Rise;
    35. begin
    36.   ShowMessage('Flopflopflopflopflopflop');
    37. end;
    38.  
    39. { TBatMobiel }
    40.  
    41. procedure TBatMobiel.Accellerate;
    42. begin
    43.   ShowMessage('VROOOAAAAAAARRRRRRRR!')
    44. end;
    45.  
    46. procedure TBatMobiel.Rise;
    47. begin
    48.   ShowMessage('SWOOOOSH')
    49. end;

    In het gebruik kan je deze (o.a.) typecasten naar een andere interface:

    Delphi Code:
    1. procedure Drive(A: ICanDrive);
    2. begin
    3.   A.Accellerate;
    4. end;
    5.  
    6. procedure Fly(A: ICanFly);
    7. begin
    8.   A.Rise;
    9. end;
    10.  
    11. procedure TForm2.FormCreate(Sender: TObject);
    12. var
    13.   Drivable: ICanDrive;
    14. begin
    15.   Drivable := TBatMobiel.Create;
    16.  
    17.   Drive(Drivable);
    18.   Fly(Drivable as ICanFly);
    19.  
    20.   Drivable := TCar.Create;
    21.   Drive(Drivable);
    22.   Fly(Drivable as ICanFly); // Deze gaat fout. "Interface not supported", want TCar implementeert ICanFly niet.
    23. end;

    En elk van die interfaces draagt bij aan de refcount. Als ik dus een ICanFly variabele heb, en een ICanDrive, en beide wijzen naar dezelfde instantie, dan zal dat object een refcount van 2 hebben, en automatisch worden opgeruimd zodra de laatste referentie uit scope gaat of expliciet wordt overschreven.

    Deze opzet kan in sommige gevallen erg handig zijn, omdat je een wat grotere class kan maken, en de verschillende onderdelen met kleine, specifieke interfaces kan ontsluiten, zoda de code die er gebruik van maakt een kleine, duidelijke en specifieke interface heeft.

    Met alleen classes heb je die flexibiliteit gewoon niet. Met een abstracte baseclass kom je een heel eind, maar je hebt dan altijd alleen die ene 'interface' van de baseclass, en in de praktijk zie je dat daar te veel in zit en dat niet alle afgeleiden dat waar kunnen maken.
    Neem als voorbeeld TDataSet. Sommige datasets zijn unidirectioneel. DataSet.Prior aanroepen geeft dan een exception. Je zou ook een aparte IUnidirectional kunnen maken waar wel Next in zit, maar niet Prior. Een procedure die alleen rechtlijnig door de dataset loopt, kan dan vragen om een IUnidirectional en accepteert daarmee impliciet meer typen datasets dan een procedure die een IBidirectional wil.
    Last edited by GolezTrol; 11-Mar-20 at 23:43.
    1+1=b

  15. #15
    Senior Member Wok's Avatar
    Join Date
    Dec 2002
    Location
    Alkmaar
    Posts
    2,085
    Ik ben niet goed thuis in interfaces, maar als ik de code van Goleztrol. 1 op 1 overneem krijg ik:

    [dcc32 Error] Unit1.pas(25): E2065 Unsatisfied forward or external declaration: 'TX.Inc'

    met een rode balk op de procedure, waar gaat het mis?
    De procedure staat toch in de interface erboven.

    Peter
    10.4.2, Delphi2010, of Lazarus 2.2.0

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •