[Java] Immutable objecten

Pagina: 1
Acties:

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Immutable objecten zijn veel makkelijker om mee te werken dan mutable objecten omdat je veel minder op je consistentie van objecten hoeft te letten. Maar sommige objecten die kan ik niet in 1 slag maken (dwz met een constructor vullen bv).

vb:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
class PersoonList{

    private ArrayList _persoonList = new ArrayList();   

    public PersoonList(){           
    }

    public void add(Persoon persoon){
        //checks ed.

        _persoonList.add(persoon);
    }
}

Een persoonList zou je bv vanuit XML kunnen vullen met Personen maar zo gauw je klaar bent met het inlezen van PersoonList dan zou je misschien willen dat het niet meer mogelijk is dat een ander object Personen kan toevoegen. (Geen vragen waarom aub... het gaat even om het immutable maken).

Ik heb nu maar een interface geschreven:
code:
1
2
3
4
5
6
public interface Immutable{
    
    public boolean isImmutable();
    
    public void makeImmutable();
}

en exception:
code:
1
2
3
4
5
6
7
8
9
public class ImmutableException extends RuntimeException{
    
    public ImmutableException(){
    }
    
    public ImmutableException(String message){
        super(message);
    }
}

en deze bij PersoonList geimplementeerd.
code:
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
class PersoonList implements Immutable{

    private ArrayList _persoonList = new ArrayList();   
    private boolean _immutable = false;

    public PersoonList(){           
    }

    public void add(Persoon persoon){
        if(_immutable){
            throw new ImmutableException();     
        }           
    
        //checks ed.

        _persoonList.add(persoon);
    }

    public boolean makeImmutable(){
        if(_immutable){
            throw new ImmutableException(
                 "persoonList already is immutable"
            );      
        }   

        _immutable = true;
    }

    public boolean isImmutable(){
        return _immutable;  
    }
}

Zo gauw je nu klaar bent met het inlezen van alle personen en er dus geen personen meer aan de lijst toegevoegd hoeven te worden, makeImmutable aanroepen. Het object is nu immutable en als een andere classe er nog een persoon aan probeerd toe te voegen krijg je dus een ImmutableException.


Ik wil graag weten of jullie deze problemen wel eens zijn tegengekomen en wat jullie van dit idee vinden.

ps: geen vragen over waarom de PersoonList immutable moet worden, het is alleen een voorbeeld ;)

[edit] layout + knipoog :P

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
code:
1
_persoonList.add(persoon)

NullPointerException :P ;) .

Even inhoudelijk: ik vind het zelf eigenlijk niet zo fraai als een klasse methoden biedt die om wat voor reden dan ook niet aangeroepen mogen worden in een specifieke situatie of zelfs altijd (zoals bijvoorbeeld remove in een Iterator).

Als je het netjes wilt doen zou je het volgende kunnen doen:

een immutable interface
code:
1
2
3
interface Person {
    String getName();
}

een uitgebreidere mutable variant:
code:
1
2
3
interface MutablePerson extends Person {
    void setName(String);
}

Je kunt nu MutablePersons aanmaken, maar het is uiteraard niet veilig om dan als 'Person' een 'MutablePerson' implementatie op te leveren omdat je dan simpelweg kan casten. Daarom heb je dit nodig:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ImmutablePersonDelegate implements Person {
     
     private Person _delegate;

     public Person(Person person) {
       super();
       _delegate = person;
     }

     public String getName() {
       return _delegate.getName();
     }
}

Het is flink wat werk, maar ja: als je zulke sterke immutable requirements hebt moet je dat er ook maar voor over hebben ;) . Voordeel hiervan is dat je twee duidelijk gescheiden interfaces hebt en een strak design...

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Overigens: de Person die je meegeeft aan de delegate zal dus een MutablePerson zijn.... Voor de delegate is dus echter niet noodzakelijk, daar heb ik hem zo algemeen mogelijk gekozen.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


Verwijderd

Kan je niet gewoon een ImmutablePersonList class definieren (subclassen van PersonList), en dan de addPerson method private maken?
Met behulp van een copyconstructor kan je dan een ImmutablePersonList maken van je PersonList, waaraan je dan geen personen meer kan toevoegen...

Eventueel performanceverlies ivm. kopieren array voor lief nemen natuurlijk... :'(

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Op zaterdag 23 maart 2002 13:54 schreef mbravenboer het volgende:
code:
1
_persoonList.add(persoon)

NullPointerException :P ;) .
vandaar ' //checks ed' ;)
Even inhoudelijk: ik vind het zelf eigenlijk niet zo fraai als een klasse methoden biedt die om wat voor reden dan ook niet aangeroepen mogen worden in een specifieke situatie of zelfs altijd (zoals bijvoorbeeld remove in een Iterator).
Ben ik met je eens. Je kunt mijn oplossing niet compile time gebriken, alleen runtime.
Het is flink wat werk, maar ja: als je zulke sterke immutable requirements hebt moet je dat er ook maar voor over hebben ;) . Voordeel hiervan is dat je twee duidelijk gescheiden interfaces hebt en een strak design...
Ik vind het inderdaad een mooi design. Ik ga er eens even heel goed naar kijken. Ik vind het heel leuk die conversie naar MutablePersoon onmogelijk te maken door ImmutablePersonDelegate

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Akhorahil: Kan je niet gewoon een ImmutablePersoonList class definieren (subclassen van PersoonList), en dan de addPerson method private maken?
Het is niet toegestaan om methoden minder beschermd te maken in sub-classes. Dit is heel logisch omdat een ImmutablePersonList als subclass van PersonList ook als een PersonList door het leven kan gaan.

