【问题标题】:How to organize an OO design with two different types of users如何组织具有两种不同类型用户的 OO 设计
【发布时间】:2011-05-15 03:35:26
【问题描述】:

我有两种不同类型的用户,我已将它们映射到两个 Java 类 UserWheelUserSea,它们有一个共同的抽象超类,称为 用户。为这些用户类型保存的数据大致相同,但行为不同。

然后我创建了一个名为 UserCollection 的抽象类,其中包含派生类 UserWheelCollectionUserSeaCollection 来搜索子用户或加载子用户。

然后我在 UserCollection 类中添加了一个带有签名的抽象方法

public abstract List<User> listAllSubusers()

这是因为实现会有所不同。创建的每个用户将是一个 UserWheel 或一个 UserSea,具体取决于调用哪个方法,但所有其余的实现都完全不同。

然后我想向 UserCollection 添加一个带有签名 public User loadById(int idUser) 的新方法。在这种情况下,除了返回的用户将是 UserWheelUserSea 的实例之外,实现将是相同的。在这种情况下,由于代码重复,我不愿意在基类中使用抽象方法。

我可以用 instanceof 检查 UserCollection 的具体类并创建一个适当的子类,但它似乎不是面向对象的并且违反了开闭原则。

另一个想法是向 UserCollection 添加一个抽象方法 createNewUser() 并在子类中添加具体实现以返回一个新实例,因此基类将只调用此 createNewUser() 方法。

您认为第二条路径有意义吗?或者你会以不同的方式和方式来组织事情?


更新。目前的情况是:

abstract class User
   public String getAddress()
   public void setAddress()
   ...

class UserSea extends User
class UserWheel extends User

abstract class UserCollection
   protected abstract User createNewUser();
   public abstract List<User> listAllSubUsers();
   public User loadById(int idUser) {
       User newUser = createNewUser();
       //populate it
       return newUser;
   }

class UserSeaCollection
   protected User createNewUser() {
        return new UserSea();
   }
   public List<User> listAllSubusers()

class UserWheelCollection
   protected User createNewUser() {
       return new UserWheel();
   }
   public List<User> listAllSubusers()

按照trashgod的建议,我试图理解策略模式,这是我的第一次尝试:

interface SubuserManagement
    List<User> listAllSubUsers();
    ...

interface UserCrud
   void create();
   User readById(int idUser);
   void update();
   void delete();

class UserSeaCollection implements SubUserManagement, UserCrud

   private SubUserManagement subuserBehavior = new SubUserManagementSeaImplementation();
       private UserCrud userCrudBehavior = new UserCrud();

   void create {
       subUserBehavior.create();
   }
   ...

class UserWheelCollection implements SubUserManagement, UserCrud
       ...

class SubUserManagementWheelImplementation implements SubUserManagement
    List<User> listAllSubUsers();

class SubUserManagementSeaImplementation implements SubUserManagement
    List<User> listAllSubUsers();

class UserCrudImplementation implements UserCrud //only 1 implementation
   void create();
   User readById(int idUser);
   void update();
   void delete();

在第一次尝试中,我创建了 UserCollectionWheel 和 UserCollectionSea,它们不再共享一个公共超类,而是实现了相同的接口。实际的实现是在外部类中。

现在 UserCollectionWheel 和 UserCollectionSea 实际上是同一个类,只是我分配给它们的行为不同。或者,我可以只用 setter 编写一个类:

UserCollection userColl = new UserCollection();
userColl.setSubUserBehavior(new SubUserManagementSeaImplementation());
userColl.setCrudBehavior(new UserCrud());

但是初始化会很麻烦,特别是如果我有更多的行为类。那么我做错了什么?如何正确组织?

更新 2:我写了一个 blog post 与我已经实现的设计。

【问题讨论】:

  • 我不了解驱动这种结构的外部力量以及您通过这样做试图实现的目标。您能否更详细地描述您正在尝试做的事情,但避免使用编程术语和特定数据结构的描述。而是描述这两个用户做了什么,使用你的程序的人从他们那里得到了什么。
  • @Hovercraft,谢谢,我会尝试一下,简要说明。这些用户会做很多事情,比如创建/更新/删除用户,创建/更新/删除子用户,发送不同的激活邮件(委托)等等。由于这是一个 stackoverflow 问题,我试图将范围缩小到两个操作:列出所有子用户并按 id 加载单个用户。例如列出所有子用户根据用户类型有一个完全不同的查询,所以我想使用继承来映射不同的行为。

标签: java oop design-patterns


【解决方案1】:

考虑使用strategy pattern 中的接口将其封装,而不是继承行为。用户在拥有interface ListSubUsersStrategyinterface CreateUserStrategy 等的两个具体实现中的任一个方面会有所不同。

另见相关bridge pattern

附录:在下面的示例中,每个用户都有一个寻找子用户的具体策略。特别是,listAllSubUsers() 调用接口方法,自动分派到正确的具体实现。该模式并不能减轻您编写接口的具体实现的负担,但它确实将它们解耦,确保更改一个不会破坏另一个。

控制台:

A has wheel users.
B has sea users.
C has wheel users.

代码:

import java.util.ArrayList;
import java.util.List;

/** @see http://stackoverflow.com/questions/6006323 */
public class UserMain {

    private static final List<User> users = new ArrayList<User>();

    public static void main(String[] args) {
        users.add(new User("A", new WheelStrategy()));
        users.add(new User("B", new SeaStrategy()));
        users.add(new User("C", new WheelStrategy()));
        for (User user : users) {
            user.listAllSubUsers();
        }
    }

