2016-08-06 25 views
0

Ich erschaffe Poker-Simulator für Lernzwecke, aber Probleme haben Deck Klasse unveränderlich.Array unveränderlich machen

public class Deck { 

    private final Card[] cards; 
    private int cardCount; 

    public Deck(@NotNull Card[] c) { 
      this.cards = Arrays.copyOf(c, c.length); 
      this.cardCount = c.length - 1; 
    } 

    public Deck shuffle() { 
      // shuffle logic 
      return new Deck(this.cards); 
    } 

    public Card pop() { 
      return this.cards[cardCount--]; 
    } 
    // other methods 
} 

Dealer Klasse hält Deck Referenz und Logik zu tun implementiert.

public class Dealer { 

    private final Deck deck; 

    public Dealer(@NotNull Deck d) { 
      this.deck = d; 
    } 

    public void deal(@NotNull Holder<Card> h) { 
      h.hold(deck.pop()); 
    } 
    // other methods 
} 

Wie Sie Deck sehen kann, ist nicht völlig unveränderlich, weil es cardCount hält, die jedes Mal aktualisiert wird, wenn pop genannt wird. ! Hinweis - cardCount ist nicht nach außen sichtbar, auch Card Klasse ist unveränderlich.

Frage 1: Wie wirkt sich dieses Design auf Deck Unveränderlichkeit?

Frage 2: Was könnte zukünftige Auswirkungen haben?

Frage 3: Ist es wert, Deck Klasse völlig unveränderlich zu machen? Wenn ja, wie?

Vielen Dank für Ihre Hilfe.

+1

Es ist nicht nur der Zähler, der Ihre Klasse veränderbar macht, sondern auch das Array, da Arrays von Natur aus änderbar sind, obwohl Ihre Referenz "final" ist. –

+0

Ich mache defensive Kopien für Arrays. –

+0

Das eigentliche Problem hier ist "Pop", das Sie als eine mutative Operation entworfen haben. Kein internes Fiedeln wird Ihr 'Deck' unveränderlich machen, solange es noch eine' Pop'-Methode mit der gegebenen Semantik unterstützen muss. – user2357112

Antwort

2

Dies ist einer der wenigen Fälle, in denen die Unveränderlichkeit nicht hilft. Sie scheinen einen veränderlichen Gemeinschaftszustand zu benötigen, da dies der ganze Zweck dieser Deck ist.

Um es unveränderlich zu machen, würde bedeuten, dass Sie ein neues Deck mit den restlichen Karten jedes Mal erstellen müssen, wenn jemand einen herausspringt. Aber Sie haben keine Kontrolle über die alte Deck (vor der Top-Karte Entfernung), so kann dies noch von einigen Kunden referenziert werden - was den gesamten Zweck des Spiels, meiner Meinung nach, besiegt.

Mit Ihrer aktuellen Implementierung können Sie auf Probleme mit mehreren Threads stoßen. Ziehen Sie in Betracht, Ihre Klasse threadsicher zu machen. Mit einem java.util.Stack anstelle eines Arrays mit einem Zähler wäre ein guter erster Versuch.

+0

Ich sehe jetzt. Wenn das gleiche "Deck" von verschiedenen "Händlern" verwendet wird, dann werde ich in Schwierigkeiten geraten. Aber ich sehe nicht, wie 'Stack' mir helfen würde. Wenn ein "Dealer" den Stapel durch das Auflegen einer Karte aktualisiert, kann ein anderer nicht mehr verwenden.Die einzige Lösung dafür ist, dass jeder "Dealer" eine eigene Kopie des "Decks" hat. –

+0

@DennisGrinch yep ... und nicht nur für jeden Händler. Da das Deck veränderbar ist und nur Karten entfernt, benötigen Sie jedes Mal, wenn Sie ein neues Spiel starten, eine neue Deck-Instanz, auch wenn es mit demselben Händler –

1

Das größte Problem ist, dass Sie eine Mutationsmethode für die Deck-Klasse haben. Sie MÜSSEN in der Lage sein, die Karten, die Sie während eines Spiels in einem Deck haben, mutieren zu können, aber Sie müssen das eigentliche Deck-Objekt dafür nicht mutieren. Angenommen, Sie haben eine unveränderliche Kartenklasse und eine unveränderliche Deckklasse. Das Deck wird mit einer vollständigen Anzahl von Karten initialisiert. Wenn Sie ein Spiel spielen möchten, fragen Sie nach einer Liste der Karten, mit denen der Dealer interagieren, modifizieren usw. kann. Daher muss die Deck-Klasse nur eine Kopie der internen Liste herausgeben. Auf diese Weise ändert der Dealer nicht die interne Liste und das Deck ist bereit für ein neues Spiel, wenn es angefordert wird. Es spielt keine Rolle, dass die kopierte Liste Verweise auf dieselben Karteninstanzen wie die interne Deck-Klasse enthält, da die Karten auch unveränderbar sind .

Da auch kein Zustand geteilt wird und das Deck nicht mehr änderbar ist, ist die Threadsicherheit kein Problem mehr. Ein weiterer Vorteil bei der Verwendung einer Liste von Karten für den Dealer ist, dass sie über die Collections-Klasse in Sortierung integriert sind (wenn Sie Kartengerät vergleichbar hatten), Shuffle usw..

Edit: hat nach der Lektüre Ihrer Kommentare:

