[OOP] Is casten te voorkomen (bij een framework)?

Pagina: 1
Acties:
  • 163 views sinds 30-01-2008
  • Reageer

Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Ik ben weer eens bezig met het uitbreiden van een framework dat ik aan het maken ben (een klein object-relational mapping framework). Nu stuit ik echter weer op wat vragen. Echt problemen zijn het niet, het lukt allemaal wel redelijk. Ik ben alleen benieuwd of mijn werkwijze nu wel goed is.

In een framework heb je vaak te maken met enkele abstracte classes of interfaces. Sommige functies zijn al geimplementeerd en sommige zijn puur abstract en moeten in concrete classes worden geimplementeerd. Als parameter van die functies zie je ook vaan abstracte types terug komen. Vaak is het dan ook zo er enkele operaties op die objecten moeten uitgevoerd worden die niet in de interface beschreven zijn. Dan moet het betreffende object dus eerst gecast worden naar het type wat de operatie(s) wel ondersteund. En dat is juist waar ik vragen bij heb. Is dit casten nu normaal of zijn er ook andere methodes om zoiets in elkaar te zetten?

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Vaak is het dan ook zo er enkele operaties op die objecten moeten uitgevoerd worden die niet in de interface beschreven zijn. Dan moet het betreffende object dus eerst gecast worden naar het type wat de operatie(s) wel ondersteund. En dat is juist waar ik vragen bij heb. Is dit casten nu normaal of zijn er ook andere methodes om zoiets in elkaar te zetten?
Ik geloof dat dit kan duiden op een slecht design....
Op die manier verlies je dan toch een stuk polymorphisme ?
Als je dit moet doen, wil dit toch zeggen dat je in die geimplementeerde abstracte functie iedere keer moet checken of het gekregen object nu van type X of Y of Z, ... is. Indien het geval is, dan roep je die bepaalde method op.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • momania
  • Registratie: Mei 2000
  • Laatst online: 22:26

momania

iPhone 30! Bam!

Bij de implementatie van een framework heb je idd soms wat casting nodig, maar hoeft niet altijd hoor. Soms kan het ook aan het design liggen dat misschien niet helemaal klopt of beter kan.
De 'gebruiker' van een framework, zou iig weer wel volledig met de aangeboden interfaces uit de voeten moeten kunnen, de implementatie zelf hoeft dat natuurlijk niet persee. :)

Kun je misschien wat begeleidende code posten om aan te geven waar je stuk loopt? :)

Neem je whisky mee, is het te weinig... *zucht*


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Ik geef wel even een stukje voorbeeld code in php (ik weet dat je daar niet hoeft te casten, maar het gaat om de princiepes):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class DatabaseMapper
{
    protected function selectCollection($query)
    {
        $recordSet = Database::query($query);
        $objects = array();
        while ( $record = $recordSet->fetch() )
        {
            $object = $this->createObject($record);
            $this->populateObject($record, $object);
            $objects[] = $object;
        }
        return $objects;
    }
    
    abstract function createObject(DatabaseRecord $record);
    abstract function populateObject(DatabaseRecord $record, PersistentObject $object);
}


Een concrete impelementatie van een DatabaseMapper moet in de functie populateObject de parameter $object nu dus casten naar het juiste type. Dit omdat de setter functies van dat type gebruikt moeten worden.

Het is ook niet dat ik de hele tijd aan het casten ben. Het gaat maar om een functie of 2/3. (aantal implementaties niet meegerekent). Wat ik wil weten is of hier een net alternatief voor is en hoe je zoiets implementeerd.

[ Voor 20% gewijzigd door Michali op 09-08-2005 17:25 ]

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Mja, in dit geval zou je mbhv reflection het object kunnen populaten. (Ik weet wel niet of zoiets mogelijk is in PHP).

Wat je ook kunt doen, is de populateObject een method laten zijn van je BusinessObjecten. Je business-objecten gaan zichzelf dan vullen met de gekregen recordset, maar dan zit je daar natuurlijk wel met een ongewenste afhankelijkheid.

