Toon posts:

[JAVA] meerdere interfaces, design patterns

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hallo allemaal,

Gisteren stuitte ik tegen een designprobleem, en tot nu toe heb ik hiervoor nog geen mooie en vooral handige oplossing gevonden.

Stel je hebt volgende interface (vergelijkbaar met java.util.Collection):
Java:
1
2
3
4
5
6
7
8
9
10
private interface MyCollection {
        public boolean isEmpty();
        public int size();
        
        public void clear();
        public boolean add(Object o);
        public boolean remove(Object o);

        public Iterator iterator();
}

Veel algoritmen (bijvoorbeeld het sorteren van een collectie) hebben maar een subset van deze functies nodig. Daarom is het een goed idee om de interface te splitsen in kleinere interfaces (mensen met ervaring met generic programming, bv. C++ STL, zullen dit wel herkennen in concepten en models van concepten):
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private interface MyCollection {
        public boolean isEmpty();
        public int size();
}

private interface MyUpdatableCollection extends MyCollection {
        public void clear();
        public boolean add(Object o);
        public boolean remove(Object o);
}

private interface MyIterableCollection extends MyCollection {
        public Iterator iterator();
}

Nu komt mijn probleem. Stel je hebt een functie die een collectie aanvaardt (input) en een collectie returnt (output). Normaliter wil je dat de output van hetzelfde type is dan de input. Bijvoorbeeld, als een inputcollectie updatable en iterable is, wil je dat je outputcollectie dat ook is. Hieronder heb ik een functie, decorateCollection(MyCollection c), geschreven die de inputcollectie 'verkapt' in een collectie van hetzelfde type, en deze returnt.
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
    public MyCollection decorateCollection(MyCollection c) {
        boolean updatable = c instanceof MyUpdatableCollection;
        boolean iterable = c instanceof MyIterableCollection;

        if (updatable && iterable)
              return new _UpdatableAndIterable(c);
        else if (updatable)
             return new _Updatable(c);
        else if (iterable)
            return new _Iterable(c);
        else
            return new _None(c);
    }

    public class _UpdatableAndIterable implements MyUpdatableCollection, MyIterableCollection
    {
        MyCollection c;

        public _UpdatableAndIterable(MyCollection c) { this.c = c; }
        public void clear() { ((MyUpdatableCollection) c).clear(); }
        public boolean add(Object o) { return ((MyUpdatableCollection) c).add(o); }
        public boolean remove(Object o) { return ((MyUpdatableCollection) c).remove(o); }
        public boolean isEmpty() { return c.isEmpty(); }
        public int size() { return c.size(); }
        public Iterator iterator() { return ((MyIterableCollection) c).iterator(); }
    }

    public class _Updatable implements MyUpdatableCollection
    {
        MyCollection c;

        public _Updatable(MyCollection c) { this.c = c; }
        public void clear() { ((MyUpdatableCollection) c).clear(); }
        public boolean add(Object o) { return ((MyUpdatableCollection) c).add(o); }
        public boolean remove(Object o) { return ((MyUpdatableCollection) c).remove(o); }
        public boolean isEmpty() { return c.isEmpty(); }
        public int size() { return c.size(); }
    }

    public class _Iterable implements MyIterableCollection
    {
        MyCollection c;

        public _Iterable(MyCollection c) { this.c = c; }
        public boolean isEmpty() { return c.isEmpty(); }
        public int size() { return c.size(); }
        public Iterator iterator() { return ((MyIterableCollection) c).iterator(); }
    }

    public class _None implements MyCollection
    {
        MyCollection c;

        public _None(MyCollection c) { this.c = c; }
        public boolean isEmpty() { return c.isEmpty(); }
        public int size() { return c.size(); }
    }

Het probleem is dat ik nu telkens het type van de inputcollectie moet controleren, om een collectie van hetzelfde type aan te kunnen maken. Dit is niet handig, en zeker niet praktisch bij een groter aantal interfaces. Het aantal combinaties is exponentieel!

Mijn vraag is nu: kan dit beter? Ik zit er al een tijdje naar te staren, maar ik denk dat ik hier op 1 van de beperkingen van Java stuit.

Verwijderd

Zonder echt goed naar je voorbeelden te hebben gekeken, moest ik meteen denken aan Generics.

Daarvoor heb je dan wel Java 5.0 nodig, maar dat mag niet zo'n enorm probleem meer zijn :)

Verwijderd