Als je de methode addPerson pas toevoegd in de immutable variant heeft heel de oplossing niet zoveel nut meer ;) .

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
welkom op het forum trouwens Akhorahil :)

Verwijderd

hehe... Mijn java kennis is ook niet meer wat het geweest is... Je zou idd. gewoon een ImmutablePersonList naar een PersonList kunnen casten :z

In C++ kan dit nl. wel (maar goed, dat is offtopic)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
misschien beetje een offtopic vraag, maar heeft indirect met die immutable objecten te maken.

Ik heb vaak behoefte aan om van a naar b te komen en van b naar a. Bijvoorbeeld:
RecordType ->RecordField en RecordField -> RecordType. Want ieder recordField hoort maar bij 1 recordType en in ieder RecordType zitten 1 of meerdere RecordFields.

Ik doe het op dit moment als volgt:
Ik maak eerst een RecordType aan zonder RecordFields. Daarna begin ik alle RecordFields in te lezen. Ze krijgen dan bij de contructor het RecordType mee waarbij ze horen. En daarna worden ze toegevoegd aan het RecordType. Als ik klaar ben met de laatste RecordField dan kan het RecordType op slot worden gezet want ik wil (om bepaalde redenenen) niet dat iemand anders na afloop nog velden loopt toe te voegen.

Ik begin intussen steeds meer te twijfelen aan deze tactiek omdat ten 1e geen recordField gemaakt kan worden zonder RecordType. Dus het is minder reusable. Het nodigt nog tot iets anders uit, en dat is dat een klasse onder aan een keten, helemaal terug gaat in een keten om allerlei operaties uit te voeren. Dus als dit je object links zijn:
a<->b<->c

Dan zou je kunnen zeggen. c.getB().getA().mooieAFunctie();

Hierdoor kun je dus allerlei package afhankelijkheden krijgen. En c niet goed meer kunnen gebruiken zonder b en a. Zoals je ziet is dit inderdaad niet een al te beste aanpak.

Maar op welke manier moet c erachter komen dat hij in b zit als hij geen link meer naar b heeft. Dus. hoe moet een recordField erachter komen in welk recordType hij zit als hij geen link heeft naar zijn recordType?

Dit is trouwens bij mij een veel voorkomend probleem. Ik heb heel veel lijst structuren waarin het handig is als het 'element' weet wie zijn ouder is (en waarbij ieder element ook maar 1 parent lijst heeft). Maar zoals je ziet zet is dus vraagtekens neer bij deze tactiek.

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

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 09-09 11:02
Ik weet niet wat je precies wilt met die RecordType en RecordFields (zijn ook geen standaardklassen voor zover ik weet) maar ik wil toch een poging tot repliek wagen.
Op zaterdag 23 maart 2002 15:29 schreef Alarmnummer het volgende:
Ik begin intussen steeds meer te twijfelen aan deze tactiek omdat ten 1e geen recordField gemaakt kan worden zonder RecordType.
Misschien is dat helemaal geen slecht idee. Als je dat wel een slecht idee vind, kan je natuurlijk een lege constructor definiëren (of een constructor met een null-argument).
Dus het is minder reusable. Het nodigt nog tot iets anders uit, en dat is dat een klasse onder aan een keten, helemaal terug gaat in een keten om allerlei operaties uit te voeren.
Tja, daar ontkom je niet aan, tenzij je van te voren weet dat (in jou voorbeeld) 'c' een speciale link met 'a' heeft en je 'a' dus in de constructor meegeeft bij het initialiseren van 'c'. Dan heeft 'c' dus ook een link meer nodig met 'b'.
Hoe moet een recordField erachter komen in welk recordType hij zit als hij geen link heeft naar zijn recordType?
Je zegt zelf dat RecordTypes en RecordFields one-to-many aan elkaar gekoppeld zijn, dus als ze elkaar willen benaderen zullen ze dus (direct of indirect) een link naar elkaar moeten hebben. Daar ontkom je mijns inziens niet aan en dat geeft ook niet, want als je een functie van een ander object wil aanroepen, zal dat object toch aan een bepaalde specificatie moeten voldoen en gaat het verhaal van de onnodige afhankelijkheden sowieso al niet meer op.

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Ik heb mijn probleem opgelost en daarbij ook nog een ander.

Ik heb een builder object gemaakt die alle logica bevat omtrent het toevoegen van elementen en het goedkeuren ervan. Als ik klaar ben met het vullen van het builder object, dan maak ik bv een PersoonList aan met in de constructor de builder. In de constructor van de PersoonList worden alle elementen uit de builder gehaald en geplaatst in de persoonList. Verder bezit de PersoonList dus geen add methode dus je kunt nu compile time het probleem aanpakken.

Een bijkomend voordeel is dat de PersoonList veel schoner blijft omdat het niet meer de add/controle logica bezit, want dit is allemaal verhuist naar de builder.

Ik ben dus helemaal blij :)
Pagina: 1