Is het beter om een ​​onveranderlijke kaart of een kaart terug te sturen?

Stel dat ik een methode schrijf die een Kaart. Bijvoorbeeld:

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

Na er een tijdje over nagedacht te hebben, heb ik besloten dat er geen reden is om deze kaart te wijzigen als deze eenmaal is gemaakt. Daarom wil ik een ImmutableMap.

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

Moet ik het retourtype als een generieke kaart laten staan, of moet ik specificeren dat ik een onveranderlijke kaart retourneer?

Aan de ene kant is dit precies waarom interfaces zijn gemaakt voor; om de implementatiedetails te verbergen.

Aan de andere kant, als ik het zo laat, zouden andere ontwikkelaars het feit kunnen missen dat dit object onveranderlijk is. Ik zal dus geen belangrijk doel van onveranderlijke objecten bereiken; om de code duidelijker te maken door het aantal objecten dat kan veranderen te minimaliseren. Erger nog, na een tijdje kan iemand proberen dit object te veranderen, en dit zal resulteren in een runtime-fout (de compiler zal er niet voor waarschuwen).


Antwoord 1, autoriteit 100%

  • Als je een openbare API schrijft en dat onveranderlijkheid een belangrijk aspect van je ontwerp is, zou ik het zeker expliciet maken, ofwel door de naam van de methode duidelijk aan te geven dat de geretourneerde kaart onveranderlijk is of door het concrete type van de kaart terug te geven. Het vermelden in de javadoc is naar mijn mening niet voldoende.

    Omdat je blijkbaar de Guava-implementatie gebruikt, heb ik naar het document gekeken en het is een abstracte klasse, dus het geeft je een beetje flexibiliteit over het daadwerkelijke, concrete type.

  • Als je een interne tool/bibliotheek schrijft, wordt het veel acceptabeler om gewoon een gewone Mapterug te sturen. Mensen zullen weten wat de code is die ze binnenhalen, of ze zullen er in ieder geval gemakkelijk toegang toe hebben.

Mijn conclusie zou zijn dat expliciet goed is, laat dingen niet aan het toeval over.


Antwoord 2, autoriteit 56%

U zou ImmutableMapals uw retourtype moeten hebben. Mapbevat methoden die niet worden ondersteund door de implementatie van ImmutableMap(bijv. put) en zijn gemarkeerd als @deprecatedin ImmutableMap.

Het gebruik van verouderde methoden resulteert in een compilerwaarschuwing & de meeste IDE’s zullen waarschuwen wanneer mensen proberen de verouderde methoden te gebruiken.

Deze geavanceerde waarschuwing heeft de voorkeur boven runtime-uitzonderingen als eerste hint dat er iets mis is.


Antwoord 3, autoriteit 20%

Aan de andere kant, als ik het zo laat, zouden andere ontwikkelaars het feit kunnen missen dat dit object onveranderlijk is.

Dat moet je vermelden in de javadocs. Ontwikkelaars lezen ze wel, weet je.

Ik zal dus geen belangrijk doel van onveranderlijke objecten bereiken; om de te maken
code duidelijker door het aantal objecten dat kan veranderen te minimaliseren.
Erger nog, na een tijdje kan iemand proberen dit object te veranderen,
en dit zal resulteren in een runtime-fout (de compiler waarschuwt niet)
erover).

Geen enkele ontwikkelaar publiceert zijn code ongetest. En wanneer hij het test, krijgt hij een Exception gegooid waarbij hij niet alleen de reden ziet, maar ook het bestand en de regel waar hij naar een onveranderlijke kaart probeerde te schrijven.

Houd er echter rekening mee dat alleen de Mapzelf onveranderlijk is, niet de objecten die het bevat.


Antwoord 4, autoriteit 14%

Als ik het zo laat, missen andere ontwikkelaars misschien het feit dat dit object onveranderlijk is