Topicstarter
Verwijderd schreef op zondag 27 maart 2005 @ 13:17:
Zonder echt goed naar je voorbeelden te hebben gekeken, moest ik meteen denken aan Generics.

Daarvoor heb je dan wel Java 5.0 nodig, maar dat mag niet zo'n enorm probleem meer zijn :)
Generics hebben hier volgens mij niet veel mee te maken. Generics laten je toe classes te parametriseren, maar ik denk dat ik hierdoor mijn probleem niet oplos.

generics != templates

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

Alarmnummer

-= Tja =-

Hoe zou je dit willen oplossen in c++? Het lijkt me lastig om nieuwe functionaliteit in een functie te injecteren.

Een algemene waarschuwing:
laat je niet verleiden tot teveel geparametriseer en het kapot breken van interfaces. Het eindresultaat is dat je een te complex systeem krijgt dat niet prettig is om mee te werken. Ik heb zelf ook al heel wat geparametriseerde collections en andere stuf gemaakt.. maar als je het te ver op wilt splitsen kom je diep in de praktische ellende.

Mijn ervaring is verder dat voor hele generieke klasses niet noodzakelijk is dat het ontwerp technisch perfect is. Als je kijkt naar het collectionframework van Java dan kan je niet aan een interface zien of het immutable is of niet. Er zit zelfs niet eens een methode op... Ok.. lijkt best wel ernstig. Maar snap ik daardoor dingen niet in de praktijk? En hoe vaak krijg ik bv immutable exceptions? Eigelijk nooit. Door de manier van werken met dit soort structuren weet ik wat mag en wat niet mag. Je hebt dus misschien een iets minder sterk ontwerp... Maar het is wel erg eenvoudig om mee te werken en zorgt niet voor problemen. Een oplossing die in de praktijk te complex is om mee te werken is geen oplossing..

[ Voor 118% gewijzigd door Alarmnummer op 27-03-2005 14:06 ]


Verwijderd

Topicstarter
Uit je tekst leid ik af dat er helemaal geen nette oplossing voor bestaat? Indien dat zo is, ga ik volledig met je akkoord: één grote interface in plaats van een aantal kleine interfaces geeft inderdaad een iets-minder-sterk ontwerp, maar het is 1000x simpeler voor gebruik. Mijn 'oplossing' die ik hierboven is inderdaad onpraktisch.

Na wat generic programming bestudeerd te hebben, leek het mij interessant om mijn interface in stukjes te delen. Algoritmen hebben meestal maar een fractie van de interface nodig. Elke fractie (of een benadering in ieder geval), een familie van functies zeg maar, laat ik dan voorstellen door een aparte interface (cfr. MyUpdatableCollection voor algoritmes die de collecite moeten updaten, MyIterableCollection voor algoritmes die over de elementen in de collecties moeten kunnen itereren, etc.). Hierdoor kan een gebruiker veel sneller en makkelijker een ad-hoc implementatie van een collectie maken, speciaal voor één algoritme: hij moet minder functies implementeren en kan zich concenteren op die functies die door het algoritme gebruikt worden. Dit zal ik dan maar vergeten :)

Eventueel kan ik in de documentatie per algoritme vermelden welke functies voor dit algoritme gebruikt worden. Als de gebruiker een specifieke implementatie wilt voor een algoritme, kan hij de overige functies uit de interface eventueel leeg laten (lege body of RuntimeException): ze worden toch niet gebruikt.

Zijn hier nog andere methodologiën die dit kunnen vergemakkelijken?

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

Alarmnummer

-= Tja =-