Wenn Sie das Deck wollen Zustand halten, dann können Sie es nicht unveränderlich sein. Es kann auf verschiedene Arten implementiert werden.Obwohl eine ArrayList in fast allen Fällen eine relativ große Datenstruktur im Vergleich zu einem Array aufweist, ist ihre Verwendung in Bezug auf die Leistung besser als eine LinkedList und ist besser als ein Array für Sicherheit und Benutzerfreundlichkeit. Wenn sehr, sehr hohe Leistung erforderlich ist, können Sie mit einem primitiven Array (vielleicht) besser werden, aber die Collections API ist ziemlich optimiert und die JVM ist auch extrem gut darin, Code on the fly zu optimieren. In meiner bisherigen beruflichen Erfahrung muss ich noch auf einen Fall eingehen, in dem ich ein Array einfach aufgrund der Performance über eine Liste setzen würde. Im Allgemeinen würde ich sagen, dass es sich um eine vorzeitige Optimierung handelt (hüte dich davor). Obwohl es in einem abstrakten Sinn Sinn macht, einen Stack über eine ArrayList zu verwenden, würde ich argumentieren, dass die Bequemlichkeit der eingebauten Shuffle-Methode für Listen (in Collections) diese überschreiben würde.

die gleichen Kartenobjekte wie oben, die wandelbar Deck wie folgt (mit einer Liste) aussehen könnte:

public class EmptyDeckException extends RuntimeException { 

    static final long serialVersionUID = 0L; 

    public EmptyDeckException(String message) { 
     super(message); 
    } 
} 

public class Deck { 

    private final List<Card> cards = new ArrayList<>(); 

    public Deck() { 
     for (Card.Suit suit : Card.Suit.values()) { 
      for (Card.Value value : Card.Value.values()) { 
       cards.add(new Card(suit, value)); 
      } 
     } 
    } 

    public void shuffle() { 
     Collections.shuffle(cards); 
    } 

    public Card pop() { 
     if (!cards.isEmpty()) { 
      return cards.remove(cards.size() - 1); 
     } 
     throw new EmptyDeckException("The deck is empty"); 
    } 
} 

Und wenn ein Stapel auf wird darauf bestanden, dann könnte es so aussehen:

+0

Tnks für Eingabe ist, aber 'ArrayList' ist weit über aufgeblähte Datenstruktur dafür . Ich möchte "Deck" als Datenstruktur implementieren, die Karten enthält und nur "Pop" - und "Shuffle" -Methoden verwendet. –

+0

Das ist in Ordnung, aber wenn Sie möchten, dass Ihr Deck den aktuellen Zustand behält, kann es nicht als unveränderlich betrachtet werden. Warum sagst du auch, dass es aufgebläht ist? Ist es wegen der Anzahl anderer Methoden oder weil Sie denken, dass es nicht performant ist? –

+0

Weil 'ArrayList' zum effizienten Speichern, Hinzufügen und Entfernen von Elementen dient. Aber mein "Deck" braucht nicht die ganze Funktionalität. Verwenden Sie besser "Stack" dafür, aber auch, dass es nicht die richtige Datenstruktur ist, verursachen Sie "Push" und dann "Pop" -Elemente. Auf der anderen Seite, wenn du Poker spielst, hast du ein Kartenspiel, du mischt Karten und nimmst eins nach dem anderen von oben. –

1

Meiner Meinung nach hängt die Veränderlichkeit von zwei Dingen ab: Wie viele Instanzen würden Sie benötigen, wenn das Objekt unveränderbar wäre und ob eine unveränderbare Kopie jemals wiederverwendet werden würde.

Die erste ist einfach, Sie müssen nicht jede Permutation eines Decks machen, Sie können einfach eine neue machen, wenn es nötig ist, das ist also nichts, das Sie davon abhält, es unveränderlich zu machen.

Wo ich anfange, ein Problem zu sehen, ist die Wiederverwendbarkeit. Sie machen ein Deck unveränderlich, aber wenn die Unveränderlichkeit nur dazu dient, ein brandneues Objekt bereitzustellen und das Alte wegzuwerfen, erscheint es logischer, das Objekt einfach an Ort und Stelle zu mutieren und keine unnötigen zu erzeugen.

Ich sehe jedoch eine eindeutige Verwendung von unveränderlichen Karten; Es wird immer nur 54 von ihnen geben und sie können definitiv wiederverwendet werden. Also, was ich tun würde, ist speichern einen Cache dieser Karten in einem unveränderlichen List oder Set:

private static final List<Card> CARDS = Collections.unmodifiableList(new ArrayList<Card>() {{ 
    //store all your cards, can be done without double-brace initialiation 
}}); 

Von dort (im Zusammenhang mit der Innenseite Deck ist), wir eine Kopie dieser List ohne kostet viel Speicher machen kann überhaupt - die Card Instanzen sind identisch, da Java "pass-by-value-of-reference" ist.

//Note: The left hand side is purposefully LinkedList and not List, you'll see why 
private final LinkedList<Card> shuffledDeck = new LinkedList<>(); 

public Deck() { 
    this.shuffle(); 
} 

public Deck shuffle() { 
    this.shuffledDeck.clear(); //wipe out any remaining cards 
    this.shuffledDeck.addAll(CARDS); //add the whole deck 
    Collections.shuffle(this.shuffledDeck); //Finally, randomize 
    return this; 
} 

public Card pop() { 
    return this.shuffledDeck.pop(); //Thanks Deque, which LinkedList implements 
} 

Dies lässt Sie mit dem gespeicherten Speicher aus machen Card unveränderlich, während Sie nicht zahlreiche Deck Objekte kosten.