web-dev-qa-db-ja.com

適切な方法で条件付きをポリモーフィズムに置き換えますか?

2つのクラスDogCatが両方とも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)
    }
}

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

10
Andrey Gordeev

プロトコルタイプitselfに動作を決定させます。プログラム全体ですべてのプロトコルを同じように扱いたいexcept実装クラス自体で。このようにすることは、CatまたはDog(または最終的にAnimalの下にある可能性のある他のプロトコル)を渡すことができるはずであると述べているLiskovの置換原則を尊重しています。 、無関心に動作させます。

そのため、isCriticalAnimalの両方によって実装されるDogCat funcを追加するとします。 Dogを実装するものはすべてfalseを返し、Catを実装するものはすべてtrueを返します。

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

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

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

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

9
Neil

伝えるか尋ねる

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

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

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

使用しているクライアントは、使用している特定のサブクラスに関係なく、常に確認メソッドを使用するため、askの代わりにtellが相互作用します。

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

4
Erik Eidt

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

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)
    }
}
2
David Arno

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;
        }
    });
}
1