web-dev-qa-db-ja.com

共通の継承ルートを持つ2つのクラスの構成

あなたの一日がうまくいくことを願っています。

同じ継承ルートを持つ2つのオブジェクトの次の構成を作成します。

enter image description here

ウォレットクラスには、addgetCardsgetPaymentCardsの3つの基本的な操作があります。 getCardsCardsとPaymentCardsの両方を返す必要があります。これは私がこれらのカードをウォレットに保存する方法の問題に直面した場所です:

enter image description here

class Card {}
class BadgeCard extends Card {}
class IdCard extends Card {}

class PaymentCard extends Card {}

class WalletT21 {
    Set<Card> cards;
    Set<PaymentCard> paymentCards;

    void add(Card c) {
        cards.add(c);
        Integer i = 0;
        i = 1;
        i = 1;
        System.out.println(i);
    }

    void add(PaymentCard c) {
        paymentCards.add(c);
    }

    Set<Card> cards() {
        return Stream.concat(cards.stream(), paymentCards.stream())
                     .collect(Collectors.<Card>toSet());
    }

    Set<PaymentCard> paymentCards() {
        return paymentCards;
    }
}
class WalletT22 {
    Set<Card> cards;
    Set<PaymentCard> paymentCards;

    void add(Card c) {
        cards.add(c);
    }

    void add(PaymentCard c) {
        cards.add(c);
        paymentCards.add(c);
    }

    Set<Card> cards() {
        return cards;
    }

    Set<PaymentCard> paymentCards() {
        return paymentCards;
    }
}

これらのアイデアはすべて、完璧ではありません。これらのクラスを1つの屋根(ウォレット)で統一する理由は、上位のコードが識別(すべてのカードがアカウント識別子である)と支払い(すべてのカードが支払い可能ではなく、すべての支払いカードが識別子(カード)である)にカードを使用するためです。

そのような構成を作成するよりエレガントな方法はありますか?

7
ovnia

すべてのカードを1つのSet<Card>、次にgetPaymentCardsでタイプ別のフィルターを適用できます。

cards.stream()
    .filter(card -> card instanceof PaymentCard)
    .collect(Collectors.toSet());

この問題を完全に回避するには、問題を別の方法でモデル化する必要がありますが、これらの正確な要件を考えると、このソリューションはより適切な方法で処理できます。

より一般的なバージョンが必要な場合は、次のように実装できます。

public Set<Card> getCardsOfType(Class<? extends Card> type) {
    return cards.stream()
            .filter(type::isInstance)
            .collect(Collectors.toSet());
}
4
Angus Goldsmith

編集: @Laivと@BenAaronsonのおかげで、ここでは実際のインスタンスの代わりにジェネリックスを使用してフィルターメソッドに渡します。以前のバージョンのコードの編集履歴を参照してください。

まあ、私はinstanceOfを使うのは好きではありません。新しい種類のカードが発生したときに新しい特別なケースを追加する必要があるためです。また、あなたのWalletクラスは結合で具体的な実装になります。

これが私の2セントです。

getCardsByType(Class <?extends Card> cardType)

MyIDCard.classなどをメソッドに渡して、そのタイプのすべてのカードのサブセットを取得します。次に、消費者は必要に応じてカードを好きなタイプにキャストできます。 私はインターフェースを使用していますが、必要に応じて抽象クラスを使用することもできます。同じように機能します。

実際のアプリではファクトリであるはずの具体的なCard実装に結合されているのはテストクラスだけであることに注意してください。

コードを表示(最後にテストメソッド):

import Java.util.Set;
import Java.util.HashSet;
import Java.util.Set;

interface Card {}
interface Badge extends Card {}
interface IDCard extends Card {}
interface PaymenCard extends Card {}
class MyIDCard implements IDCard {}
class MyBadge implements Badge {}

interface Wallet {
    public void addCard(Card card);
    public Set<Card> getCards();
    public Set<Card> getCardsByType(Class<? extends Card> cardType);
}

class MyWallet implements Wallet {
    Set<Card> cards = new HashSet<Card>();
    @Override
    public void addCard(Card card) { this.cards.add(card);}
    @Override
    public Set<Card> getCards() { return this.cards;}
    @Override
    public Set<Card> getCardsByType(Class<? extends Card> cardType) {
        Set<Card> subSet = new HashSet<Card>();         
        for (Card card: this.cards){
            if (card.getClass() == cardType){
                subSet.add(card);   
            }           
        }
        return subSet;
    }
}

    public class Test {    
        public static void main (String[] args){
            Card c1 = new MyIDCard();
            Card c2 = new MyBadge();
            Card c3 = new MyBadge();
            Card c4 = new MyBadge();
            Card c5 = new MyIDCard();
            Wallet myWallet = new MyWallet();
            myWallet.addCard(c1);
            myWallet.addCard(c2);
            myWallet.addCard(c3);
            myWallet.addCard(c4);
            myWallet.addCard(c5);
            //now my wallet has two IDs and a Badge
            //
            // I can ask for a list of cards "like this one"
            Set<Card> badges = myWallet.getCardsByType(MyBadge.class); 

            for (Card card: badges){
                System.out.println(card.getClass().getCanonicalName());
            }       
        }
    }

タイプによるフィルターが機能することを示す出力:

$ Java Test 
MyBadge
MyBadge
MyBadge

ここではデフォルトのパッケージを使用していますが、クラスの正規名には次のようなパッケージの名前空間が含まれているため、名前の衝突はありません。

$ Java Test 
com.test.cards.MyBadge
com.test.cards.MyBadge
com.test.cards.MyBadge
2

他の人々がそれらをすべて1つのSetに含めると述べたように、絶対に2つの別個のセットが必要な場合は、何らかの理由でgetCards()でセットの組み合わせを返すことができます。 。

void add(Card c) {
    cards.add(c);
}

void add(PaymentCard c) {
    paymentCards.add(c);
}

Set<Card> cards() {
    Set<Card> combinedSet = new HashSet<>();
    combinedSet.addAll(cards);
    combinedSet.addAll(paymentCards);
    return combinedSet;
}

Set<PaymentCard> paymentCards() {
    return paymentCards;
}
1
yitzih