[ Voor 50% gewijzigd door whoami op 09-08-2005 17:27 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
whoami schreef op dinsdag 09 augustus 2005 @ 17:24:
Mja, in dit geval zou je mbhv reflection het object kunnen populaten. (Ik weet wel niet of zoiets mogelijk is in PHP).
Het gaat hier even om het idee. Waar je dus een abstracte class hebt met een geimplementeerde functie en een abstracte functie die als parameter een abstract type vraagt. Je komt dan echt niet altijd weg met reflection en metadata.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Michali schreef op dinsdag 09 augustus 2005 @ 17:27:
[...]


Het gaat hier even om het idee. Waar je dus een abstracte class hebt met een geimplementeerde functie en een abstracte functie die als parameter een abstract type vraagt. Je komt dan echt niet altijd weg met reflection en metadata.
Huh, ik volg je even niet.


Echter, als ik het zo zie, dan maakt de createObject method jouw specifieke business-object, juist ?
Als dit het geval is, waarom vul je het BO niet in deze method ? Die method krijgt toch al de recordset mee.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21-09 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Michali schreef op dinsdag 09 augustus 2005 @ 17:22:
Ik geef wel even een stukje voorbeeld code in php (ik weet dat je daar niet hoeft te casten, maar het gaat om de princiepes):
Ah, dus net zoiets als een Cloneable interface. Je hebt een functie clone() die een exacte kopie van het object maakt, maar omdat het een generieke interface is returnt hij een Object. Als programmeur weet je echter dat het een exact kopie is van het object waar je de clone op aan hebt geroepen, in dat geval kun (en vaak moet) je het object casten naar hetzelfde type als het object waar je de clone functie op aangeroepen hebt, en er is niet echt een andere optie (de interface is immers generiek, en dus kun je geen specifiek object returnen)

Overigens ondersteunen sommige talen covariant return types, waardoor je in een implementatie van een interface method een subclass van het originele returntype mag returnen (In C++ is dit zo, ik weet niet hoe het zit met andere talen zoals Java en .Net). In dat geval hoef je niet meer te casten, omdat een MyClass.clone() in m'n voorbeeld dan gewoon een MyClass returnt.

[ Voor 21% gewijzigd door .oisyn op 09-08-2005 17:38 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Ik heb in dit geval dus te maken met 2 trees van objecten. Daarbij heb ik te maken met 2 abstracte classes. 1 tree met domain objecten en 1 tree met mappers daarvan. In de algemene code van DatabaseMapper wil ik niet weten met wat voor domein object ik te maken heb. Dus werk ik met een abstract type (alle domein objecten moeten van deze class extenden, daar zit ook wat algemene functionaliteit in). In de implementatie van de abstracte functie populateObject (die in een geimplementeerde functie wordt aangeroepen) wil ik dit wel weten en daarom cast ik.

De reden waarom ik create en populate gesplits heb is omdat in de werkelijk functie er nog wat code tussen staat. Er wordt namelijk nog gecontroleerd of het object niet in de cache voorkomt en hij krijgt nog een ID toegewezen. Dit wil ik niet kopieeren in iedere populate functie. Dat is bad practice imo en erg fout gevoelig.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 23:40
Michali schreef op dinsdag 09 augustus 2005 @ 17:22:
Een concrete impelementatie van een DatabaseMapper moet in de functie populateObject de parameter $object nu dus casten naar het juiste type. Dit omdat de setter functies van dat type gebruikt moeten worden.
Je hoeft toch helemaal niet te casten? Als jij een object 'Tabelnaampje' (die afgeleid is van PersistentObject) meegeeft aan de parameter $object bij genoemde functie, dan cast php dat (neem ik aan) toch niet naar een PersistentObject type?

Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Dennis schreef op dinsdag 09 augustus 2005 @ 17:42:
[...]

Je hoeft toch helemaal niet te casten? Als jij een object 'Tabelnaampje' (die afgeleid is van PersistentObject) meegeeft aan de parameter $object bij genoemde functie, dan cast php dat (neem ik aan) toch niet naar een PersistentObject type?
Zoals ik zei is dit voorbeeld even in php code, waarin je niet hoeft te casten. Het gaat hier even om het idee. In een taal als Java of C# kun je bij een object alleen gebruik maken van de methods gedeclareerd in de gegeven interface. In php kun je ook totaal andere functies aanroepen (als je zeker weet dat het object die ondersteund), maar dat is dan meer een soort van impliciete cast. Een object verandert nooit van type zo, het enige wat verandert is de interface.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Ik snap het probleem niet. Stel je hebt een abstracte class, A. Deze heeft een ongeimplementeerde functie, F. Daarnaast heb je een niet abstracte class, B, welke A implementeert. Als een functie dan A retourneert, zal hij in werkelijkheid dus een geimplementeerde class teruggeven. B dus. Als je dan A.F() aanroept, roep je de implementatie in B aan. Je hoeft helemaal niet te casten?

Begrijp ik de vraag nou niet, of snapt de OP OO niet?

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Het probleem is dus als je van functies in B gebruik wilt maken die niet in A zijn gedefinieerd. Dan moet je wel eerst casten naar B.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 23:40
Maar toch vraag ik me af of dat goed is, want ik denk dat je soms beter een interface kunt bouwen in plaats van een abstracte klasse als je dit soort situaties krijgt.

Verder, als je een klasse C hebt met een functie die een parameter heeft met abstracte klasse A en die je vervolgens aanroept met een klasse B, dan krijg je hem in klasse C als een klasse A. Als jij dan een functie die alleen in B zit aanroept, doe je iets verkeerd, en had je de functie van klasse C een klasse B parameter mee moeten geven.

Acties:
  • 0 Henk 'm!

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

.oisyn schreef op dinsdag 09 augustus 2005 @ 17:35:
[...]

je het object casten naar hetzelfde type als het object waar je de clone functie op aangeroepen hebt, en er is niet echt een andere optie (de interface is immers generiek, en dus kun je geen specifiek object returnen)
Bij Java kan dit opgelost worden mbv geparametriseerde types of met behulp van een covariant return type. Maar er zijn ook talen (kan er geen bekende opnoemen ;) ) waarbij er een Self type is.
Overigens ondersteunen sommige talen covariant return types, waardoor je in een implementatie van een interface method een subclass van het originele returntype mag returnen (In C++ is dit zo, ik weet niet hoe het zit met andere talen zoals Java en .Net).
Sinds jdk 5.0 en bij .net zal het vanaf 2.0 er vast ook wel in zitten. Covariante return types zijn eenvoudig uit te leggen. Contravariante parameters als iets lastiger. Maar variantie bij geparametriseerde types is wel het meest complex.

[ Voor 4% gewijzigd door Alarmnummer op 09-08-2005 18:40 ]


Acties:
  • 0 Henk 'm!

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Dennis schreef op dinsdag 09 augustus 2005 @ 18:02:
Maar toch vraag ik me af of dat goed is, want ik denk dat je soms beter een interface kunt bouwen in plaats van een abstracte klasse als je dit soort situaties krijgt.
Een abstract class mag imho in 99% van de tijd alleen gebruikt worden als basis voor subclasses maar mag imho nooit als type gebruikt worden. Je kunt dus beter geen variabelen declareren die als type een abstracte class hebben.

dit is imho dus helemaal fout
AbstractList list = new LinkedList();

Abstracte class mag (over het algemeen) geen type definieeren.

De reden hierachter is dat je met een abstract helemaal vast zit aan die implementatie en je geen scheiding hebt van definitie en implementatie. Vooral als herbruik belangrijk is (utility,platform,framework achtige zaken) dan is het imho een must.

[ Voor 20% gewijzigd door Alarmnummer op 09-08-2005 18:54 ]


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Dennis schreef op dinsdag 09 augustus 2005 @ 18:02:
Maar toch vraag ik me af of dat goed is, want ik denk dat je soms beter een interface kunt bouwen in plaats van een abstracte klasse als je dit soort situaties krijgt.
Ik begrijp je niet? Hoe kan ik er nou een interface van maken als de class standaard functionaliteit heeft. Ik kan natuurlijk wel een interface maken en die class hem laten implementeren, maar dat helpt me niet echt verder in het probleem.
Verder, als je een klasse C hebt met een functie die een parameter heeft met abstracte klasse A en die je vervolgens aanroept met een klasse B, dan krijg je hem in klasse C als een klasse A. Als jij dan een functie die alleen in B zit aanroept, doe je iets verkeerd, en had je de functie van klasse C een klasse B parameter mee moeten geven.
nofi, maar volgens begrijp je het idee van een framework niet helemaal. Klass A is gedefinieert in het package van het framework en class B in een andere. We zouden er zelfs vanuit kunnen gaan dat class C en A al lang gecompiled zijn en dat we die niet eens meer kunnen aanpassen. Bovendien is het niet direct class C die functies van B aanroept, maar het is een subclass van C en een al geimplementeerde functie binnen C roept een abstracte functie aan die als parameter een object van type A vraagt, B in dit geval. Stel dat we de class die erft van C even D noemen. D kan niet afwijken van de interface van A (hij kan wel nieuwe functies toevoegen, maar gelijknamige functies maken met een afwijkende parameter lijst (je kunt dacht ik wel overloaden in sommige talen, maar ik geloof dat je die declaraties ook binnen C moet zetten wil dat werken)). Ik kan dus niet zorgen dat ik inees een parameter van type B krijg. Dit kan dus alleen(?) door te casten.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Michali schreef op dinsdag 09 augustus 2005 @ 17:27:
[...]


Het gaat hier even om het idee. Waar je dus een abstracte class hebt met een geimplementeerde functie en een abstracte functie die als parameter een abstract type vraagt. Je komt dan echt niet altijd weg met reflection en metadata.
Ik blijf dit niet snappen.
Je krijgt in je implementatie van je abstracte functie een subtype mee (een type die inherit van je abstracte type; je 'root business object' zeg maar), maar daar kan je toch mbhv reflection (als de taal het ondersteunt) de variablen gaan zetten ?

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Zeker kan dat. Maar soms werk je niet alleen met het zetten van variabelen. Het kan ook zijn dat je wat van de functionaliteit van het object nodig hebt. Dan moet je wel casten. En of ik dit oplossen met reflection nou zo veel netter vind dat casten...

Het populaten van een object was maar een voorbeeldje. Het zou ook iets kunnen zijn wat totaal niet met data te maken heeft maar puur met gedrag.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Michali schreef op dinsdag 09 augustus 2005 @ 19:54:

Het populaten van een object was maar een voorbeeldje. Het zou ook iets kunnen zijn wat totaal niet met data te maken heeft maar puur met gedrag.
Maar ben je dan nog eigenlijk bezig 'binnen je framework', of ben je dan al bezig met een class die jouw framework gebruikt ? (een concrete domain-class, of een concrete mapper) ?

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Ik ben met beide bezig eigenlijk. Ik had al een redelijk deel af, maar ik ben het weer een beetje om aan het gooien vanwege wat nieuwe concepten waarover ik had gelezen. Onder andere uit het boek "Data Access Patterns" waarin ook veel gecast wordt. Vandaar dat ik dit topic er dan ook over open. De concepten werken allemaal goed, alleen heb ik hier en daar artikeltjes gelezen waarin ze met alle macht proberen om casts weg te werken, aangevend dat het niet echt een nette methode is.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 23:40
Michali schreef op dinsdag 09 augustus 2005 @ 19:12:
nofi, maar volgens begrijp je het idee van een framework niet helemaal. Klass A is gedefinieert in het package van het framework en class B in een andere. We zouden er zelfs vanuit kunnen gaan dat class C en A al lang gecompiled zijn en dat we die niet eens meer kunnen aanpassen. Bovendien is het niet direct class C die functies van B aanroept, maar het is een subclass van C en een al geimplementeerde functie binnen C roept een abstracte functie aan die als parameter een object van type A vraagt, B in dit geval. Stel dat we de class die erft van C even D noemen. D kan niet afwijken van de interface van A (hij kan wel nieuwe functies toevoegen, maar gelijknamige functies maken met een afwijkende parameter lijst (je kunt dacht ik wel overloaden in sommige talen, maar ik geloof dat je die declaraties ook binnen C moet zetten wil dat werken)). Ik kan dus niet zorgen dat ik inees een parameter van type B krijg. Dit kan dus alleen(?) door te casten.
Duidelijk voorbeeld :).
Maar toch blijf ik er mijn twijfels over houden, wellicht dat ik het niet helemaal begrijp. Maar zie hieronder hoe ik erover denk.
Alarmnummer schreef op dinsdag 09 augustus 2005 @ 18:44:
Een abstract class mag imho in 99% van de tijd alleen gebruikt worden als basis voor subclasses maar mag imho nooit als type gebruikt worden. Je kunt dus beter geen variabelen declareren die als type een abstracte class hebben.

