Java 如何使用多态性将对象映射到帮助器类?

Java 如何使用多态性将对象映射到帮助器类?,java,polymorphism,Java,Polymorphism,我想用多态性替换switch语句。让我们以邮局为例。这家邮局发送信件和包裹,它们都是邮件的子类。有特定的方式发送不同类型的邮件,因此有信函服务和打包服务,这两种服务都是邮件服务 public class PostOffice { @Inject private LetterSender letterSender; @Inject private PackageSender packageSender; public void send( Mail m

我想用多态性替换switch语句。让我们以
邮局
为例。这家邮局发送
信件
包裹
,它们都是
邮件
的子类。有特定的方式发送不同类型的
邮件
,因此有
信函服务
打包服务
,这两种服务都是
邮件服务

public class PostOffice {

    @Inject
    private LetterSender letterSender;

    @Inject
    private PackageSender packageSender;

    public void send( Mail mail ) {
        if ( mail instanceof Letter ) {
            letterSender.send( (Letter) mail );
        } else if ( mail instanceof Package ) {
            packageSender.send( (Package) mail );
        }
    }

}

如何避免条件和instanceof?我被告知可以使用多态性删除这些,但我仍然不知道如何根据实际逻辑将正确的
邮件类型“路由”到正确的
邮件发件人,
LetterSender
PackageSender
可能有两个不同的方法,每个方法都有一个不同的参数。 对于第一个:

 public void send(Letter letter);
对于第二个问题:

 public void send(Package letter);
为了从多态性中获益,您应该在这两个类实现的接口中定义一个公共方法。例如:

public interface MailSender{
   void send(Mail mail);
}
但是在Java中,
重写的参数不是协变的。因此,您无法通过子键入
Mail
参数来实现该接口。
因此,这意味着您必须在两个发件人类中实现
void send(Mail-Mail)
,例如:

public class LetterSender implements MailSender{
   @Override
   public void send(Mail mail){
      // ...
   }
}  

public class PackageSender implements MailSender{
   @Override
   public void send(Mail mail){
      // ...
   }
}  
要实现这一目标,您应该从较高的角度定义
Mail
,定义任何
Mail
子类所需的行为/方法。
每个
Mail
子类将定义它们的实现。

因此,这两个发送者实现可以处理
send(Mail)
,而无需向下转换参数

这类问题可以用几种不同的方法来解决。最简单的版本是责任链:

interface Sender {
    boolean canSend(Mail mail);
    void send(Mail mail);
}
...
List<Sender> senders;
...
senders.stream()
    .filter(s -> s.canSend(mail))
    .findAny()
    .ifPresentOrElseThrow(
        s -> s.send(mail),
        () -> new SomethingException()
    );
接口发送器{
可以发送(邮件);
无效发送(邮件);
}
...
名单发送者;
...
senders.stream()
.filter(s->s.canSend(邮件))
.findAny()
.IfPresentoreSethrow(
s->s.send(邮件),
()->新事物例外()
);

您可以使用访问者模式进行此操作

将接口MailVisitor定义为:

public interface MailVisitor {

    void visitLetter(Letter letter);
    void visitPackage(Package package);
}
在MailSender中实现此接口:

public class MailSender implement MailVisitor {
     @Override
     public void visitLetter(Letter letter) {//letter sending goes here}
     @Override
     public void visitPackage(Package package) {//package sending goes here}
}
现在,在邮局课程中,您可以让邮件发送者访问刚刚到达的邮件包:

public class PostOffice {

    @Inject
    private MailSender mailSender;

    public void send(Mail mail) {
        mail.visit(mailSender);
    }
}
访问方法的实现如下所示:

public abstract class Mail {

    public abstract void visit(MailVisitor visitor);
}

public class Letter extends Mail {

    public void visit(MailVisitor visitor) {
        visitor.visitLetter(this);
    }
}

public class Package extends Mail {

    public void visit(MailVisitor visitor) {
        visitor.visitPackage(this);
    }
}
在我第一次遇到它时,我花了一段时间才完全理解它是如何工作的。但这是一种非常强大的设计模式,它允许您消除每个instanceof+cast操作


这样做的最大优点是,当您定义一个新的邮件子类时,比如说AirMail。编译器将强制您实现visit(MailVisitor)方法。这将自动使您在MailVisitor上定义一个新方法。这反过来又要求您在MailSender类中实现该新方法。因此,在定义了能够处理新创建的子类型的逻辑之前,代码不会编译。然而,如果您使用了if语句,您可能会忘记为AirMail添加一个新的分支,这将使您的应用程序无法发送任何需要飞机运输的邮件:)

该模式实现得很好。附带说明:对于已访问的类,
accept()
优于
visit()
。但我不认为这是一个很好的模式用例。实际代码将两个发送方的逻辑划分为不同的类。访问者为同一访问者类中的已访问类收集相同类型的操作。它改变了班级的总体责任。在需要实现双重分派或在同一类中收集相同操作时,可以使用访问者模式。这实际上不是OP的要求。OP问他如何删除条件语句和instanceof语句。我相信这是实现这一目标的最干净的解决方案,它也消除了铸造的任何需要。如果确实希望在单独的类中实现,MailSender类还可以委托给PackageSender和LetterSender。“如果确实希望在单独的类中实现,MailSender类还可以委托给PackageSender和LetterSender。”当然,它将创建一个没有任何值的间接寻址。对于已经添加了间接寻址的访问者,它将进行大量间接寻址:3个委托调用来执行任务。