2
votes

Quand le casting / instance est-il une bonne utilisation?

Chaque fois que je google instanceof et cast, je verrai toujours des réponses disant de l'éviter et d'utiliser le modèle X.

J'ai un exemple où je ne vois aucun modèle que je pense pouvoir utiliser.

Nous avons 2 classes: Commande et Paiement (CashPayment et CardPayment).

CashPayment a 1 propriété appelée amount et une méthode implémentée pay .

CardPayment a 1 propriété appelée cardNumber et un pay implémenté qui appelle une API tierce.

Maintenant, dites que vous souhaitez rédiger une vue sur une commande, comment quelqu'un éviterait-il d'utiliser instanceof ou de diffuser ici pour afficher les détails de paiement?

Avec instanceof, je peux faire ceci:

class OrderRepository {
  public function save(Order order) {
    // Insert into order query here...
    // Insert into orderItems query here...
    // Insert payment into its table

    queryBuilder
      .table(order.payment().tableName())
      .insert([
        order.payment().columnName() => order.payment().value()
      ]);
  }
}

Question : peut-on vraiment éviter les instances et les castings? Si oui, comment pouvons-nous y parvenir en "OO-way"? Si non, je suppose que c'est l'un des cas valides?

IMO, nous pouvons éviter instanceof / casting et favoriser l'utilisation de méthodes surchargées, mais si vous voulez connaître un objet concret, cela ne peut pas être évité .

Modifier:

J'essaie d'écrire mes modèles de domaine, ce qui signifie qu'il est indépendant de l'infrastructure et des éléments spécifiques à l'application.

Imaginez que nous devions enregistrer la commande via OrderRepository et que le paiement ait ses propres tables. Ne serait-ce pas moche si c'était comme:

order = new Order(...);
order.checkout(aPayment);

Payment Details (Cash):
Type: (instanceof CashPayment ? "Cash") or order.payment().type();
Amount: ((CashPayment) order.payment()).amount();

Payment Details (Card):
Type: (instanceof CardPayment ? "Card") or order.payment().type();
Card Number: ((CardPayment) order.payment()).cardNumber();


0 commentaires

3 Réponses :


0
votes

La solution orientée objet évidente consiste à ajouter une méthode display () au Paiement .

En général, instanceof / casting est mal vu, car il indique généralement une conception moins qu'optimale. Le seul moment où cela est autorisé, c'est lorsque le système de types n'est pas assez puissant pour exprimer quelque chose. J'ai rencontré quelques situations en Java où il n'y a pas de meilleure solution (principalement parce qu'il n'y a pas de collections en lecture seule en Java, donc le paramètre générique est invariant), aucune dans Scala ou Haskell pour le moment.


2 commentaires

L'affichage peut fonctionner. Je pense que l'exemple que j'ai écrit était lié à l'interface utilisateur, mais c'est l'exemple le plus simple que je puisse trouver. Le contexte que j'avais dans mon esprit était celui des modèles de domaine où il ne contient que de la logique métier et indépendamment de tout ce qui l'utiliserait. Mettra à jour la question


La façon de conserver ou de présenter un objet dépendra toujours par définition des éléments internes de l'objet. Ces comportements sont cohérents avec l’objet. Je ne souscris pas à l'idée que les objets devraient être «agnostiques» des fonctions qui leur sont très cohérentes.



0
votes

Vous pouvez utiliser la composition plutôt que l ' héritage .

Peut-être quelque chose du genre:

public class Payment
{
    private CardPaymentDetail _cardPaymentDetail;

    public PaymentType Type { get; private set; }
    public decimal Amount { get; }

    private Payment(decimal amount)
    {
        // > 0 guard

        Amount = amount;
    }

    private Payment(decimal amount, CardPaymentDetail cardPayment)
        : this(amout)
    {
        // null guard

        CardPayment = cardPayment;
    }

    public CardPaymentDetail CardPayment
    {
        get 
        {
            if (Type != PaymentType.Card)
            {
                throw new InvalidOperationException("This is not a card payment.");
            }

            return _cardPaymentDetail;
        }
    }
}

La persistance à mon humble avis peut également être plus facile. Dans le même ordre d'idées, j'ai également utilisé ce qui équivaut à un type de paiement Inconnu par défaut, puis j'ai une méthode pour spécifier le type: AsCard (CardPaymentDetail cardPayment) {} .


4 commentaires

Je suis désolé, peut-être que je ne suis tout simplement pas familier avec ce langage, mais Card a cardNumber et Cash a un montant ou combien a été donné afin que le changement puisse être calculé; la carte n'en a pas besoin. Étant donné que vous êtes intéressé par différentes propriétés, comment résolvez-vous cela? Avez-vous des méthodes cardDetails, cashDetails, peut-être même paypalDetails qui renvoie null si cela ne leur est pas applicable?


J'aurais un objet de valeur * Detail différent pour chaque regroupement de données. Lorsque vous payez, vous payez généralement un Montant avec une Taxe et ainsi de suite, c'est pourquoi je l'avais sur le Paiement mais si c'est le cas ne s'appliquera qu'à certains types, alors cela serait divisé. En persistant, vous voudriez de toute façon tous les détails pertinents, c'est pourquoi ce bit, à mon avis, devient également plus facile à gérer. J'ai utilisé C # ici comme exemple.


Cela nécessitera encore une instance, non? if instanceof CardDetails, details.cardNumber () else if instanceof CashDetails, details.cashAmount (), else .... Ainsi de suite.


Je ne pense pas que instanceof serait nécessaire. Vous aurez une propriété pour chaque PaymentType sur le Payment et utilisez la propriété "Type" sur le Payment pour accéder / définir le paiement correspondant détail. Il n'y aura pas de classe de détail héritée par type. Vous pourriez avoir une interface, comme l'a déclaré Robert, qui expose quelque chose comme une méthode display () mais vous devriez décider à quel point cela sera utile . Je travaillerais toujours avec les types individuels car ils sont vraiment distincts; sinon vous allez être abattu.



1
votes

Si vous voulez absolument séparer l'opération de l'objet lui-même (par exemple pour maintenir la séparation des préoccupations), mais que l'opération est fortement couplée aux détails de la sous-classe, vous n'avez que deux choix.

Vous devez soit repenser le modèle et trouver une abstraction homogène, ce qui pourrait être n'importe quelle approche vous permettant de traiter les différents types de la même manière.

par exemple

interface IPaymentVisitor {
    public void visit(CashPayment payment);
    public void visit(CardPayment payment);
}

class PaymentRenderer implements IPaymentVisitor ...

class CashPayment extends Payment {
   ...
   public void visit(IPaymentVisitor visitor) {
       visitor.visit(this);
   }
}

var renderer = new PaymentRenderer(outputStream);
payment.accept(renderer);


0 commentaires