...

De reden hierachter is dat je met een abstract helemaal vast zit aan die implementatie en je geen scheiding hebt van definitie en implementatie. Vooral als herbruik belangrijk is (utility,platform,framework achtige zaken) dan is het imho een must.
Dit is dus precies wat ik ook probeer te zeggen, namelijk dat ik het probleem van de TS 'niet begrijp' omdat ik het een vieze oplossing vind. Klasse A zou niet eens een klasse moeten zijn maar een interface. En ja, als het al gecompiled is dan begrijp ik niet waarom TS er iets vanaf wilt leiden. Dat was vast niet helemaal de bedoeling van de maker van betreffende systeem, anders had hij wellicht een scheiding gemaakt tussen de klasse en een interface.

Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Dennis schreef op dinsdag 09 augustus 2005 @ 20:15:
[...]

Duidelijk voorbeeld :).
Maar toch blijf ik er mijn twijfels over houden, wellicht dat ik het niet helemaal begrijp. Maar zie hieronder hoe ik erover denk.

[...]

Dit is dus precies wat ik ook probeer te zeggen, namelijk dat ik het probleem van de TS 'niet begrijp' omdat ik het een vieze oplossing vind. Klasse A zou niet eens een klasse moeten zijn maar een interface. En ja, als het al gecompiled is dan begrijp ik niet waarom TS er iets vanaf wilt leiden. Dat was vast niet helemaal de bedoeling van de maker van betreffende systeem, anders had hij wellicht een scheiding gemaakt tussen de klasse en een interface.
Ik gebruik in de geval een abstracte class als type. Mischien kan ik idd beter een interface gebruiken. Maar dat veranderd helemaal niets aan het probleem. Mischien bevat die abstracte class wel enkel abstracte functies. Of heb ik iedere functie overridden in de subclass. Het gaat hier puur om de interface, of bepaalde door een echte interface of door een (abstracte) class. Dat maakt voor het probleem even niet zo veel uit. Ook al was het een interface, dan zou ik nog moeten casten. Want ook die interface zou niet niet de methods implementeren die ik nodig heb.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 23:40
Dit is volgens mij wat jij bedoelt.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    public interface a
    {
        public void function();
    }

    public class b : a
    {
        public void function()
        {
            //
        }

        public void newfunction()
        {
            //
        }
    }

    abstract public class c
    {
        abstract public void function(a voorbeeld);
    }

    public class d : c
    {
        override public void function(a voorbeeld)
        {
            b cast = (b) voorbeeld as b;
            cast.newfunction();
        }
    }

toch?

Je zou nu meerdere objecten b kunnen hebben die allemaal een functie "newfunction" hebben en dan zou jij het goed vinden om het object te casten vanuit d. Ik vind dit dus gewoon een vieze constructie en dus een design fout. Ik zou deze situatie iig proberen te vermijden.

Acties:
  • 0 Henk 'm!

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 15-05 16:29

Macros

I'm watching...

Persoonlijk moet je casten voorkomen zolang dat mogelijk is, maar bij grotere frameworks die zo uitbreidbaar mogelijk moet zijn is het vaak niet mogelijk. Kijk maar naar eens naar het Eclipse framework. Daarbij maken ze het zelfs mogelijk om classes een 'interface' te laten 'implementeren' zonder een interface (zie IAdaptable).
Maar bij kleine zelf geschreven frameworks is het vaak mogelijk om casten te voorkomen. Ik gebruik voornamelijk Generics om dat mogelijk te maken. En als het niet mogelijk is laat ik het framework zelf casten en niet de gebruiker van het framework.

"Beauty is the ultimate defence against complexity." David Gelernter


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Dennis schreef op dinsdag 09 augustus 2005 @ 21:15:
Dit is volgens mij wat jij bedoelt.
[...]
toch?

Je zou nu meerdere objecten b kunnen hebben die allemaal een functie "newfunction" hebben en dan zou jij het goed vinden om het object te casten vanuit d. Ik vind dit dus gewoon een vieze constructie en dus een design fout. Ik zou deze situatie iig proberen te vermijden.
Dat is precies wat ik bedoel ja. Als ik het zelf een mooie nette constructie zou vinden zou ik dit topic niet openen. Maar dit is wel precies zoals het hier in een boek beschreven staat. Hoe zou jij dit herschrijven dan? Ik zit er zelf diep over na te denken, maar het wil me nog niet echt lukken. Zo veel ervaring met OO heb ik nu ook weer niet.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Michali schreef op woensdag 10 augustus 2005 @ 09:08:
Dat is precies wat ik bedoel ja. Als ik het zelf een mooie nette constructie zou vinden zou ik dit topic niet openen. Maar dit is wel precies zoals het hier in een boek beschreven staat. Hoe zou jij dit herschrijven dan? Ik zit er zelf diep over na te denken, maar het wil me nog niet echt lukken. Zo veel ervaring met OO heb ik nu ook weer niet.
Nogmaals, wat is in hemelsnaam het probleem? Als een class 'weet' heeft van de interne implementatie van een class, en die functionaliteit nodig heeft, dan zul je moeten casten ja. Het is compleet afhankelijk van het ontwerp van je systeem.

Maar...

Als de *gebruiker* van je framework class A moet gaan casten naar class B om een bepaalde functie te exposen, is je ontwerp gewoon brak.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06 13:31

drm

f0pc0dert

Hydra:
Als de *gebruiker* van je framework class A moet gaan casten naar class B om een bepaalde functie te exposen, is je ontwerp gewoon brak.
:? Hoe wou je dat dan oplossen als je geen returntypes kunt overriden?

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Hier komt dan een iets uitgebreider voorbeeld:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// interfaces IShape en IShapeRenderer, en class Renderer behoren tot het framework

interface IShape
{
    public void aGenericFunction();
}

interface IShapeRenderer
{
    public void render(IShape shape);
}

class Renderer
{
    private IShapeRenderer shapeRenderer;
    
    public Renderer(IShapeRenderer shapeRenderer)
    {
        this.shapeRenderer = shapeRenderer;
    }
    
    public void renderShape(IShape shape)
    {
        shape.aGenericFunction();
        this.shapeRenderer.render(shape);
    }
}


// classes Rectangle en RectangleRenderer maken gebruik van het framework

