如何在不复制代码的情况下去除耦合?

How to remove coupling without duplicating the code?

我正在为多人游戏制作服务器,但在弄清楚如何使逻辑模块化时遇到了一些问题。当玩家移动时,我想更新对象,更新数据库记录并将移动发送到所有连接的客户端。现在我是这样做的:

var socket = require('./socket');
var db = require('./db');

function Player(id, x, y) {
    this.id = id;
    this.x = x;
    this.y = y;
}

Player.prototype.move = function(x, y) {
    this.x = x;
    this.y = y;

    db.update({id: this.id}, {x: x, y: y});
    socket.emit('player moved', {x: x, y: y});
}

这将套接字和数据库紧密耦合到感觉不对的播放器对象。但是,我不想每次更新玩家对象时都在游戏循环中执行 db.update 和 socket.emit。

这样做的正确方法是什么?

如果 Player "class" 的唯一目的是处理这类更新,那么我看到的唯一真正的改变是参数化播放器代码,而不是让它直接包含数据库和套接字。但这是一个非常小的变化,因为 require 调用已经有些解耦了。

我可以看到三种方法:

  1. 将它们设为 Player:

    的参数
    function Player(id, x, y, db, socket) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.db = db;
        this.socket = socket;
    }
    
    Player.prototype.move = function(x, y) {
        this.x = x;
        this.y = y;
    
        this.db.update({id: this.id}, {x: x, y: y});
        this.socket.emit('player moved', {x: x, y: y});
    };
    

但是你最终会在每个对象中得到这些引用。

  1. 将它们作为创建 Player 构造函数的构建函数的参数

    exports.buildPlayerClass = function(db, socket) {
    
        function Player(id, x, y) {
            this.id = id;
            this.x = x;
            this.y = y;
        }
    
        Player.prototype.move = function(x, y) {
            this.x = x;
            this.y = y;
    
            db.update({id: this.id}, {x: x, y: y});
            socket.emit('player moved', {x: x, y: y});
        };
    };
    

    调用模块中的用法是:

    var socket = require('./socket');
    var db = require('./db');
    var Player = require('./player').buildPlayerClass(db, socket);
    
    // ...
    
    var p = new Player("some-id", 0, 0);
    
  2. 使 Player 成为发出 move 事件的 EventEmitter

    var EventEmitter = require('events').EventEmitter;
    
    function Player(id, x, y) {
        EventEmitter.call(this);
        this.id = id;
        this.x = x;
        this.y = y;
    }
    Player.prototype = Object.create(EventEmitter.prototype);
    Player.prototype.constructor = Player;
    
    Player.prototype.move = function(x, y) {
        this.x = x;
        this.y = y;
    
        this.emit('move', {p: this, x: x, y: y});
    };
    

    用法是:

    var socket = require('./socket');
    var db = require('./db');
    
    // ...
    
    var p = new Player("some-id", 0, 0);
    p.on('move', function(e) {
        db.update({id: e.p.id}, {x: e.x, y: e.y});
        socket.emit('player moved', {x: e.x, y: e.y});
    });
    
  3. 使Player使用普通事件发射器:

    function Player(id, x, y, emitter) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.emitter = emitter;
    }
    
    Player.prototype.move = function(x, y) {
        this.x = x;
        this.y = y;
    
        this.emitter.emit('move', {p: this, x: x, y: y});
    };
    

    用法是:

    var playerEvents = new EventEmitter();
    playerEvents.on('move', function(e) {
        db.update({id: e.p.id}, {x: e.x, y: e.y});
        socket.emit('player moved', {x: e.x, y: e.y});
    });
    
    // ...
    
    var p1 = new Player("p1", 0, 0, playerEvents);
    var p2 = new Player("p2", 0, 0, playerEvents);
    

我本人非常有兴趣看到在这种情况下解耦的建议。我的个人建议

Player.prototype.observers = [];
Player.prototype.observers.push(
      function(){
          if(conditionToUpdateDb){
              db.update({id: this.id}, {x: this.x, y: this.y});
          }
      });
Player.prototype.observers.push(
      function(){
          if(conditionToEmit){
              socket.emit('player moved', {x: this.x, y: this.y});
          }
      });

然后

Player.prototype.move = function(x, y) {
    this.x = x;
    this.y = y;

    for(key in this.observers){
        this.observers[key]();
    }
}

如果您想要不同的 observers 对应不同的 Players,您可以选择

function Player(id, x, y, observers) {
    this.id = id;
    this.x = x;
    this.y = y;
    this.observers = observers;
}

然后

for(key in this.observers){
        this.observers[key]();
    }

我会做两件事:

1) 让您的播放器 class 发出事件。这使您可以在将来添加更多触发器而无需修改您的播放器 class

2) 使用 pos 对象来保存您的 x 和 y 值。这使得代码更加简洁。

// In Player file
var EventEmitter = require('events').EventEmitter;

function Player(id, pos) {
  this.id = id;
  this.pos = pos;
}

Player.events = new EventEmitter();

Player.prototype.move = function (pos) {
  Player.events.emit('move', this);
};

module.exports = Player;

// In database file...
var db = require('./db');
var Player = require('./Player');

Player.events.on('move', function (player) {
  db.update({id: player.id}, player.pos);
});

// In socket file...
var socket = require('./socket');
var Player = require('./Player');

Player.events.on('move', function (player) {
  socket.emit('player moved', player.pos);
});