Verwijderd schreef op zondag 27 maart 2005 @ 14:22:
Uit je tekst leid ik af dat er helemaal geen nette oplossing voor bestaat?
Hmmm tja... bestaan vind ik zo`n groot woord. Er vast wel iets te bedenken. Maar hoe zou je het in c++ oplossen?
....
Hierdoor kan een gebruiker veel sneller en makkelijker een ad-hoc implementatie van een collectie maken, speciaal voor één algoritme: hij moet minder functies implementeren en kan zich concenteren op die functies die door het algoritme gebruikt worden. Dit zal ik dan maar vergeten :)
Kijk.. als je extreem veel er mee bezig bent dan vraagt zo`n framework uiteraard om verdieping.. maar algemene classes ben ik bijna nooit tot zo in de diepte nodig. Daarom wil ik ook nooit de prijs van complexiteit betalen als er geen toegevoegde waarde in zit.
Eventueel kan ik in de documentatie per algoritme vermelden welke functies voor dit algoritme gebruikt worden. Als de gebruiker een specifieke implementatie wilt voor een algoritme, kan hij de overige functies uit de interface eventueel leeg laten (lege body of RuntimeException): ze worden toch niet gebruikt.
Hmm tja.. voor generieke collection stuf zou ik niet zo moeilijk doen. Een ander probleem is dat jouw interfaces ook geen deel uitmaken van het Collection framework. Dus veel functies (zoals bv bij Collections) zijn ineens niet meer bruikbaar. Ik heb zelf wel eens Collectionframework enhancements erop gehad en ook wel eens wat geschreven bv de NCollections (nullrejectingcollections) en PredicateCollections, maar ben het eigelijk nooit nodig. Collections blijven altijd binnen classes en Collections geef ik niet zo maar vrij (druk er meestal een immutable wrapper omheen als ik ze naar buiten toe zichtbaar maak aangezien ik niet wil dat iedereen aan het roeren is in mijn classes). En functies op die collections zijn imho zaak van de overkoepelende class.

Verwijderd

Topicstarter
Alarmnummer schreef op zondag 27 maart 2005 @ 14:30:
Hmmm tja... bestaan vind ik zo`n groot woord. Er vast wel iets te bedenken. Maar hoe zou je het in c++ oplossen?
Op 1 2 3 kan ik hier ook geen antwoord op geven. Ik heb niet zoveel C++ ervaring. Wat je wel kan is aan de hand van templates functies implementeren, zonder dat je moet overerven van een abstract base class (interface).
Kijk.. als je extreem veel er mee bezig bent dan vraagt zo`n framework uiteraard om verdieping.. maar algemene classes ben ik bijna nooit tot zo in de diepte nodig. Daarom wil ik ook nooit de prijs van complexiteit betalen als er geen toegevoegde waarde in zit.

[...]

Hmm tja.. voor generieke collection stuf zou ik niet zo moeilijk doen. Een ander probleem is dat jouw interfaces ook geen deel uitmaken van het Collection framework.
Ik gebruikte het voorbeeld van de collections hier alleen maar om uit te leggen wat ik bedoelde, louter illustratief dus. Ik ben bezig met een graphlibrary, en dat is in vele opzichten gelijkaardig aan het collections framework. Hier heb je alleen heel veel algoritmen, vandaar dat ik eraan dacht om interfaces te splitsen.

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

Alarmnummer

-= Tja =-

Verwijderd schreef op zondag 27 maart 2005 @ 14:56:
Ik gebruikte het voorbeeld van de collections hier alleen maar om uit te leggen wat ik bedoelde, louter illustratief dus. Ik ben bezig met een graphlibrary, en dat is in vele opzichten gelijkaardig aan het collections framework. Hier heb je alleen heel veel algoritmen, vandaar dat ik eraan dacht om interfaces te splitsen.
Dan zou het handig kunnen zijn. Ik doe het ook zo nu en dan om implementaties te scheiden van interfaces en hierdoor zijn functies breeder in te zetten. Tja... ik denk dat je er zelf het beste antwoord op kunt geven of je de complexiteit nodig bent (soms is die gewoon verdomde handig).

Maar vaak wordt door complexiteit simpele dingen ook complex... en dan geeft een complex systeem meestal nadelen. Neem bv de IO gebeuren van Java. In grote lijnen misschien leuk opgezet maar zo lang simpele dingen complex zijn hebben ze het gewoon niet goed begrepen.

  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Je kunt dit probleem inderdaad niet elegant oplossen in een object oriented omgeving. Als je geen duidelijke hierarchie van interfaces kunt definiëren zul je in de code op een bepaald punt rekening moeten houden met alle combinaties van interfaces als je hulpmethodes op basis van deze combinaties resultaten wilt laten retourneren.

Dit is geen specifiek probleem van Java, maar heeft te maken met het begrip interface in object oriented programming. Een interface is een abstracte eenheid van functionaliteit op basis waarvan je concreet met een object kunt werken. In jouw voorbeeld is echter een combinatie van interfaces pas een eenheid, die je uitdrukt in een concrete klasse. Dan werkt het principe van interfaces niet meer.

Met aspect oriented programming zou je ad-hoc methodes kunnen injecteren met extra gedrag. Iedere combinatie zou je als een apart aspect kunnen zien. In deze situatie is dat echter niet ideaal, omdat je iedere methode die je anders vol if statements zet nu met een aspect moet gaan bedienen. Dat is in principe net zo onderhoudsgevoelig en ook nog eens veel ingewikkelder.