class Rectangle implements IShape
{
    public void aGenericFunction()
    {
        // doe iets generieks
    }
    
    public void aNotGenericFunction()
    {
        // doe iets niet generieks
    }
    
    public void anOtherNotGenericFunction()
    {
        // doe nog iets niet generieks
    }
}

class RectangleRenderer implements IShapeRenderer 
{
    public void render(IShape shape)
    {
        Rectangle rect = (Rectangle) shape;
        rect.aNotGenericFunction();
        rect.anOtherNotGenericFunction();
    }
}


// dit is een voorbeeld van hoe client code er uit zou kunnen zien

Renderer renderer = new Renderer(new RectangleRenderer());
Rectangle rect = new Rectangle();
renderer.renderShape(rect);


Het casten moet wel in class RectangleRenderer gebeuren omdat het framework niets afweet van class Rectangle. Het probleem is dus dat ik een stuk generieke code heb waartussen code aangeroepen kan worden die niet generiek is. In dit geval gebeurt dat via een aparte class die interface IShapeRenderer implementeert. Dit is ongeveer ook hoe het in het boek staat beschreven (kwa idee dan). De vraag is dus of dit ook op een andere manier kan en hoe?

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
drm schreef op woensdag 10 augustus 2005 @ 10:28:
:? Hoe wou je dat dan oplossen als je geen returntypes kunt overriden?
Nogmaals, er is weinig zinnigs over te zeggen als je niet weet WAAROM het een probleem is. Voorbeeld zijn bijvoorbeeld Hashtables. Iedereen gebruikt die, en er wordt naar hartelust gecast.

Als het framework een eigen object (en niet zoals in 't geval van een Hashtable jouw eigen objecten) als returnwaarde geeft, en dan de enige manier om het te gebruiken casten naar een subclass welke ook binnen 't framework gedefinieerd is, dan zit er iets scheef in het ontwerp. Je kunt hier alleen weinig over zeggen, want er zijn genoeg redenen te bedenken waarom een class een subclass van het gereturnde object als returnwaarde heeft.
Michali schreef op woensdag 10 augustus 2005 @ 10:58:
Het casten moet wel in class RectangleRenderer gebeuren omdat het framework niets afweet van class Rectangle. Het probleem is dus dat ik een stuk generieke code heb waartussen code aangeroepen kan worden die niet generiek is. In dit geval gebeurt dat via een aparte class die interface IShapeRenderer implementeert. Dit is ongeveer ook hoe het in het boek staat beschreven (kwa idee dan). De vraag is dus of dit ook op een andere manier kan en hoe?
Je stopt de Rectangle specifieke dingen dan toch gewoon in de RectangeRenderer? Die wordt lijkt mij alleen voor rectangles gebruikt.

[ Voor 31% gewijzigd door Hydra op 10-08-2005 11:08 ]

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Hydra schreef op woensdag 10 augustus 2005 @ 11:04:
Nogmaals, er is weinig zinnigs over te zeggen als je niet weet WAAROM het een probleem is. Voorbeeld zijn bijvoorbeeld Hashtables. Iedereen gebruikt die, en er wordt naar hartelust gecast.
Je moet het ook niet zien alsof het ook daadwerkelijk een probleem is. Daar probeer ik juist achter te komen, of dit een probleem is. Als dit gewoon normaal is en kan, dan is er toch niets aan de hand?
Je stopt de Rectangle specifieke dingen dan toch gewoon in de RectangeRenderer? Die wordt lijkt mij alleen voor rectangles gebruikt.
Dat doe ik in het voorbeeld toch ook?

[ Voor 3% gewijzigd door Michali op 10-08-2005 11:12 ]

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 21-09 02:21

Janoz

Moderator Devschuur®

!litemod

Een andere manier zou kunnen zijn dat je niet een renderer gebruikt, maar de shape zelf verantwoordelijk maakt voor het renderen. Je zou een IRenderableShape interface kunnen maken die IShape extend, of gewoon een IRenderable die je voor alles dat zichzelf kan renderen gebruikt. Nadeel hiervan is dat je al je code centreert in je shape object terwijl je dat misschien helemaal niet zou willen (omdat Rectangle zelf helemaal niks nodig heeft met hoe hij gerenderd zou worden bv).

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Michali schreef op woensdag 10 augustus 2005 @ 11:12:
Je moet het ook niet zien alsof het ook daadwerkelijk een probleem is. Daar probeer ik juist achter te komen, of dit een probleem is. Als dit gewoon normaal is en kan, dan is er toch niets aan de hand?

Dat doe ik in het voorbeeld toch ook?
Precies ;) En dat is dus doodnormaal. :)

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 21-09 02:21

Janoz

Moderator Devschuur®

!litemod

Hydra schreef op woensdag 10 augustus 2005 @ 11:04:

Je stopt de Rectangle specifieke dingen dan toch gewoon in de RectangeRenderer? Die wordt lijkt mij alleen voor rectangles gebruikt.
Die zitten daar ook in. Het probleem is juist dat de Rectangle bekend gemaakt moet worden binnen de RectangleRenderer. Bij dit bekent maken kun je vanuit het framework alleen een IShape afdwingen omdat binnen het framework Rectangle helemaal niet bekend is.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Janoz schreef op woensdag 10 augustus 2005 @ 11:15:
Een andere manier zou kunnen zijn dat je niet een renderer gebruikt, maar de shape zelf verantwoordelijk maakt voor het renderen. Je zou een IRenderableShape interface kunnen maken die IShape extend, of gewoon een IRenderable die je voor alles dat zichzelf kan renderen gebruikt. Nadeel hiervan is dat je al je code centreert in je shape object terwijl je dat misschien helemaal niet zou willen (omdat Rectangle zelf helemaal niks nodig heeft met hoe hij gerenderd zou worden bv).
Je gooit een grote bak code op 1 hoop, en er zit ook nog eens een hoop generieke code (instellingen voor kleur e.d.) in een renderer. Lijkt me logisc een base renderer en een base shape te hebben, en voor elke concrete shape een concrete renderer te maken.
Janoz schreef op woensdag 10 augustus 2005 @ 11:17:
Die zitten daar ook in. Het probleem is juist dat de Rectangle bekend gemaakt moet worden binnen de RectangleRenderer. Bij dit bekent maken kun je vanuit het framework alleen een IShape afdwingen omdat binnen het framework Rectangle helemaal niet bekend is.
Het lijkt me dat het framework juist WEL weet dat het een Rectangle betreft. Ik zie niet hoe je een shape rendering framework kunt maken zonder iets van de shapes af te weten.

[ Voor 25% gewijzigd door Hydra op 10-08-2005 11:20 ]

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 21-09 02:21

Janoz

Moderator Devschuur®

!litemod

