Java继承和避免过度使用instanceof

5

我有三个类,一个抽象的User和两个具体的类: NormalUser持有一个或多个地址对象的ArrayList,这些地址对象可以是不同的(国内、国际、自定义等),然后是Admin类,它有一个返回true的方法。它们都包含更多与彼此无关的方法。

abstract class User{
    public User(String username, String pw){
 ...

}

public class NormalUser extends User{
...
    private ArrayList<Address> addresses;

...

    public void addAdress(ArrayList<Address> address){
        addresses.addAll(address);
}

public class Admin extends User{

...
    public boolean getIsAdmin(){
        return true;
  }
}

假设我在另一个类中创建了4个用户对象,就像这样:

    ArrayList<User> users;

    users.add(new NormalUser( "1", "pw");
    users.add(new NormalUser( "2", "pw");
    users.add(new NormalUser( "3", "pw");
    users.add(new NormalUser( "4", "pw");
    users.add(new Admin("5", "pw"));
    users.add(new NormalUser( "6", "pw");

假设我想在NormalUser中使用addAddress方法,那么我必须先将users中的特定用户向下转换为NormalUser,在像这样使用NormalUser中的addAddress方法:

     if (user instanceof NormalUser){
        NormalUser normal = (NormalUser) user;
        normal.addAddress(...)
        }

我希望NormalUser和Admin都成为User,这样我可以在登录时一起处理它们。
我考虑将addEmail添加到User类中,然后在NormalUser类中重写它,但是我必须为NormalUser类中的每个方法都这样做,而且Admin也会从User继承它,当它不需要该功能时。
问题1:有更好的方法吗?我听说使用instanceof是不好的,而且每次使用NormalUser类特定的方法时都必须使用instanceof。
问题2:一个对象地址的ArrayList是将RegularUser链接到特定地址/对象的最佳方式吗?
现在没有涉及到数据库。
例如,用户a有两个地址,一个国内地址和一个国际地址,用户b只有一个国内地址,用户c有一个国内地址和一个自定义地址等。
谢谢。
PS. 我已经广泛搜索了以前的帖子,但没有找到解决方案。在我的两本Java书中,它们都显示了instanceof的示例,但没有提到它是一个不好的实践。

关于这个具体情况,我恐怕无法确切地说哪个更好,但是不,instanceof并不是根据定义来说一个坏东西。当多态性更合适时,您只需不要过度使用它即可。 - Bart van Heukelom
关于 instanceof 相关的问题:您应该重新考虑您的设计,使得您永远不会在基本的 User 上调用 addAddress。只有当您处于专门为 NormalUser 设计的代码块中时,才应该调用这样的方法。 - toto2
管理员没有地址有什么好的理由吗? - soulcheck
@soulcheck 不仅仅是地址,而且还有10种不同的方法 :) - Brah
2个回答

3
你可以使用访问者模式 - 有点笨拙和稍微难以阅读,但可能是解决你问题的最佳方案。
实际上,在基类中推送addEmail的解决方案并不差。只需在基类User中提供一个空实现,并在RegularUser中覆盖即可。如果你想检查给定的User实例是否支持添加电子邮件,请提供另一个方法,如supportsAddEmail,默认返回false,当覆盖addEmail时返回true

但是 Admin 不会继承 addAddress 方法(即使它是空的)吗? - Brah
是的,这意味着您可以调用 Admin.addEmail(),它什么也不做(no-op)。这就是为什么我建议使用 supportsAddEmail,但它并不真正像一个好主意。但这难道不正是您想要的吗?如果用户是“NormalUser”,则添加电子邮件,否则不执行任何操作。 - Tomasz Nurkiewicz
我认为将 addEmail 添加到基类是一个好主意,而且无害。关于它的 no-op 特性,你可以把它看作是“有人告诉管理员对象一个电子邮件地址,如果管理员不在意,那就这样吧。” 如果你需要在基类中放置 getEmail(),情况会变得更加棘手;在这种情况下,某些实现将不得不抛出异常,我觉得这很丑陋。如果你这样做,至少要有一个像 supportsGetEmail() 这样的方法。最好的方法是调用站点知道他们有哪个用户子类,这样你就可以只将 getEmail() 添加到 NormalUser 中。 - yshavit

0

我认为最简单的解决方案是创建一个名为UserList的类,该类包含NormalUser列表和Admin列表。 UserList类的实例将替换原始列表。 UserList类可以提供一些方法,例如:

  • User getUser(index i)//使用两个列表实现

  • User removeUser(index i)//使用两个列表实现

  • NormalUser getNormalUser(index i)//使用普通用户列表实现
  • NormalUser removeNormalUser(index i)//使用普通用户列表实现
  • Admin getAdmin(index i)//使用管理员用户列表实现
  • Admin removeAdmin(index i)//使用管理员用户列表实现
  • ....

处理适当列表的所有代码都封装在UserList类中。您可以拥有使用两个列表或仅一个列表的方法,具体取决于您需要对用户执行什么操作。与UserList交互的类不会知道UserList内部是否只有一个或两个列表。


谢谢你的回复Phil,这对我来说似乎是个好主意,尽管我希望不必创建新类来保存列表。虽然我是Java的新手,但如果例如我有一个动物超类,然后是猫和狗子类。如果我有一组包含狗和猫的动物列表,并且我想在狗上调用bark(),那么我就必须创建一个狗的列表才能正确执行,这对我来说似乎有些违反直觉 :/ - Brah
你好。如果我是你,我会创建(1)一个猫的列表和(2)一个狗的列表。考虑到你有10000只狗和10000只猫。如果你把它们分开成两个列表,那么查找所有的狗将会比在一个包含20000只动物的列表中查找要快得多。当你需要调用像bark()这样的方法时,你只需要处理狗的列表,而不是处理20000只动物并在调用bark()之前检查它们是狗还是猫。如果你有很多元素,使用两个列表性能应该会更好。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接