Dat is waar, maar andere ontwikkelaars zouden hun code moeten testen en ervoor zorgen dat deze gedekt wordt.

Toch heb je nog 2 opties om dit op te lossen:

  • Javadoc gebruiken

    @return a immutable map
    
  • Kies een beschrijvende methodenaam

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    Voor een concrete use case kun je de methoden zelfs beter benoemen. Bijv.

    public Map<String, Integer> getUnmodifiableCountByWords()
    

Wat kun je nog meer doen?!

Je kunt een

. retourneren

  • kopiëren

    private Map<String, Integer> myMap;
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    Deze aanpak moet worden gebruikt als u verwacht dat veel klanten de kaart zullen wijzigen en zolang de kaart maar een paar items bevat.

  • CopyOnWriteMap

    kopiëren op schrijfverzamelingen worden meestal gebruikt als je te maken hebt met
    gelijktijdigheid. Maar het concept helpt u ook in uw situatie, aangezien a
    CopyOnWriteMap maakt een kopie van de interne gegevensstructuur bij een mutatieve bewerking (bijvoorbeeld toevoegen, verwijderen).

    In dit geval heb je een dunne wikkel rond je kaart nodig die alle methode-aanroepen naar de onderliggende kaart delegeert, behalve de mutatieve bewerkingen. Als een mutatiebewerking wordt aangeroepen, wordt er een kopie van de onderliggende kaart gemaakt en worden alle verdere aanroepen naar deze kopie gedelegeerd.

    Deze aanpak moet worden gebruikt als u verwacht dat sommige klanten de kaart zullen wijzigen.

    Java heeft helaas niet zo’n CopyOnWriteMap. Maar misschien vindt u een derde partij of implementeert u deze zelf.

Ten slotte moet je er rekening mee houden dat de elementen op je kaart nog steeds veranderlijk kunnen zijn.


Antwoord 5, autoriteit 11%

Retourneer absoluut een onveranderlijke kaart, met als rechtvaardiging:

  • De handtekening van de methode (inclusief het retourtype) moet zelfdocumenterend zijn. Opmerkingen zijn als klantenservice: als uw klanten erop moeten vertrouwen, is uw primaire product defect.
  • Of iets een interface of een klasse is, is alleen relevant bij het uitbreiden of implementeren ervan. Gegeven een instantie (object), 99% van de tijd weet de clientcode niet of iets een interface of een klasse is. Ik nam eerst aan dat ImmutableMap een interface was. Pas nadat ik op de link had geklikt, realiseerde ik me dat het een klas is.

Antwoord 6, autoriteit 7%

Het hangt af van de klasse zelf. Guava’s ImmutableMapis niet bedoeld als een onveranderlijke weergave van een veranderlijke klasse. Als je klasse onveranderlijk is en een structuur heeft die in feite een ImmutableMapis, maak dan het retourtype ImmutableMap. Als uw klasse echter veranderlijk is, doe dat dan niet. Als je dit hebt:

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

Guava kopieert de kaart elke keer. Dat is traag. Maar als internalMapal een ImmutableMapwas, dan is het prima.

Als je je klas niet beperkt tot het retourneren van ImmutableMap, dan zou je in plaats daarvan Collections.unmodifiableMapkunnen retourneren zoals:

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

Merk op dat dit een onveranderlijke weergaveis op de kaart. Als internalMapverandert, verandert ook een gecachte kopie van Collections.unmodifiableMap(internalMap). Ik geef er echter nog steeds de voorkeur aan.


7

Dit is misschien wel een kwestie van mening, maar het betere idee hier is om een ​​interface voor de kaartklasse te gebruiken. Deze interface hoeft niet expliciet te zeggen dat het onveranderlijk is, maar het bericht zal hetzelfde zijn als u geen van de zettermethoden van de ouderklasse in de interface blootstelt.

Bekijk het volgende artikel:

Andy Gibson

Other episodes