Dan zou ik als ik jou was het voorbeeld nog eens ietsje beter door nemen. Het punt dat het voorbeeld juist wil maken is dat de Shape implementaties zich niet in het framework bevinden. Neem de code nog eens door en lees ipv rectangle eens 'VerySpecificShape' (en dus ook 'VerySpecificShapeRenderer') oid.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Hydra schreef op woensdag 10 augustus 2005 @ 11:19:
Je gooit een grote bak code op 1 hoop, en er zit ook nog eens een hoop generieke code (instellingen voor kleur e.d.) in een renderer. Lijkt me logisc een base renderer en een base shape te hebben, en voor elke concrete shape een concrete renderer te maken.
Zo zie ik het ook idd, en janoz ook zoals in zijn laatste zin is te lezen. Ik wil het renderen volledig los laten staan van de vorm zelf, ook omdat ik bijvoorbeeld te maken zou kunnen hebben met verschillende manieren van renderen (zoals naar een beeldscherm of een printer oid).
Het lijkt me dat het framework juist WEL weet dat het een Rectangle betreft. Ik zie niet hoe je een shape rendering framework kunt maken zonder iets van de shapes af te weten.
Je moet even voorbij het voorbeeld kijken. Mischien dat ik latern wel een shape wil toevoegen genaamt GoTLogo, moet ik daar dan bij het maken van het framework al rekening mee houden? Mischien dat ik wel functies kan impelementeren die werkelijk alles kunnen renderen, maar dat is even niet van belang voor de discussie.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
Michali schreef op woensdag 10 augustus 2005 @ 11:29:
Je moet even voorbij het voorbeeld kijken. Mischien dat ik latern wel een shape wil toevoegen genaamt GoTLogo, moet ik daar dan bij het maken van het framework al rekening mee houden? Mischien dat ik wel functies kan impelementeren die werkelijk alles kunnen renderen, maar dat is even niet van belang voor de discussie.
Als je een nieuwe shape implementeert, zal je de implementatie van de renderer ook zelf doen lijkt me. Dus dan lever je zowel een renderer als een shape aan het 'framework'. Ik zie nog steeds het probleem niet.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Hydra schreef op woensdag 10 augustus 2005 @ 12:11:
[...]


Als je een nieuwe shape implementeert, zal je de implementatie van de renderer ook zelf doen lijkt me. Dus dan lever je zowel een renderer als een shape aan het 'framework'. Ik zie nog steeds het probleem niet.
Het is ook geen probleem. Het werkt perfect. Alleen vraag ik me af of het ook anders kan. Beide classes schrijf ik inderdaad zelf en maken gebruik van het framework, maar behoren er niet toe.

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Het punt is heel simpel. Je hebt een framework dat een gegeven set functionaliteit biedt, en dat kan doen door met een gegeven set types te werken. Deze types zijn bekend BINNEN het framework en zodoende kan het framework dus met die types werken. Dit kunnen interfaces zijn maar ook abstract base classes. (ik zie hier iemand beweren dat je een abstract base class niet als type mag gebruiken... volgens mij belemmer je dan alleen jezelf en ook nog onnodig).

Welnu, je framework werkt dus met bekende types. Voor types die EXTERN ook worden gebruikt, defineer je INTERFACES, voor typen die louter INTERN worden gebruikt, kun je ook abstract base classes gebruiken. Dit laatste heeft als voordeel dat je je methods internal kunt definieren en ze dus niet buiten het framework worden gebruikt maar dat je TOCH generieke code kunt bouwen.

Als je niet wilt casten, zorg je ervoor dat je methods die public zijn, alleen parameters accepteren die al in die typen zijn gedefineerd, dus bv:
SaveEntity(IEntity toSave)

IN SaveEntity kun je nu werken met IEntity en dat kan generiek zonder te casten, en je code die deze method gebruikt kan gewoon een CustomerEntity naar deze method passen, mits deze natuurlijk IEntity implementeert.

Internal interfaces kunnen, maar hebben het nadeel dat de implementatie zelf public is. Een abstract base class met internal methods is dan een betere oplossing.

