以适当的方式将条件替换为多态吗?


10

考虑两个类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)
    }
}

该代码如何重构?闻起来很香

Answers:


9

您要让协议类型本身来确定行为。除了在实现类本身中,您希望在整个程序中将所有协议视为相同。这样做是在遵守Liskov的“替代原则”,该原则说您应该能够通过CatDog(或您可能最终根据任何其他协议达成的协议Animal),并使其无动于衷。

因此,大概您要添加一个要由和实现的isCritical函数。任何实现的结果都将返回false,而任何实现的结果都将返回true。AnimalDogCatDogCat

到那时,您只需要这样做(如果语法不正确,我深表歉意。不是Swift的用户):

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

那里只有一个小问题,那就是DogCat协议,这意味着它们本身并不决定isCritical返回什么,而是将其留给每个实现类自行决定。如果您有很多实现,那么可能值得花点时间来创建CatDog已经正确实现的可扩展类,isCritical并有效地将所有实现类清除为无需重写isCritical

如果这不能回答您的问题,请在评论中写出内容,我将相应地扩大答案!


这是在问题的陈述有点不清楚,但DogCat被描述为类,同时Animal是可以被每个这些类的实现的协议。因此,问题和答案之间有些不符。
加勒布

因此,您建议让模型决定是否显示确认弹出窗口?但是,如果涉及繁琐的逻辑,例如仅在显示10只猫的情况下显示弹出窗口,该怎么办?逻辑Interactor现在取决于状态
安德烈·戈尔德耶夫

是的,对于这个不清楚的问题,对不起,我进行了很少的编辑。现在应该更清楚了
Andrey Gordeev '18

1
这种行为不应与模型关联。它取决于上下文,而不取决于实体本身。我认为猫和狗更有可能是POJO。行为应该在其他地方处理,并且可以根据上下文进行更改。在“猫或狗”中委派行为或行为依赖的方法将导致在此类课程中承担过多责任。
了GrégoryElhaimer

@GrégoryElhaimer请注意,它不是确定行为。它只是说明它是否是关键类。然后,整个程序中需要了解其是否为关键类的行为可以进行评估并采取相应的措施。如果这确实是一个可以区分实例Cat和实例Dog的处理方式的属性,则它可以并且应该是中的通用属性Animal。做其他任何事情都会在以后引起维护麻烦。
尼尔,

4

告诉与询问

您显示的条件方法将称为“ ask ”。消费客户在这里询问 “您是什么样的人”?并相应地自定义其行为和与对象的交互。

这与我们称为“ tell ” 的替代方法相反。使用tell可以将更多的工作推入多态实现中,从而使使用方的客户端代码更简单,没有条件,并且无论可能的实现如何都是通用的。

由于要使用确认警报,因此可以使该接口具有显式功能。因此,您可能有一个布尔方法,可以选择与用户核对并返回确认布尔值。在不想确认的类中,它们只是用覆盖return true;。其他实现可能会动态确定他们是否要使用确认。

消费客户端将始终使用确认方法,而不管它使用的是哪个特定子类,这使交互成为告知而不是ask

(另一种方法是将确认推送到删除中,但这会使希望删除操作成功的使用方感到惊讶。)


因此,您建议让模型决定是否显示确认弹出窗口?但是,如果涉及繁琐的逻辑,例如仅在显示10只猫的情况下显示弹出窗口,该怎么办?逻辑Interactor现在取决于状态
安德烈·戈尔德耶夫

2
好的,是的,这是一个不同的问题,需要不同的答案。
埃里克·艾德

2

确定是否需要确认是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实例时,向其提供TellSceneToShowConfirmationAlerttrue如果可以删除,则需要返回:

var model = new Cat(TellSceneToShowConfirmationAlert);

然后您的函数变为:

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

1
这不是将删除逻辑移到模型中吗?使用另一个对象来处理这个问题会更好吗?可能是一个数据结构,例如ApplicationService中的Dictionary <Cat>;检查猫是否存在,是否存在,然后触发确认警报?
keelerjr12 '18 -10-3

@ keelerjr12,它将确定是否需要删除的确认移到Cat类中。我认为那是它的归属。它不必决定如何实现确认(即注入),也不会删除自身。因此,不,它不会将删除逻辑移入模型。
David Arno

2
我觉得这种方法会导致将大量与UI相关的代码附加到类本身。如果打算在多个UI层上使用该类,则问题会加剧。但是,如果这是ViewModel类型类,而不是业务实体,则似乎是合适的。
格雷厄姆

@Graham,是的,使用这种方法绝对是有风险的:它依赖于轻松地将其TellSceneToShowConfirmationAlert注入的实例Cat。在那并非易事的情况下(例如在多层系统中此功能位于更深层次上),那么这种方法将不是一个好方法。
David Arno

1
确切地说我在说什么。业务实体与ViewModel类。在业务领域中,Cat不应了解与UI相关的代码。我的家猫没有提醒任何人。谢谢!
keelerjr12 '18 -10-3

1

我建议您选择一种访客模式。我用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;
        }
    });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.