【问题标题】:How to decouple data and behavior objects in java?java中如何解耦数据和行为对象?
【发布时间】:2017-06-21 03:19:41
【问题描述】:

我正在使用 Java 为服务器构建一个多人游戏。目前,我正在使用单个类文件来存储播放器数据并处理数据。我是初学者,所以我不知道这是一个不好的做法。 http://howtodoinjava.com/best-practices/5-class-design-principles-solid-in-java/这篇文章让我明白我违反了“单一责任原则”的规则。

这就是我的代码现在的样子。

public class PlayerSession{

    String playerId;
    String playerName;

    // 20+ player data fields, which I am trying to reduce
    // and keep only the most used data

    public void messageProcessor(JSONObject clientRequest) throws JSONException{

        switch(clientRequest.getString("task")){            
        case "login": loginProcess(); break;
        case "logout": logoutProcess(); break;

        //50+ different actions 
        }
    }

    public void populateSessionData(String playerId){
        // populate player data from database
    }

    private void loginProcess(){
        //Process login
    }

    private void logoutProcess(){
        //Process logout
    }

    //20+ other methods which do entirely different tasks.
}

随着我们添加更多功能,该类将变得极其难以维护和修改。现在我试图将这个类解耦成两个不同的类。一个,仅用于存储玩家数据,另一个用于处理行为,如下所示。

public class PlayerSession {

    final TaskHandler taskHandler = new TaskHandler();

    public void messageProcessor(JSONObject clientRequest) throws JSONException {

        switch (clientRequest.getString("task")) {
        case "login":
            taskHandler.loginProcess();
            break;
        case "logout":
            taskHandler.logoutProcess();
            break;

        // 50+ different actions
        }
    }
}

public class PlayerData {

    String playerId;
    String playerName;

    // 20+ player data fields, which I am trying to reduce
    // and keep only the most used data

    public void populateSessionData(String playerId) {
        // populate player data from database
    }
}

public class TaskHandler {

    final PlayerData player = new PlayerData();

    private void loginProcess() {
        // Process login
    }

    private void logoutProcess() {
        // Process logout
    }

    // 20+ other methods which do entirely different tasks.
}

这种设计导致为单个客户端创建 2 个额外的对象,即 PlayerData 和 TaskHandler。对于 10,000 个并发客户端的服务器,这会成为问题吗?这是正确的方法吗?如果不是,对于这种情况,最好的方法是什么?

在某处我读到对象只是为了保存数据不是一个好方法。对吗?

【问题讨论】:

  • 你在使用spring之类的框架吗?
  • @AshutoshJha 我正在使用 netty 和 websockets。这不是基于 REST 的应用程序。这是一个需要全双工实时通信的网络应用程序。

标签: java oop class-design


【解决方案1】:

你需要在这里做很多事情:

PlayerSession 类:

你为什么在这里解析JsonObject?您需要创建名为ClientRequest 的类,所有工作都将在其中完成。将您需要的所有内容从客户端请求放到此类中,并且只有那些准备好使用 something 的方法才不会从对象中提取数据并在您的代码中手动计算,这是程序方式。还将PlayerSession 重命名为Session

PlayerData 类

首先将其重命名为Player 类,它代表玩家而不是玩家数据。不要在 Player 中执行任何数据库关系操作。同样在创建之后你需要准备好使用播放器,在这里你创建实例,然后用数据填充它,最好在构造函数中完成。

TaskHandler 类

不要创建做很多事情的类,顺便说一句,你几乎以正确的方式,简单地创建接口 TaskAction 将只有一种方法 performTask() 并创建许多实现如 LoginTask, LogoutTask 等。也许您需要有一种结构,它可以为您提供特定操作的实例,也有助于摆脱手动创建具体实现,您将以更多态的方式来完成它。

public class Session {

    private final SessionActions actions;
    private final RequestFabric requests;
    private final Player player;

    public Session (SessionActionFabric actionsFabric,
                    RequestFabric requests, Player player) {
        this.actionsFabric = actionsFabric;
        this.requests = request;
        this.player = player;
    }

    void login() throws LoginException {
        Request request = request.createRequest();
        SessionAction login = actions.createActions("login", player, request);
        login.performAction();
        //something like that it's depends on whole picture of your project and maybe changed
    }