Waar soms voor wordt gekozen (C# Only) is explicit interface implementation, waarbij je de interface methods private implementeert (zonder access operator) en ze alleen beschikbaar zijn wanneer je expliciet naar die interface cast. Dit kan een oplossing zijn wanneer je niet vereist dat classes van een abstract base class erven maar wel die code nodig hebt om je framework te runnen.

[ Voor 13% gewijzigd door EfBe op 10-08-2005 12:23 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Michali schreef op woensdag 10 augustus 2005 @ 10:58:
Het casten moet wel in class RectangleRenderer gebeuren omdat het framework niets afweet van class Rectangle. Het probleem is dus dat ik een stuk generieke code heb waartussen code aangeroepen kan worden die niet generiek is. In dit geval gebeurt dat via een aparte class die interface IShapeRenderer implementeert. Dit is ongeveer ook hoe het in het boek staat beschreven (kwa idee dan). De vraag is dus of dit ook op een andere manier kan en hoe?
Visitor pattern?

(wat je hier wilt is een functie kiezen op basis van twee dynamische types (RectangleRenderer, en Rectangle), waarvan het statische type bekend is(Renderer en Shape). Dat heet double-dispatch, en het visitor pattern is een vorm om double dispatch te implementeren. Sommige talen bevatten op zich al double- of multi-dispatch. Er zijn ook nog andere manieren; multi-dispatch is een bekend probleem. Dat casten wat je nu doet is erg riskant, en zou je moeten vermijden.)

[ Voor 29% gewijzigd door Zoijar op 10-08-2005 12:20 ]


Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 20-09 14:15
Hydra schreef op woensdag 10 augustus 2005 @ 12:11:
[...]


Als je een nieuwe shape implementeert, zal je de implementatie van de renderer ook zelf doen lijkt me. Dus dan lever je zowel een renderer als een shape aan het 'framework'. Ik zie nog steeds het probleem niet.
Het is dan ook niet echt een probleem. Denk ook niet dat het op te lossen is, maar laat ik aan Hydra uit proberen te leggen wat de TS bedoelt (want ik heb het idee dat je de TS z'n vraag nog niet helemaal ziet, misschien heb ik het mis).

Als iemand zijn framework wil gebruiken en een specifiekere renderer wil gebruiken, zal hij inderdaad zowel een Shape als een Renderer leveren. Deze Renderer implementeert dan de interface IShapeRenderer en moet dus de methode render(IShape iShape) implementeren. Waar het, naar mijn idee, de TS om draait is het implementeren van deze functie. Hij zou liever de mogelijkheid zien om de render methode te implementeren met een parameter van het type Rectangle (bv.) in plaats van een generieke IShape type parameter omdat hij dan moet casten, wil hij andere methoden van het specifiekere type Rectangle gebruiken. Omdat het eerste niet mogelijk is, vraagt hij of de cast niet erg ranzig is om die te verplichten en of hij misschien een ander design zou moeten maken.

Naar mijn idee is er echter niet veel anders mogelijk en zullen de gebruikers die cast gewoon moeten doen als ze specifiekere functies van het object willen aanroepen.
Zoijar schreef op woensdag 10 augustus 2005 @ 12:17:
[...]

Visitor pattern?

(wat je hier wilt is een functie kiezen op basis van twee dynamische types (RectangleRenderer, en Rectangle), waarvan het statische type bekend is(Renderer en Shape). Dat heet double-dispatch, en het visitor pattern is een vorm om double dispatch te implementeren. Sommige talen bevatten op zich al double- of multi-dispatch. Er zijn ook nog andere manieren; multi-dispatch is een bekend probleem. Dat casten wat je nu doet is erg riskant, en zou je moeten vermijden.)
Volgens mij gaat ook dat niet werken, omdat je in je IVisitor interface (die dus in het framework zou komen) definieert welke typen hij kan visitten. En die typen kunnen door de gebruiker van het framework worden geimplementeerd, die weet je dus niet van tevoren.

[ Voor 23% gewijzigd door Amras op 10-08-2005 12:26 ]


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Michali schreef op woensdag 10 augustus 2005 @ 10:58:
Het casten moet wel in class RectangleRenderer gebeuren omdat het framework niets afweet van class Rectangle. Het probleem is dus dat ik een stuk generieke code heb waartussen code aangeroepen kan worden die niet generiek is. In dit geval gebeurt dat via een aparte class die interface IShapeRenderer implementeert. Dit is ongeveer ook hoe het in het boek staat beschreven (kwa idee dan). De vraag is dus of dit ook op een andere manier kan en hoe?
Dat kan helemaal niet. Je framework kent alleen de types die het kent, en de interfaces van die types, meer niet. Je framework kent dus geen methods die niet in die types zitten. Je kunt wel het framework een generieke method laten callen die door polymorphism dan je feitelijke custom code aanroept, maar direct in je framework kan dat niet. Je kunt ook niet casten, want je framework kent het type niet waarnaar je wilt casten.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Zoijar schreef op woensdag 10 augustus 2005 @ 12:17:
[...]

Visitor pattern?

(wat je hier wilt is een functie kiezen op basis van twee dynamische types (RectangleRenderer, en Rectangle), waarvan het statische type bekend is(Renderer en Shape). Dat heet double-dispatch, en het visitor pattern is een vorm om double dispatch te implementeren. Sommige talen bevatten op zich al double- of multi-dispatch. Er zijn ook nog andere manieren; multi-dispatch is een bekend probleem. Dat casten wat je nu doet is erg riskant, en zou je moeten vermijden.)
Ik heb het visitor pattern al de hele tijd in gedachten, maar volgens mij is die hier niet van toepassing. Je moet namelijk de te bezoeken object al van te voren weten. Het enige wat dan dynamisch is zijn de classes die de bezoeken uitvoeren, de IShapeRenderers in dit geval.

Of ik moet er nu toch echt naast zitten, maar zou je dan een klein voorbeeldje kunnen geven van hoe ik dat zou kunnen implementeren?
EfBe schreef op woensdag 10 augustus 2005 @ 12:26:
[...]

Dat kan helemaal niet. Je framework kent alleen de types die het kent, en de interfaces van die types, meer niet. Je framework kent dus geen methods die niet in die types zitten. Je kunt wel het framework een generieke method laten callen die door polymorphism dan je feitelijke custom code aanroept, maar direct in je framework kan dat niet. Je kunt ook niet casten, want je framework kent het type niet waarnaar je wilt casten.
Dat is toch precies wat ik zei? Of reageerde je ook op de post waarop ik reageerde?

[ Voor 24% gewijzigd door Michali op 10-08-2005 12:33 ]

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Amras schreef op woensdag 10 augustus 2005 @ 12:25:
Volgens mij gaat ook dat niet werken, omdat je in je IVisitor interface (die dus in het framework zou komen) definieert welke typen hij kan visitten. En die typen kunnen door de gebruiker van het framework worden geimplementeerd, die weet je dus niet van tevoren.
Ja, dat klopt, dat is inderdaad een probleem van standaard visitor. Maar er zijn wel oplosingen gevonden. Modern C++ design (loki) bevat bv al een templates visitor mbv template-lists (dat is dan wel C++) Maar er zijn ook andere manieren met het bijhouden van lijsten etc. Wat ik bedoelde te zeggen is dat het voor de TS misschien handig is om een over double dispatch te lezen. Er is al zo veel gedaan om deze problemen op te lossen.

Acties:
  • 0 Henk 'm!

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 06-09 00:37

curry684

left part of the evil twins

.oisyn schreef op dinsdag 09 augustus 2005 @ 17:35:
[...]

Overigens ondersteunen sommige talen covariant return types, waardoor je in een implementatie van een interface method een subclass van het originele returntype mag returnen (In C++ is dit zo, ik weet niet hoe het zit met andere talen zoals Java en .Net). In dat geval hoef je niet meer te casten, omdat een MyClass.clone() in m'n voorbeeld dan gewoon een MyClass returnt.
C# ondersteunt het iig niet:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    abstract class Base
    {
        public abstract Base Clone();
    }
    
    class Derived : Base
    {
        public Derived Clone()
        {
        }
        
        static public void MyMethod()
        {
            Derived woei = new Derived();
        }
    }
'RegExper.Derived' does not implement inherited abstract member 'RegExper.Base.Clone()'
Dit kun je in .NET ook terugzien aan constructies bij WebRequest.Create(), die overridden wordt als bijv. HttpWebRequest.Create() maar dus nog steeds een WebRequest teruggeeft. In dat geval gebruik je in .NET de 'as' constructie:
C#:
1
2
3
HttpWebRequest req = HttpWebRequest.Create(...) as HttpWebRequest;
if(req == null)
  throw new FatalException(...);

Professionele website nodig?


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Amras schreef op woensdag 10 augustus 2005 @ 12:25:
Als iemand zijn framework wil gebruiken en een specifiekere renderer wil gebruiken, zal hij inderdaad zowel een Shape als een Renderer leveren. Deze Renderer implementeert dan de interface IShapeRenderer en moet dus de methode render(IShape iShape) implementeren. Waar het, naar mijn idee, de TS om draait is het implementeren van deze functie. Hij zou liever de mogelijkheid zien om de render methode te implementeren met een parameter van het type Rectangle (bv.) in plaats van een generieke IShape type parameter omdat hij dan moet casten, wil hij andere methoden van het specifiekere type Rectangle gebruiken. Omdat het eerste niet mogelijk is, vraagt hij of de cast niet erg ranzig is om die te verplichten en of hij misschien een ander design zou moeten maken.
Dat is idd precies wat ik bedoel.
Zoijar schreef op woensdag 10 augustus 2005 @ 12:33:
[...]

Ja, dat klopt, dat is inderdaad een probleem van standaard visitor. Maar er zijn wel oplosingen gevonden. Modern C++ design (loki) bevat bv al een templates visitor mbv template-lists (dat is dan wel C++) Maar er zijn ook andere manieren met het bijhouden van lijsten etc. Wat ik bedoelde te zeggen is dat het voor de TS misschien handig is om een over double dispatch te lezen. Er is al zo veel gedaan om deze problemen op te lossen.
Ik heb even wat gelezen over double dispatch en het lijkt een variant van visitor. Deze oplossing is ook eigenlijk degene die whoami voorstelde in de 5de post. Hier wil ik dus eigenlijk niet voor kiezen en is zeker niet een oplossing in alle gevallen. Probleem is dat ik (om even verder te gaan op het voorbeeld) de Rectangle geen verantwoordelijk wil geven van het renderen. Dat moet een apart onderdeel blijven. In zo'n geval gaat double dispatch niet op.


Is dit trouwens ook niet een voorbeeld van wat ik bedoel? (vooral de functie paint dan): [rml][ Java] 2x een plaatje? :S[/rml]

[ Voor 40% gewijzigd door Michali op 10-08-2005 14:02 ]

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • NetForce1
  • Registratie: November 2001
  • Laatst online: 20-09 23:15

NetForce1

(inspiratie == 0) -> true

Amras schreef op woensdag 10 augustus 2005 @ 12:25:
Het is dan ook niet echt een probleem. Denk ook niet dat het op te lossen is, maar laat ik aan Hydra uit proberen te leggen wat de TS bedoelt (want ik heb het idee dat je de TS z'n vraag nog niet helemaal ziet, misschien heb ik het mis).

Als iemand zijn framework wil gebruiken en een specifiekere renderer wil gebruiken, zal hij inderdaad zowel een Shape als een Renderer leveren. Deze Renderer implementeert dan de interface IShapeRenderer en moet dus de methode render(IShape iShape) implementeren. Waar het, naar mijn idee, de TS om draait is het implementeren van deze functie. Hij zou liever de mogelijkheid zien om de render methode te implementeren met een parameter van het type Rectangle (bv.) in plaats van een generieke IShape type parameter omdat hij dan moet casten, wil hij andere methoden van het specifiekere type Rectangle gebruiken. Omdat het eerste niet mogelijk is, vraagt hij of de cast niet erg ranzig is om die te verplichten en of hij misschien een ander design zou moeten maken.

Naar mijn idee is er echter niet veel anders mogelijk en zullen de gebruikers die cast gewoon moeten doen als ze specifiekere functies van het object willen aanroepen.
Als je generics tot je beschikking hebt kun je het daar mee oplossen. Zonder generics moet je je volgens mij in allerlei rare bochten gaan wringen om het zonder casten te doen (als het al mogelijk is). Het lijkt me dat dat ook niet echt de bedoeling is.

Voorbeeldje hoe het met generics kan:
Java:
1
2
3
4
5
6
7
8
9
public interface IShapeRenderer<T implements IShape> {
  public void render(T shape);
}

public class MyShapeRenderer implements IShapeRenderer<MyShape> {
  public void render(MyShape shape) {
    //doe hier je ding
  }
}

[ Voor 2% gewijzigd door NetForce1 op 10-08-2005 20:27 . Reden: reflection veranderd in generics ]

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


Acties:
  • 0 Henk 'm!

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

EfBe schreef op woensdag 10 augustus 2005 @ 12:17:
Het punt is heel simpel. Je hebt een framework dat een gegeven set functionaliteit biedt, en dat kan doen door met een gegeven set types te werken. Deze types zijn bekend BINNEN het framework en zodoende kan het framework dus met die types werken. Dit kunnen interfaces zijn maar ook abstract base classes. (ik zie hier iemand beweren dat je een abstract base class niet als type mag gebruiken... volgens mij belemmer je dan alleen jezelf en ook nog onnodig).
Juist niet. Een willekeurige implementatie van een interface kan gebruikt worden terwijl het lastiger is om een willekeurige implementatie van een abstract class te leveren. Bijna alle patterns zijn gebaseerd op scheiding tussen interface en implementatie en ik heb al zo vaak lopen vloeken op beperkte ontwerp keuzes omdat deze scheiding niet gemaakt is. Vooral als je meer richting herbruikbare componenten komt (dus zaken die in meerdere applicaties gebruikt moeten worden) dan is voor mij een belangrijk signaal dat ik een inflexibel ontwerp voor de kiezen heb de hoeveelheid abstract classes waar geen interface voor beschikbaar is.

Het is trouwens geen 100% regel.. maar in veel gevallen zou beter een interface gebruikt kunnen worden dan een abstract class.

PS:
naarmate ik meer richting concretere code kom, vind ik herbruikbaarheid en dat soort zaken een stuk minder belangrijk. Daar ga ik ook niet voor ieder wiswasje een interface opstellen.
Internal interfaces kunnen, maar hebben het nadeel dat de implementatie zelf public is. Een abstract base class met internal methods is dan een betere oplossing.
Nope.. want jouw interface beschrijft hetzelfde contract als een abstracte class beschrijft, wat er intern gebeurd is een 2e. Verder lever ik van veel interfaces ook een abstracte implementatie zodat ik de vrijheid heb van een interface, maar het gemak van een abstract class. Als je trouwens wilt weten hoe fijn dit is, moet je eens kijken naar het collectionframework van Java of naar de concurrency library. Dat zijn libraries die in grote lijnen zo zijn opgezet en dat is een genot om uitbreidingen op te schrijven. Verder is het zelfs een van de grondregels van het Spring framework.

Ik neem aan dat ik nu een hele scheldserenade over me heen gaan krijgen, maar dat boeit me niet zoveel. Ik denk dat we andere stijlen van ontwikkelen erop na houden en dat we jou niet meer op het rechte pad kunnen krijgen ;)

[ Voor 11% gewijzigd door Alarmnummer op 10-08-2005 19:37 ]


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
NetForce1 schreef op woensdag 10 augustus 2005 @ 19:17:
[...]


Als je reflection tot je beschikking hebt kun je het daar mee oplossen. Zonder reflection moet je je volgens mij in allerlei rare bochten gaan wringen om het zonder casten te doen (als het al mogelijk is). Het lijkt me dat dat ook niet echt de bedoeling is.

Voorbeeldje hoe het met reflection kan:
Java:
1
2
3
4
5
6
7
8
9
public interface IShapeRenderer<T implements IShape> {
  public void render(T shape);
}

public class MyShapeRenderer implements IShapeRenderer<MyShape> {
  public void render(MyShape shape) {
    //doe hier je ding
  }
}
Heet dat reflection? Ik dacht dat reflection het soort van reverse engineren van classes was. Dit is heet toch gewoon generics of templates? (php5 waarin ik nu werk ondersteund trouwens wel reflection, maar geen generics)

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Nu online
Reflection is het at-runtime opvragen van gegevens van een object (bv, de members opvragen, methods-opvragen, etc...)

Hetgeen NetForce1 als voorbeeld gebruikt, is idd generics.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • NetForce1
  • Registratie: November 2001
  • Laatst online: 20-09 23:15

NetForce1

(inspiratie == 0) -> true

whoami schreef op woensdag 10 augustus 2005 @ 20:23:
Reflection is het at-runtime opvragen van gegevens van een object (bv, de members opvragen, methods-opvragen, etc...)

Hetgeen NetForce1 als voorbeeld gebruikt, is idd generics.
Idd, stom zeg... |:(

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 21-08 17:09
offtopic:
Sorry dat ik erover begin, maar heb je al eens commentaar op je ondertitel gehad? :)

https://niels.nu


  • NetForce1
  • Registratie: November 2001
  • Laatst online: 20-09 23:15

NetForce1

(inspiratie == 0) -> true

Hydra schreef op woensdag 10 augustus 2005 @ 22:57:
[...]


offtopic:
Sorry dat ik erover begin, maar heb je al eens commentaar op je ondertitel gehad? :)
offtopic:
nee nog nooit, ik post ook niet zoveel, misschien dat het daar aan ligt. Hoezo?

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Alarmnummer schreef op woensdag 10 augustus 2005 @ 19:26:
[...]
Juist niet. Een willekeurige implementatie van een interface kan gebruikt worden terwijl het lastiger is om een willekeurige implementatie van een abstract class te leveren.
Daarom is die interface ook voor public zaken en de code in de abstract class voor interne zaken. En die is beschikbaar al, dus hoef je niet te leveren.
Bijna alle patterns zijn gebaseerd op scheiding tussen interface en implementatie en ik heb al zo vaak lopen vloeken op beperkte ontwerp keuzes omdat deze scheiding niet gemaakt is. Vooral als je meer richting herbruikbare componenten komt (dus zaken die in meerdere applicaties gebruikt moeten worden) dan is voor mij een belangrijk signaal dat ik een inflexibel ontwerp voor de kiezen heb de hoeveelheid abstract classes waar geen interface voor beschikbaar is.
Je haalt er weer weet ik wat bij, maar ik beschreef een duidelijke situatie: een framework dat INTERNE functionaliteit wil gebruiken en daar kun je geen interface voor gebruiken, want dan zijn die methods PUBLIC. (tenzij je die methods public wilt natuurlijk).

In zn algemeenheid gebruik je daarnaast een abstract base class voor verplichte plumbing code en is het veelal zo dat je een interface daar al implementeert met abstract methods.
[...]
Nope.. want jouw interface beschrijft hetzelfde contract als een abstracte class beschrijft, wat er intern gebeurd is een 2e. Verder lever ik van veel interfaces ook een abstracte implementatie zodat ik de vrijheid heb van een interface, maar het gemak van een abstract class. Als je trouwens wilt weten hoe fijn dit is, moet je eens kijken naar het collectionframework van Java of naar de concurrency library. Dat zijn libraries die in grote lijnen zo zijn opgezet en dat is een genot om uitbreidingen op te schrijven. Verder is het zelfs een van de grondregels van het Spring framework.
1) spring is AOP, en heeft hier niets mee te maken
2) je begrijpt niet welke situatie ik bedoelde. Functionaliteit voor INTERN gebruik in een framework kun je niet in een interface stoppen, tenzij je die private implementeert. Nu kun jij roepen 'nope', maar dan moet je maar eens een testje bouwen. Dan ga jij fijn een interface definieren en die alleen gebruiken intern, 10 tegen 1 dat jouw interface methods public zijn en dat wil je niet bij functionaliteit die alleen intern gebruikt wordt.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • EfBe
  • Registratie: Januari 2000
  • Niet online
