Je crée un jeu de plateau (comme les échecs) en Java, où chaque pièce est de son propre type (comme Pawn
, Rook
etc.). Pour la partie GUI de l'application, j'ai besoin d'une image pour chacune de ces pièces. Puisque faire pense comme
rook.image();
viole la séparation de l'interface utilisateur et de la logique métier, je vais créer un présentateur différent pour chaque pièce, puis mapper les types de pièces à leurs présentateurs correspondants, comme
private HashMap<Class<Piece>, PiecePresenter> presenters = ...
public Image getImage(Piece piece) {
return presenters.get(piece.getClass()).image();
}
Jusqu'ici tout va bien. Cependant, je sens qu'un gourou de la POO prudent froncerait les sourcils en appelant ungetClass()
méthode et suggérerait d'utiliser un visiteur par exemple comme ceci:
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;
}
}
J'aime cette solution (merci, gourou), mais elle a un inconvénient majeur. Chaque fois qu'un nouveau type de pièce est ajouté à l'application, PieceVisitor doit être mis à jour avec une nouvelle méthode. Je voudrais utiliser mon système comme cadre de jeu de société où de nouvelles pièces pourraient être ajoutées grâce à un processus simple où l'utilisateur du cadre ne fournirait que la mise en œuvre de la pièce et de son présentateur, et le brancherait simplement dans le cadre. Ma question: existe-t-il une solution propre de POO sans instanceof
, getClass()
etc. qui permettrait ce type d'extensibilité?
la source
Réponses:
Oui il y a.
Permettez-moi de vous poser cette question. Dans vos exemples actuels, vous trouvez des façons de mapper des types de pièces à des images. Comment cela résout-il le problème du déplacement d'une pièce?
Une technique plus puissante que de demander le type est de suivre Tell, don't ask . Et si chaque pièce prenait une
PiecePresenter
interface et ressemblait à ceci:La construction ressemblerait à ceci:
L'utilisation ressemblerait à quelque chose comme:
L'idée ici est d'éviter de prendre la responsabilité de faire quelque chose dont d'autres choses sont responsables en ne posant pas de questions à ce sujet ni en prenant des décisions en fonction de cela. Au lieu de cela, tenez une référence à quelque chose qui sait quoi faire à propos de quelque chose et dites-lui de faire quelque chose à propos de ce que vous savez.
Cela permet le polymorphisme. Vous ne vous souciez pas de ce à quoi vous parlez. Vous ne vous souciez pas de ce qu'il a à dire. Vous vous souciez juste qu'il puisse faire ce que vous avez besoin de faire.
Un bon schéma qui maintient ces derniers dans des couches séparées, suit le dire-ne-demande, et montre comment ne pas la couche deux à la couche est injustement ce :
Il ajoute une couche de cas d'utilisation que nous n'avons pas utilisée ici (et peut certainement ajouter) mais nous suivons le même modèle que vous voyez dans le coin inférieur droit.
Vous remarquerez également que Presenter n'utilise pas l'héritage. Il utilise la composition. L'héritage devrait être un moyen de dernier recours pour obtenir le polymorphisme. Je préfère les designs qui privilégient la composition et la délégation. C'est un peu plus de frappe au clavier, mais c'est beaucoup plus de puissance.
la source
Que dire de cela:
Votre modèle (les classes de figures) a des méthodes communes dont vous pourriez également avoir besoin dans un autre contexte:
Les images à utiliser pour afficher une certaine figure obtiennent des noms de fichiers par un schéma de dénomination:
Ensuite, vous pouvez charger l'image appropriée sans accéder aux informations sur les classes java.
Je pense que vous ne devriez pas trop vous concentrer sur les cours . Pensez plutôt en termes d' objets métier .
Et la solution générique est une cartographie de toute nature. À mon humble avis, l'astuce consiste à déplacer ce mappage du code vers une ressource plus facile à maintenir.
Mon exemple fait ce mappage par convention qui est assez facile à implémenter et évite d'ajouter des informations liées à la vue dans le modèle économique . D'un autre côté, vous pouvez le considérer comme un mappage "caché" car il n'est exprimé nulle part.
Une autre option consiste à voir cela comme une analyse de rentabilisation distincte avec ses propres couches MVC, y compris une couche de persistance qui contient le mappage.
la source
Je créerais une classe d'interface utilisateur / vue distincte pour chaque pièce contenant les informations visuelles. Chacune de ces classes à la pièce a un pointeur sur son homologue modèle / entreprise qui contient la position et les règles de jeu de la pièce.
Alors prenez un pion par exemple:
Cela permet une séparation complète de la logique et de l'interface utilisateur. Vous pouvez passer le pointeur de pièce logique à une classe de jeu qui gérerait le déplacement des pièces. Le seul inconvénient est que l'instanciation devrait se produire dans une classe d'interface utilisateur.
la source
Piece* p
. Comment savoir que je dois créer unPawnView
pour l'afficher, et non unRookView
ouKingView
? Ou dois-je créer une vue ou un présentateur d'accompagnement immédiatement chaque fois que je crée une nouvelle pièce? Ce serait essentiellement la solution de @ CandiedOrange avec les dépendances inversées. Dans ce cas, lePawnView
constructeur peut également prendre unPawn*
, pas seulement unPiece*
.J'aborderais cela en rendant
Piece
générique, où son paramètre est le type d'une énumération qui identifie le type de morceau, chaque morceau ayant une référence à un tel type. L'interface utilisateur pourrait alors utiliser une carte de l'énumération comme précédemment:Cela présente deux avantages intéressants:
Tout d'abord, applicable à la plupart des langages typés statiquement: si vous paramétrez votre planche avec le type de pièce à attendre, vous ne pouvez pas y insérer le mauvais type de pièce.
Deuxièmement, et peut-être plus intéressant encore, si vous travaillez en Java (ou dans d'autres langages JVM), vous devez noter que chaque valeur d'énumération n'est pas seulement un objet indépendant, mais elle peut également avoir sa propre classe. Cela signifie que vous pouvez utiliser vos objets de type pièce comme objets de stratégie pour personnaliser le comportement de la pièce:
(De toute évidence, les implémentations réelles doivent être plus complexes que cela, mais j'espère que vous avez l'idée)
la source
Je suis un programmeur pragmatique et je ne me soucie vraiment pas de ce qu'est une architecture propre ou sale. Je crois que les exigences et elles doivent être traitées de manière simple.
Votre exigence est que la logique de votre application d'échecs soit représentée sur différentes couches de présentation (appareils) comme sur le Web, une application mobile ou même une application console, vous devez donc prendre en charge ces exigences. Vous pouvez préférer utiliser des couleurs très différentes, des images de pièces sur chaque appareil.
Comme vous l'avez vu, le paramètre du présentateur doit être transmis différemment sur chaque périphérique (couche de présentation). Cela signifie que votre couche de présentation décidera comment représenter chaque pièce. Quel est le problème dans cette solution?
la source
Il existe une autre solution qui vous aidera à abstraire complètement l'interface utilisateur et la logique du domaine. Votre carte doit être exposée à votre couche d'interface utilisateur et votre couche d'interface utilisateur peut décider comment représenter les pièces et les positions.
Pour ce faire, vous pouvez utiliser la chaîne Fen . La chaîne de fenêtre est essentiellement des informations sur l'état du tableau et donne les pièces actuelles et leurs positions à bord. Ainsi, votre carte peut avoir une méthode qui retourne l'état actuel de la carte via la chaîne Fen, puis votre couche d'interface utilisateur peut représenter la carte comme elle le souhaite. C'est en fait ainsi que fonctionnent les moteurs d'échecs actuels. Les moteurs d'échecs sont des applications console sans interface graphique, mais nous les utilisons via une interface graphique externe. Le moteur d'échecs communique avec l'interface graphique via des chaînes de fen et une notation d'échecs.
Vous demandez que si j'ajoute un nouveau morceau? Il n'est pas réaliste que les échecs introduisent une nouvelle pièce. Ce serait un énorme changement dans votre domaine. Suivez donc le principe YAGNI.
la source