適切な方法で条件付きを多態性に置き換えますか?


10

2つのクラスDogと、Cat両方がAnimalプロトコルに準拠している(Swiftプログラミング言語に関しては、Java / C#のインターフェースになる)と考えてください。

犬と猫の混合リストを表示する画面があります。Interactor舞台裏のロジックを処理するクラスがあります。

ここで、ユーザーが猫を削除するときに確認アラートを表示します。ただし、犬はアラートなしですぐに削除する必要があります。条件付きのメソッドは次のようになります。

func tryToDeleteModel(model: Animal) {
    if let model = model as? Cat {
        tellSceneToShowConfirmationAlert()
    } else if let model = model as? Dog {
        deleteModel(model: model)
    }
}

このコードはどのようにリファクタリングできますか?それは明らかににおいがする

回答:


9

あなたはプロトコルタイプ自体が振る舞いを決定するのを任せています。実装クラス自体を除いて、プログラム全体ですべてのプロトコルを同じに扱いたいと考えています。このようにすることは、リスコフの代替原則を尊重することであり、CatまたはDog(またはAnimal潜在的に潜在的に使用する可能性のある他のプロトコル)を通過し、無関心に機能させることができると述べています。

したがって、おそらく、との両方で実装されるisCriticalfuncを追加します。実装するものはすべてfalseを返し、実装するものはすべてtrueを返します。AnimalDogCatDogCat

その時点で、必要な作業は次のとおりです(構文が正しくない場合の謝罪。Swiftのユーザーではありません)。

func tryToDeleteModel(model: Animal) {
    if model.isCritical() {
        tellSceneToShowConfirmationAlert()
    } else {
        deleteModel(model: model)
    }
}

これには小さな問題しかありません。つまりDog、とCatはプロトコルです。つまり、プロトコル自体は何をisCritical返すかを決定せず、これをすべての実装クラスに任せて決定します。多数の実装がある場合、Catまたはの拡張可能なクラスを作成して、すべての実装クラスをオーバーライドする必要からすべてDog正しく実装しisCritical、効果的にクリアすることは、おそらく価値がありますisCritical

これがあなたの質問に答えない場合は、コメントに書き込んでください。それに応じて私の答えを広げます!


それは質問の文で少しは不明だが、DogCatしながら、クラスとして記述されているAnimalそれらのクラスのそれぞれで実装しているプロトコルです。ですから、質問とあなたの答えの間には少しの不一致があります。
Caleb

確認ポップアップを表示するかどうかをモデルに決定させることをお勧めしますか?しかし、10匹の猫が表示されている場合にのみポップアップを表示するなど、重いロジックが含まれている場合はどうでしょうか ロジックはInteractor現在の状態に依存しています
Andrey Gordeev

わかりにくい質問については申し訳ありませんが、私はいくつかの編集を行いました。より明確になるはずです
Andrey Gordeev

1
この種の動作はモデルにリンクされるべきではありません。エンティティ自体ではなく、コンテキストに依存します。猫と犬はPOJOである可能性が高いと思います。動作は他の場所で処理され、コンテキストに応じて変更できる必要があります。振る舞いまたは振る舞いがCatまたはDogで依存するメソッドを委任すると、そのようなクラスでの責任が大きくなりすぎます。
GrégoryのElhaimer

@GrégoryElhaimerこれは動作を決定するものではないことに注意してください。重要なクラスかどうかを示すだけです。それが重要なクラスであるかどうかを知る必要があるプログラム全体の振る舞いは、評価し、それに応じて行動できます。これが実際に両方のインスタンスCatDog処理方法を区別するプロパティである場合、それはの共通プロパティである可能性があり、またそうである必要がありますAnimal。他のことをすると、後でメンテナンスの頭痛がします。
Neil

4

伝えるか質問する

あなたが示している条件付きアプローチは、「アスク」と呼ばれます。これは、消費しているクライアント「あなたはどのような人ですか」と尋ねるところです。それに応じて、オブジェクトの動作とオブジェクトとの相互作用をカスタマイズします。

これは、「tell」と呼ばれる代替方法とは対照的です。tellを使用する、より多くの作業をポリモーフィックな実装にプッシュできるため、使用するクライアントコードがよりシンプルになり、条件なしで、可能な実装に関係なく共通になります。

確認アラートを使用したいので、それをインターフェースの明示的な機能にすることができます。そのため、オプションでユーザーに確認し、確認ブール値を返すブールメソッドがあるとします。確認したくないクラスでは、単にでオーバーライドしreturn true;ます。他の実装では、確認を使用するかどうかを動的に決定する場合があります。

使用しているクライアントは、使用している特定のサブクラスに関係なく、常に確認メソッドを使用するため、インタラクションはaskではなくtellになります

(別のアプローチは、確認を削除にプッシュすることですが、削除操作が成功することを期待する消費クライアントを驚かせます。)


確認ポップアップを表示するかどうかをモデルに決定させることをお勧めしますか?しかし、10匹の猫が表示されている場合にのみポップアップを表示するなど、重いロジックが含まれている場合はどうでしょうか ロジックはInteractor現在の状態に依存しています
Andrey Gordeev

2
わかりました、はい、それは別の質問であり、別の答えが必要です。
Erik Eidt 2018年

2

確認が必要かどうかを判断するのはCatクラスの責任なので、そのアクションを実行できるようにします。Kotlinは知らないので、C#で表現します。うまくいけば、アイデアはコトリンにも転送可能です。

interface Animal
{
    bool IsOkToDelete();
}

class Cat : Animal
{
    private readonly Func<bool> _confirmation;

    public Cat (Func<bool> confirmation) => _confirmation = confirmation;

    public bool IsOkToDelete() => _confirmation();
}

class Dog : Animal
{
    public bool IsOkToDelete() => true;
}

次に、Catインスタンスを作成するときに、を指定します。削除してもよければTellSceneToShowConfirmationAlert、返す必要がありますtrue

var model = new Cat(TellSceneToShowConfirmationAlert);

そして、あなたの関数は次のようになります:

void TryToDeleteModel(Animal model) 
{
    if (model.IsOKToDelete())
    {
        DeleteModel(model)
    }
}

1
これは削除ロジックをモデルに移動しませんか?これを処理するために別のオブジェクトを使用するほうがはるかに良いでしょうか?おそらく、ApplicationService内のDictionary <Cat>のようなデータ構造です。猫が存在するかどうかを確認し、存在する場合は確認アラートを発射しますか?
keelerjr12 2018年

@ keelerjr12は、削除の確認が必要かどうかを決定する責任をCatクラスに移します。それが属する場所だと私は主張します。その確認がどのように行われるか(つまり、注入されるか)を決定することはできず、自分自身を削除することもありません。したがって、いいえ、それは削除ロジックをモデルに移動しません。
David Arno

2
このアプローチは、クラス自体にアタッチされた膨大な数のUI関連コードにつながると思います。クラスが複数のUIレイヤーで使用されることが意図されている場合、問題は大きくなります。ただし、これがビジネスエンティティではなくViewModelタイプのクラスである場合は、適切と思われます。
グラハム、

@Graham、はい、それは間違いなくこのアプローチのリスクです。それはTellSceneToShowConfirmationAlertのインスタンスに注入するのが簡単であることに依存していますCat。これが容易ではない状況(この機能が深いレベルにある多層システムなど)では、このアプローチは適切ではありません。
David Arno

1
私が得ていたものを正確に。ビジネスエンティティとViewModelクラス。ビジネスドメインでは、猫はUI関連のコードを知らないはずです。私の家族の猫は誰にも警告しません。ありがとう!
keelerjr12 2018年

1

Visitorパターンに行くことをお勧めします。Javaで小さな実装を行いました。私はSwiftに慣れていませんが、簡単に適応できます。

訪問者

public interface AnimalVisitor<R>{
    R visitCat();
    R visitDog();
}

あなたのモデル

abstract class Animal { // can also be an interface like VisitableAnimal
    abstract <R> R accept(AnimalVisitor<R> visitor);
}

class Cat extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitCat();
     }
}

class Dog extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitDog();
     }
}

訪問者に電話する

public void tryToDelete(Animal animal) {
    animal.accept( new AnimalVisitor<Void>() {
        public Void visitCat() {
            tellSceneToShowConfirmation();
            return null;
        }

        public Void visitDog() {
            deleteModel(animal);
            return null;
        }
    });
}

AnimalVisitorの実装は必要なだけ持つことができます。

例:

public void isColorValid(Color color) {
    animal.accept( new AnimalVisitor<Boolean>() {
        public Boolean visitCat() {
            return Color.BLUE.equals(color);
        }

        public Boolean visitDog() {
            return true;
        }
    });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.