NetForce1 schreef op donderdag 11 augustus 2005 @ 08:38:
[...]
offtopic:
nee nog nooit, ik post ook niet zoveel, misschien dat het daar aan ligt. Hoezo?
offtopic:
een boolean expression kan niet de LHS zijn van een assignment expressie! Aho Ullman maar weer voor het daglicht!

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

EfBe schreef op donderdag 11 augustus 2005 @ 09:23:
[...]

Je haalt er weer weet ik wat bij, maar ik beschreef een duidelijke situatie: een framework dat INTERNE functionaliteit wil gebruiken en daar kun je geen interface voor gebruiken, want dan zijn die methods PUBLIC. (tenzij je die methods public wilt natuurlijk).
Dat ben ik met je eens. De troep die in een class staat waar niemand iets mee te maken heeft, kieper ik ook in private methodes.
In zn algemeenheid gebruik je daarnaast een abstract base class voor verplichte plumbing code en is het veelal zo dat je een interface daar al implementeert met abstract methods.
Gedeeltelijk. Als er al bepaalde logica in die abstract class zit, dan is het soms gigantisch lastig om daar andere implementaties van te leveren. En als je richting herbruikbare componenten zit, kan dit grote beperkingen opleveren. Ik ben er al te vaak tegen aangelopen.

