Ich erstelle ein Brettspiel (wie Schach) in Java, wo jedes Stück seine eigene Art ist (wie Pawn
, Rook
etc.). Für den GUI-Teil der Anwendung benötige ich für jedes dieser Teile ein Bild. Da denkt das ja gerne
rook.image();
Verstößt die Trennung von Benutzeroberfläche und Geschäftslogik, erstelle ich für jedes Stück einen anderen Präsentator und ordne dann die Stücktypen den entsprechenden Präsentatoren zu
private HashMap<Class<Piece>, PiecePresenter> presenters = ...
public Image getImage(Piece piece) {
return presenters.get(piece.getClass()).image();
}
So weit, ist es gut. Ich spüre jedoch, dass ein umsichtiger OOP-Guru beim Aufrufen einer getClass()
Methode die Stirn runzelt und die Verwendung eines Besuchers zum Beispiel so vorschlägt:
class Rook extends Piece {
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitRook(this);
}
}
class ImageVisitor implements PieceVisitor<Image> {
@Override
public Image visitRook(Rook rook) {
return rookImage;
}
}
Ich mag diese Lösung (danke, Guru), aber sie hat einen wesentlichen Nachteil. Jedes Mal, wenn der Anwendung ein neuer Stücktyp hinzugefügt wird, muss der PieceVisitor mit einer neuen Methode aktualisiert werden. Ich möchte mein System als Brettspiel-Framework verwenden, in dem neue Teile durch einen einfachen Prozess hinzugefügt werden können, bei dem der Benutzer des Frameworks nur die Implementierung des Teils und seines Präsentators bereitstellt und es einfach in das Framework einfügt. Meine Frage: Gibt es eine saubere OOP Lösung ohne instanceof
, getClass()
etc. , die für diese Art von Erweiterbarkeit ermöglichen würden?
quelle
Antworten:
Ja da ist.
Lass mich dich das fragen. In Ihren aktuellen Beispielen finden Sie Möglichkeiten zum Zuordnen von Objekttypen zu Bildern. Wie löst dies das Problem, dass ein Teil bewegt wird?
Eine wirkungsvollere Technik als nach dem Typ zu fragen, ist, Tell zu folgen , nicht zu fragen . Was ist, wenn jedes Teil eine
PiecePresenter
Schnittstelle hat und so aussieht:Die Konstruktion würde ungefähr so aussehen:
Die Verwendung würde ungefähr so aussehen:
Hier geht es darum, keine Verantwortung dafür zu übernehmen, etwas zu tun, wofür andere Dinge verantwortlich sind, indem sie nicht danach fragen oder darauf basierende Entscheidungen treffen. Halten Sie stattdessen einen Verweis auf etwas bereit, das weiß, was mit etwas zu tun ist, und weisen Sie es an, etwas zu tun, was Sie wissen.
Dies ermöglicht Polymorphismus. Sie kümmern sich nicht darum, womit Sie sprechen. Es ist dir egal, was es zu sagen hat. Sie kümmern sich nur darum, dass es das kann, was Sie tun müssen.
Ein gutes Diagramm , das diese in getrennten Schichten hält, folgt sagen-nicht-fragen, und zeigt , wie man nicht Paar Schicht zu Unrecht ist die Schicht dies :
Es wird eine Use-Case-Ebene hinzugefügt, die wir hier nicht verwendet haben (und die wir sicherlich hinzufügen können), aber wir folgen demselben Muster, das Sie in der unteren rechten Ecke sehen.
Sie werden auch feststellen, dass Presenter keine Vererbung verwendet. Es nutzt Komposition. Vererbung sollte der letzte Ausweg sein, um Polymorphismus zu erreichen. Ich bevorzuge Designs, die Komposition und Delegation bevorzugen. Es ist ein bisschen mehr Tastatur-Eingabe, aber es ist viel mehr Leistung.
quelle
Was ist damit:
Ihr Modell (die Abbildung Klassen) haben eine gemeinsame Methoden, die Sie möglicherweise auch in anderen Kontext benötigen:
Die Bilder, die zum Anzeigen einer bestimmten Figur verwendet werden sollen, erhalten Dateinamen anhand eines Namensschemas:
Anschließend können Sie das entsprechende Image laden, ohne auf Informationen zu den Java-Klassen zugreifen zu müssen.
Ich denke, Sie sollten sich nicht so sehr auf den Unterricht konzentrieren . Denken Sie lieber in Geschäftsobjekten .
Und die generische Lösung ist ein Mapping jeglicher Art. IMHO besteht der Trick darin, diese Zuordnung vom Code zu einer Ressource zu verschieben, die einfacher zu warten ist.
In meinem Beispiel wird dieses Mapping gemäß Konvention durchgeführt, das recht einfach zu implementieren ist und das es vermeidet, sichtbezogene Informationen in das Geschäftsmodell einzufügen . Auf der anderen Seite könnte man es als "verstecktes" Mapping betrachten, da es nirgendwo zum Ausdruck kommt.
Eine andere Möglichkeit besteht darin, dies als separaten Geschäftsfall mit eigenen MVC-Layern einschließlich eines Persistenz-Layers zu betrachten, der das Mapping enthält.
quelle
Ich würde eine separate UI / View-Klasse für jedes Stück erstellen, die die visuellen Informationen enthält. Jede dieser Teilsichtklassen hat einen Zeiger auf ihr Modell / Geschäftsgegenstück, der die Position und die Spielregeln des Teils enthält.
Also nimm einen Bauern zum Beispiel:
Dies ermöglicht eine vollständige Trennung von Logik und Benutzeroberfläche. Sie können den Zeiger der Logikfigur an eine Spielklasse übergeben, die die Bewegung der Figuren handhaben würde. Der einzige Nachteil ist, dass die Instanziierung in einer UI-Klasse erfolgen müsste.
quelle
Piece* p
. Woher weiß ich, dass ich ein erstellen mussPawnView
, um es anzuzeigen, und nicht einRookView
oderKingView
? Oder muss ich sofort eine begleitende Ansicht oder einen Präsentator erstellen, wenn ich ein neues Stück erstelle? Das wäre im Grunde die Lösung von @ CandiedOrange mit den umgekehrten Abhängigkeiten. In diesem FallPawnView
könnte der Konstruktor auch a nehmenPawn*
, nicht nur aPiece*
.Ich würde mich dem nähern, indem ich
Piece
generisch mache , wobei sein Parameter der Typ einer Aufzählung ist, die den Typ eines Stücks identifiziert, wobei jedes Stück einen Verweis auf einen solchen Typ hat. Dann könnte die Benutzeroberfläche eine Karte aus der Aufzählung wie zuvor verwenden:Dies hat zwei interessante Vorteile:
Erstens gilt dies für die meisten statisch typisierten Sprachen: Wenn Sie Ihr Board mit der Art der Figur auf xpect parametrieren, können Sie nicht die falsche Art der Figur in das Board einfügen.
Zweitens und vielleicht interessanter, wenn Sie in Java (oder anderen JVM-Sprachen) arbeiten, sollten Sie beachten, dass jeder Aufzählungswert nicht nur ein unabhängiges Objekt ist, sondern auch eine eigene Klasse haben kann. Dies bedeutet, dass Sie Ihre Objekttypen als Strategieobjekte verwenden können, um das Verhalten des Objekts zu bestimmen:
(Offensichtlich müssen die tatsächlichen Implementierungen komplexer sein, aber Sie haben hoffentlich die Idee)
quelle
Ich bin ein pragmatischer Programmierer und es ist mir wirklich egal, was saubere oder schmutzige Architektur ist. Ich glaube Anforderungen und es sollte einfach gehandhabt werden.
Ihre Anforderung ist, dass Ihre Schach-App-Logik auf verschiedenen Präsentationsebenen (Geräten) wie im Web, in mobilen Apps oder sogar in Konsolen-Apps dargestellt wird, sodass Sie diese Anforderungen unterstützen müssen. Sie können es vorziehen, sehr unterschiedliche Farben und Einzelbilder auf jedem Gerät zu verwenden.
Wie Sie gesehen haben, sollte der Presenter-Parameter auf jedem Gerät (Presentation Layer) unterschiedlich übergeben werden. Das heißt, Ihre Präsentationsebene entscheidet, wie jedes Stück dargestellt wird. Was ist an dieser Lösung falsch?
quelle
Es gibt eine andere Lösung, mit der Sie die Benutzeroberfläche und die Domänenlogik vollständig abstrahieren können. Ihr Board sollte Ihrer UI-Ebene ausgesetzt sein, und Ihre UI-Ebene kann entscheiden, wie Teile und Positionen dargestellt werden sollen.
Um dies zu erreichen, können Sie Fen String verwenden . Die Fen-Zeichenfolge enthält im Grunde genommen Informationen zum Board-Status und gibt aktuelle Stücke und ihre Positionen an Board an. So kann Ihre Karte eine Methode haben, die den aktuellen Status der Karte über die Fen-Zeichenfolge zurückgibt. Dann kann Ihre UI-Ebene die Karte so darstellen, wie sie es wünscht. So funktioniert die aktuelle Schachengine. Schach-Engines sind Konsolenanwendungen ohne GUI, aber wir verwenden sie über eine externe GUI. Die Schachengine kommuniziert mit der GUI über Fenstrings und Schachnotation.
Sie fragen sich, was ist, wenn ich ein neues Stück hinzufüge? Es ist nicht realistisch, dass Schach eine neue Figur einführt. Das wäre eine große Veränderung in Ihrer Domain. Folgen Sie also dem YAGNI-Prinzip.
quelle