    //etc.
}

public interface Request {

    public performRequest(Player player, /*request data your api specs here*/) throws RequestException;
}

public class Player {

    private String id;
    private String name;

   public Player(String id, String name){
       this.id = id;
       this.name = name;
   }

   //getters, setters 
}

interface SessionAction {

    void performAction() throws SessionActionException;
}

class LoginAction implements SessionAction {
    private final Player player;
    private final Request request;


    LoginAction (Player player, Request request) {
        this.player = player;
        this.request = request;
    }

    void performAction() {
        // do you things here
    }

}

Q 对于10,000个并发客户端的服务器,这会成为问题吗?这是正确的方法吗?如果不是,对于这种情况,最好的方法是什么?

A 不介意性能最好关注好的设计,如果您遇到性能问题,您几乎总能找到通过好的设计(缓存、池等)改进它的方法

Q在某处我读到对象只是为了保存数据并不是一个好方法。对吗?

A你说得对,这称为贫血模型,(数据和处理它的方法是分开的)但目前它非常流行。

【讨论】:

  • 我喜欢这种方法,我正在考虑设计类似的东西。但我想避免创建太多对象。每隔 2 秒就会有来自客户端的请求。因此,为单个客户端每两秒创建一个操作对象很快就会导致 OutOfMemoryException。如果我错了,请纠正我。
  • 客户端和服务器通信是通过 JSON 进行的。这就是 JSONObject 的原因。问题中使用的所有命名都是为了更好地理解读者。关于 Player 类,你是对的。我必须在构造函数本身中填充数据。
  • @KaleshKaladharan 您需要更好地了解 Java 的工作原理,尤其是您需要阅读有关垃圾收集的更多信息。在 2 秒内创建一个对象是完全没有的。在方法中创建对象时,使用它并退出该对象垃圾收集的方法,不会抛出 OfMemoryException。附言如果您接受答案,请将其标记为已接受。
  • 感谢您的反馈。每个客户端 2 秒内一个对象。对于 10,000 多个并发客户端,仅操作接口每秒将创建 5,000 个对象。将创建更多用于完成任务的对象。这会是一个问题吗?是的你是对的。我应该了解更多关于垃圾收集的知识。
  • 我已经按照你说的做了测试。看起来,像这样创建临时对象对 JVM 影响不大。谢谢。
【解决方案2】:

根据您与乔伊的讨论。如果您担心像 LoginAction 这样的 Action 类会每 2 秒创建一次。使用单例模式,具有动作类的静态最终实例和该实例的一个静态 getter (LoginAction.getInstanceOfLoginAction()),并且每次都使用它而不是创建新的。但是要确保你的动作类是无状态的而不是有状态的!!

class LoginAction implements SessionAction {
  private static final LoginAction loginAction = new LoginAction();
  public static LoginAction getLoginAction() {
      return loginAction;
  }

  void performAction(Player player, Request request) {
    // do you things here
  }

}

另一件事是使用工厂(参见工厂模式)根据请求获取 Action 接口的实现。

【讨论】:

  • dzone.com/articles/stateful-or-stateless-classes 检查此链接一次,了解无状态与有状态。
  • 但是,当 100 个并发客户端尝试访问该对象时会发生什么。 LoginAction 应该能够使用令牌验证客户端并在其各自的 sessionData 对象上填充数据。您能否展示一些示例代码来执行此操作。谢谢。
  • 我在没有私有字段的情况下编写 LoginAction 的方式,即使有 100 个并发客户端,它也可以按预期工作。现在,如果您想在不同的线程上发送它们,那么我想知道您是否使用像 Future 这样的 java api,您是否还使用 RxJava(并行化非常棒)。
  • 我正在使用 Netty,它是异步 NIO 框架并使用 Future。看起来我把事情复杂化了太多了。目前,我的所有东西都在一个对象下工作。测试了几千个并发连接。但现在我想为应用程序添加更多功能。所以类变得难以维护。我追求一个好的设计而不影响性能。
  • 好的,然后使用 ExecutorService,如果您正在使用最新的 java 版本,请使用 CompletableFuture。
猜你喜欢
  • 1970-01-01
  • 2015-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 2016-09-09
  • 1970-01-01
相关资源
最近更新 更多