    private static class User {

        private String name;
        private SubUsersStrategy suStrategy;

        public User(String name, SubUsersStrategy suStrategy) {
            this.name = name;
            this.suStrategy = suStrategy;
        }

        public void listAllSubUsers() {
            System.out.print(name + " manages ");
            List<User> subUsers = suStrategy.getList();
        }
    }

    private interface SubUsersStrategy {

        List<User> getList();
    }

    private static class WheelStrategy implements SubUsersStrategy {

        @Override
        public List<User> getList() {
            System.out.println("wheel users.");
            return null;
        }
    }

    private static class SeaStrategy implements SubUsersStrategy {

        @Override
        public List<User> getList() {
            System.out.println("sea users.");
            return null;
        }
    }
}

【讨论】:

  • 谢谢,我已经更新了这个问题,提供了有关当前情况和诱人策略模式的更多详细信息。你能看看,看看如何解决它?
  • 我添加了一个示例,为每个用户提供寻找子用户的策略。
  • 我明白了,所以你不会使用“Collection”类,而是将所有内容都放在 User 中。我曾经读过单一责任原则,我认为列出自己、查找自己、保存自己不应该是用户的责任。我还读到,帮助设计的一种方法是像人类一样思考一个班级,一个狭窄领域的专家。因此,当用户代表自己时,他们持有自己的数据,就像护照一样,我们可以考虑让专家来列出/搜索他们,比如 UserManager 或类似的。你认为这是不可取的吗?
  • 我对问题域的理解还不够透彻。我的示例旨在为用户提供一种在没有instanceof 的情况下拥有正确行为的方法。
【解决方案2】:

FWIW,这是我对 Trashgod 策略模式方法的看法。这不是答案,只是对 Trashgod 答案的支持性解释。

public interface UserStore<T extends User> {
    public T create(int id);
    public List<T> listAll();
}

public class SeaUserStore implements UserStore<SeaUser> {
    public SeaUser create(int id) { return new SeaUser(id); }
    public List<SeaUser> listAll() { whatever }
}

// the dry counterpart of 'sea' is either 'land' or 'road', not 'wheel' :)
public class RoadUserStore implements UserStore<RoadUser> {
    public RoadUser create(int id) { return new RoadUser(id); }
    public List<RoadUser> listAll() { whatever }
}

public class UserCollection<T extends User> {
    private UserStore<T> store;
    public UserCollection(UserStore<T> store) {
        this.store = store;
    }
    public List<T> listAll() {
        return store.listAll();
    }
    public T getById(int id) {
        T user = store.create(id);
        // populate
        return user;
    }
}

这留给客户端来创建用户集合。你可以把它包起来;将 UserCollection 构造函数设为私有,并添加:

public class UserCollection<T extends User> {
    public static UserCollection<SeaUser> createSeaUserCollection() {
        return new UserCollection<SeaUser>(new SeaUserStore());
    }
    public static UserCollection<RoadUser> createRoadUserCollection() {
        return new UserCollection<RoadUser>(new RoadUserStore());
    }
}

【讨论】:

  • +1 表示genericity。另请参阅Bloch,第 5 章,第 27 项,通用静态工厂方法
  • 感谢cmets,现在我正在研究/实验它们。暂时一个问题。您说“将 UserCollection 构造函数设为私有,然后添加:”——调用 UserCollection.createSeaUserCollection()new SeaUserCollection() 的优势是什么——我是不明白为什么您似乎建议第一种方法更好。
  • @stivlo:优点是它可以编译。在我的示例中没有 SeaUserCollection。没有必要。你可以创建一个,但创建一个完整的类只是为了提供一个构造函数似乎很浪费。
  • 看着这个,我不满意。集合和商店之间的划分是任意的。如果UserStore.listAll() 返回一个List&lt;Integer&gt; 会更好一些,那么集合仍然可以负责填充实例(委托给存储以获取 ID 并创建对象)。但老实说,我想知道 Stivlo 最初的继承方法是否实际上不是这里的最佳解决方案。
  • @trashgod:我认为对象关系阻抗不匹配是一个被严重夸大的问题。我认为这主要是由不了解其中一项或两项技术的不充分开发人员部署的烟幕。但这是我们应该为 cljp 保留的讨论。
【解决方案3】:

我可以用 instanceof 检查 UserCollection 的具体类并创建一个适当的子类,但它似乎不是面向对象的,并且违反了开闭原则。

这会破坏 Liskovs 替换原则,这确实是检查一个人是否具有健全的对象层次结构的规则。该规则说,如果一个方法需要 User 作为参数,那么用户是 CompledUserDirtyUser 还是任何其他用户都无关紧要。 (因此您可能没有任何 ifs 检查 instanceof 等)。

继承是关于“is-a”关系的,这基本上意味着您应该能够获取任何派生对象并将其传递给期望基类的方法。如果你不能做到这一点,你就有很多麻烦了。这样的问题是因为你的课程失败了。

另一个想法是在 UserCollection 中添加一个抽象方法 createNewUser() 并在子类中添加具体实现以返回一个新实例,因此基类只需调用此 createNewUser() 方法

这是一种更好的方法,因为调用者 if createNewUser 并不关心它获得了哪种用户。它只知道它是一个用户。

这种方法称为工厂方法模式。

【讨论】:

    猜你喜欢
    • 2012-10-07
    • 2014-12-09
    • 1970-01-01
    • 2020-06-24
    • 1970-01-01
    • 1970-01-01
    • 2015-02-25
    • 2012-01-03
    • 1970-01-01
    相关资源
    最近更新 更多