Het nadeel van veel interfaces is dat het systeem complexer wordt. Als ik kijk naar Lucene zie ik dat de api relatief eenvoudig is om te gebruiken doordat ze niet veel interfaces hebben en omdat ze veel package friendly (dus buiten de package onzichtbaar) classes hebben. Tja.. de een vind het fijn, ik vind het minder prettig. Stukken code gebruiken is lastig omdat je er niet bij kan en omdat je niet een implementatie erin kan prikken waarover je 100% controle hebt.
1) spring is AOP, en heeft hier niets mee te maken
Spring kan AOP toepassen maar daar ging mijn opmerking niet over. Spring is een extreem uitbreidbaar platform (zie het al een normaal stuk java code) en doordat ze als basis bijna altijd een interface hebben, is het een genot om daar plugins voor te schrijven. Je hebt een algemeen contract.. als jij iets beters weet dan dat standaard in Spring zit.. nou.. maak maar een implementatie van dat contract en je bent klaar. En ik maak dus ook regelmatig uitbreidingen.

Voor veel andere frameworks waar ik mee bezig ben geld hetzelfde.
2) je begrijpt niet welke situatie ik bedoelde. Functionaliteit voor INTERN gebruik in een framework kun je niet in een interface stoppen, tenzij je die private implementeert.
Dat ben ik met je eens. Mijn interfaces zijn over het algemeen erg klein (meestal 2 tot 3 methodes) en die laten zeker geen implementatie-detail methodes zien (dat zou ook onzin zijn).

Maar op het moment dat je een framework hebt die op veel punten uitgebreid moet kunnen worden zorg ik ervoor dat ik contracten opstel waarmee verschillende implementatie geleverd kunnen worden. En mijn basis is dan bijna altijd een interface waarna ik steeds meer ga toewerken naar specifiekere implementatie.

Zie bv een de class hierarchie van:
ThreadPoolExecutor

Deze erf van een abstract class AbstractExecutorService omdat er veel overeenkomstig gedrag is tussen de meeste ExecutorServices.

Maar als jij denkt dat jij iets beters kan leveren, dan kan dat:
De ThreadPoolExecutor die implementeerd de ExecutorService een interface waarin beschreven wordt waar iedere executorservice aan moet voldoen. En een client vande executor service die heeft nog minder behoefte aan functionaliteit en die krijgt de meest minimale interface aangeboden:
Executor

Dit is een goed ontwerp waarbij je minimale contracten hebt, concretere contracten voor concretere situaties en abstract implementaties die de pijn weg kunnen nemen van het volledig zelf bouwen.

Dit beschouw ik als een goed ontwerp en deze manier van werken zie ik graag terug in mijn en ander mans code. Ik begin trouwens altijd bij wat is mijn client-wens voor deze interface, in dit geval de Executor. Vanaf dat punt ga ik dan werken naar concretere situaties. Op het moment dat ik in de herbruikbare componenten zit die uitgebreid moeten worden is dit mijn manier van werken. Als ik in een of ander eindproduct zit is dit niet mijn manier van werken aangezien herbruikbaarheid dan een veel minder grote rol spelen. Als je dan zo gaat werken ga je de baas geld kosten.

[ Voor 7% gewijzigd door Alarmnummer op 11-08-2005 09:52 ]


  • NetForce1
  • Registratie: November 2001
  • Laatst online: 20-09 23:15

NetForce1

(inspiratie == 0) -> true

EfBe schreef op donderdag 11 augustus 2005 @ 09:26:
[...]

offtopic:
een boolean expression kan niet de LHS zijn van een assignment expressie! Aho Ullman maar weer voor het daglicht!
offtopic:
dat had ik zelf ook al bedacht idd. Ik ben al ong een jaar aan het bedenken dat ik die ; misschien wel weg zou moeten halen

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Wat zou eigenlijk een argument tegen zijn om gewoon een sub type te mogen specificeren voor parameters bij het overriden (of implementeren bij een pure interface) van een functie? Een sub type bevat namelijk altijd (in de meeste talen iig) de volledige interface van het super type en wat soms uitbreidingen. Mij lijkt het geen probleem iig. Je krijgt dan een soort van impliciete cast die ook mis kan gaan uiteraard, maar daar dien je dan rekening mee te houden. Ook met zo'n expliciete cast kun je problemen krijgen. Hoe denken jullie hier over?

Noushka's Magnificent Dream | Unity


Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 21-09 02:21

Janoz

Moderator Devschuur®

!litemod

Een sub type bevat namelijk altijd (in de meeste talen iig) de volledige interface van het super type
Hiervan ga je uit voor die uitbreiding, maar de uitbreiding zorgt juist dat dit niet geldt. Heb je in SuperA een methode met parameter SuperB zitten, en in SubA een methode met parameter SubB, dan heeft SubA niet dezelfde functionaliteit als SuperA omdat Sub a niet SuperB als parameter accepteerd.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Janoz schreef op vrijdag 12 augustus 2005 @ 13:34:
[...]

Hiervan ga je uit voor die uitbreiding, maar de uitbreiding zorgt juist dat dit niet geldt. Heb je in SuperA een methode met parameter SuperB zitten, en in SubA een methode met parameter SubB, dan heeft SubA niet dezelfde functionaliteit als SuperA omdat Sub a niet SuperB als parameter accepteerd.
Voor de liefhebbers: zoek maar eens naar contravariante functie argumenten en covariant return type.

[ Voor 3% gewijzigd door Alarmnummer op 12-08-2005 13:45 ]


Acties:
  • 0 Henk 'm!

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 29-05 22:54
Janoz schreef op vrijdag 12 augustus 2005 @ 13:34:
[...]

Hiervan ga je uit voor die uitbreiding, maar de uitbreiding zorgt juist dat dit niet geldt. Heb je in SuperA een methode met parameter SuperB zitten, en in SubA een methode met parameter SubB, dan heeft SubA niet dezelfde functionaliteit als SuperA omdat Sub a niet SuperB als parameter accepteerd.
idd, daar had ik nog niet eens aan gedacht. Grappig dat een mogelijke oplossing zichzelf ook weer onmogelijk maakt :)

Noushka's Magnificent Dream | Unity

Pagina: 1