De constructie die je nu voorstelt is een oplossing voor een bepaald probleem neem ik aan. Misschien wil je dat probleem gewoon op een andere manier oplossen. :) Waarom wil je de invoercombinatie in je geretourneerde resultaat terug laten komen, zonder hier specifieke methodes voor te hoeven aan te roepen?

Verwijderd

Topicstarter
misfire schreef op zondag 27 maart 2005 @ 15:08:
De constructie die je nu voorstelt is een oplossing voor een bepaald probleem neem ik aan. Misschien wil je dat probleem gewoon op een andere manier oplossen. :) Waarom wil je de invoercombinatie in je geretourneerde resultaat terug laten komen, zonder hier specifieke methodes voor te hoeven aan te roepen?
De constructie die ik hier gaf was 'zomaar een oplossing, uit gebrek aan beters'. Ik snap even je vraag niet, wat bedoel je met 'zonder hier specifieke methodes voor te hoeven aan te roepen'?

Laat ik een beter voorbeeld geven. Stel ik heb een interface die een graaf (een ADT bestaande uit een verzameling nodes en een verzameling edges tussen die nodes - al dan niet directed). De meeste algoritmen hebben een subset van volgende 'queries' nodig:

1) gegeven een graaf en een node, geef mij een iterator over alle bereikbare nodes
2) gegeven een graaf en een node, geef mij een iterator over alle uitgaande edges
3) gegeven een graaf en een node, geef mij een iterator alle inkomende edges
4) gegeven een graaf, geef mij een iterator over alle nodes
5) gegeven een graaf, geef mij een iterator over alle edges
6) gegeven een graaf en twee nodes, geef mij een iterator over alle edges van node1 naar node2
7) gegeven een graaf, geef mij het aantal nodes
8 ) gegeven een graaf, geef mij het aantal edges
9) ...

Stel ik heb interfaces voor deze functies (al dan niet sommige functies samen in 1 interface, dit terzijde). Nu wil ik een functie maken die een view maakt over de transposed graph, nl. de richting van de edges van de originele graph omkeren.

Java:
1
2
3
4
public Graph transposedView(Graph graph)
{

}


Hier wil ik dus bijvoorbeeld dat het resultaat dezelfde interfaces implementeert als de input, anders verliest mijn resultaat aan functionaliteit.

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

Alarmnummer

-= Tja =-

Signature:
code:
1
public <G extends Graph> G transposedView(G graph)

[ Voor 10% gewijzigd door Alarmnummer op 27-03-2005 16:43 ]


Verwijderd

Topicstarter
Alarmnummer schreef op zondag 27 maart 2005 @ 16:40:
Signature:
code:
1
public <G extends Graph> G transposedView(G graph)
Akkoord, maar wat wil je hiermee zeggen? Ik moet nog altijd de juiste 'G' aanmaken in de body van de functies: een hele resem if-testen, zoals hierboven.

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

Alarmnummer

-= Tja =-

Verwijderd schreef op zondag 27 maart 2005 @ 17:18:
[...]

Akkoord, maar wat wil je hiermee zeggen?
Dat je signature nu correct is :)
Ik moet nog altijd de juiste 'G' aanmaken in de body van de functies: een hele resem if-testen, zoals hierboven.
Idd.. maar ik zou geen oplossing hiervoor weten... of jij bouwt een stuk polymorfisme in bij je classes (bv makeNewGraphOfSameClass) of je gaat wat pielen met bv if else statements. Maar ergens zal een stuk code gemaakt moet worden die op een bepaald type een ander type aanmaakt. Je hebt dan verschillende oplossingen... maar geen van allen is mooi.. visitors... if then else... polymorfisme (is in dit geval minder mooi aangezien je versnippering krijgt van een stuk logica dat eigelijk maar hoort bij een functie). Of je kunt natuurlijk nog gaan pielen met reflectie.. (en dan met de nadruk op pielen)

[edit]
Je zou eventueel ook nog kunnen pielen met een map met GraphFactories. Als key gebruik je dan de Graph.class. Moet je er wel voor zorgen dat je alle GraphFactories bij het opstarten van het systeem registreerd.

[ Voor 12% gewijzigd door Alarmnummer op 27-03-2005 18:10 ]

Pagina: 1