高性能聊天系统

  作者:板桥banq

上页

1.4.5  重整Refactoring

现在,在Socket I/O的读写部分已经有不少语句,需要完成几个功能:从Queue中取出或放入;协议封装;获得特定大小的byte数组。最后一个功能特别说明一下:设定byte数组需要初始化,因此在未读取数据之前只能规定好数组的大小。但是如果数据包很大,可能需要调整初始化参数,这个调整为以后可能修改Socket I/O部分留下隐患,这是面向对象程序设计的一个忌讳。

在目前的考虑范围已经预计到将来可能需要修改这部分代码,而设计者的初衷是不想这部分代码随便被修改,希望被完整封装,因为Socket I/O部分是整个系统的核心,不希望没有专门相关非堵塞I/O知识的程序员再深入Socket I/O进行修改。

在这种情况下,就开始要考虑重整(Refactoring),消灭这个隐患。决定重整之前,如果还有其他因素促进重整就更好了,Socket I/O读写部分功能有两个,整个Socket底层有4处代码:UDPClient、服务器端的UDPHandlerTCPCLient和服务器端的TCPHandler,每个类中有两次Socket读写操作,Socket读写操作代码发生重复两次以上。

当一种现象重复3次以上时,就需要使用重整了。

重整是在不改变代码功能的情况下,提高代码质量的一种手段。在一般编程中,程序员和项目经理更多的是看重功能如何迅速地被完成,而对代码质量则不重视,其实,代码质量对有效率地快速完成新的功能是有重要意义的,正所谓“磨刀不误砍柴工”,在系统开发初期,只是一味地增加新功能,不实现代码重整,这种弊病可能看不出来,但是系统达到一定规模,矛盾就开始出现甚至激化。

客户抱怨自己的新功能无法快速完成,甚至拖延很长的时间,但是在完成后,又影响了其他已经成熟运行的功能,客户不得不再去测试以前的程序功能,疲于奔命,后悔上了一条“不归路”;而程序员也在抱怨诉苦:系统增加修改功能很困难,原来很多功能代码纠缠在一起,要分开它们已经很困难,不用说在其之间增加新的功能。

这些问题都是由于没有经常实现重整造成的,重整和新功能开发如同两条腿走路,特别是新功能开发,是促使重整的最好前提,只有将旧代码重整理顺,增加新功能才会更迅速,因此重整和新功能增加在一个系统开发中是轮流交替进行的。

现在,开始对Socket I/O部分实现重整,将这些重复的Socket读写代码集中到一个类中,这样将与Socket无关的代码全部从Socket底层分离出来。

建立一个SocketDataHandler,主要封装Socket I/O读写方面的功能,如下(程序1-10):

程序1-10

/**

 * 本类是供Socket I/O调用,以实现信号的发送和收取

 * MessageQueueSocket I/O的接口部分

 * <p>Copyright: Jdon.com Copyright (c) 2003</p>

 * <p>Company: 上海极道计算机技术有限公司</p>

 * @author banq

 * @version 1.0

 */

public class SocketDataHandler {

  private final static String module = SocketDataHandler.class.getName();

  //包装工厂

  private final static WrapFactory wrapFactory = WrapFactory.getInstance();

  //队列Queue

  private final static MessageQueue messageQueue = MessageQueue.getInstance();

  private final static int DATA_LENGTH = 1024;

  private int queueType;

  public SocketDataHandler(){}

  //获得特定大小的byte数组,以接受数据,可根据应用调整

  public byte[] getByte() {

    return new byte[DATA_LENGTH];

  }

 

  /**

   * Queue中取出Request 并实行协议包装

   */

  public byte[] sendRequest() {

    byte[] request = null;

    try {

      ByteArrayOutputStream outByte = (ByteArrayOutputStream) messageQueue

          removeReqFirst();

      request = wrapFactory.getRequest(outByte.toByteArray());

    } catch (Exception ex) {

      Debug.logError("sendRequest() error:" + ex, module);

    }

    return request;

  }

  /**

   * Request去除协议解码,正文内容保存到Queue

   */

  public void receiveRequest(byte[] array) {

    try {

      byte[] content = wrapFactory.getContentFromRequest(array);

      InputStream bin = new ByteArrayInputStream(content);

      messageQueue.pushRequest(bin);

    } catch (Exception ex) {

      Debug.logError("sendRequest() error:" + ex, module);

    }

  }

 

  /**

   *  Queue中取出Response 并实行协议包装

   */

  public byte[] sendResponse() {

    byte[] response = null;

    try {

      ByteArrayOutputStream bout = (ByteArrayOutputStream) messageQueue.

          removeResFirst();

      response = wrapFactory.getResponse(bout.toByteArray());

    } catch (Exception ex) {

      Debug.logError("sendRequest() error:" + ex, module);

    }

    return response;

  }

  /**

   * Response去除协议解码,正文内容保存到Queue

   * @param array

   */

  public void receiveResponse(byte[] array) {

    try {

      MessageQueue messageQueue = queueFactory.getQueue(queueType);

      byte[] response = wrapFactory.getContentFromResponse(array);

      InputStream bin = new ByteArrayInputStream(response);

      messageQueue.pushResponse(bin);

 

    } catch (Exception ex) {

      Debug.logError("sendRequest() error:" + ex, module);

    }

  }

}

在这个SocketDataHandler中集中了Socket读写的4种方法,这样可以被客户端和服务器两个方面使用。以UDPClient为例子,它的doKey部分代码重整为如下:

private void doKey(SelectionKey key) throws Exception {

    DatagramChannel keyChannel = null;

 

    try {

      keyChannel = (DatagramChannel) key.channel();

      if (key.isReadable()) { //如果可以从服务器读取response数据

        Debug.logVerbose("get response from the Server", module);

        //可以在不更改本类代码情况下,更改byte数组大小

byte[] array = socketDataHandler.getByte();

        ByteBuffer buffer = ByteBuffer.wrap(array);

        keyChannel.receive(buffer);

        //精简为一条语句

        socketDataHandler.receiveResponse(array);

 

        key.interestOps(SelectionKey.OP_WRITE);

        selector.wakeup();

      } else if (key.isWritable()) { //如果可以向服务器发送request数据

        Debug.logVerbose("-->begin to send request", module);

//精简为一条语句

        byte[] request = socketDataHandler.sendRequest();

 

        ByteBuffer buffer = ByteBuffer.wrap(request);

        keyChannel.write(buffer);

        key.interestOps(SelectionKey.OP_READ);

        selector.wakeup();

      }

    } catch (Exception e) {

      Debug.logError("run error:" + e, module);

      close(keyChannel);

      throw new Exception(e);

    }

}

这样,通过SocketDataHandler的屏蔽,使得外界不必直接在Socket I/O读写部分改动代码,Socket底层部分变得更加稳定和健壮。

这样重整后,为增加新功能提供了方便。例如,因为Socket底层有两种协议实现TCPUDP,而MessageQueue是负责传送数据给Socket的,那么MessageQueue对象也需要两种,分别对应两种Socket连接。

原来设计开发时,使用了单态模式保证全局只有一个MessageQueue对象,这与功能设计是矛盾的。因此,必须使得一个JVM中有两个MessageQueue对象,使用工厂方法模式可以创建一个专门生产Queue产品的Queue工厂,代码如下:

public class QueueFactory {

   //TCP队列Queue

    private final static MessageQueue tcp_queue = new MessageQueue();

    //UDP队列Queue

    private final static MessageQueue udp_queue = new MessageQueue();

 

    public final static int TCP_QUEUE = 1;

    public final static int UDP_QUEUE = 2;

    private final static QueueFactory factory = new QueueFactory();

    public static QueueFactory getInstance(){

        return factory;

    }

    //工厂方法

    public  MessageQueue getQueue(int queueType){

        if (queueType == TCP_QUEUE)

            return tcp_queue;

        else

            return udp_queue;

    }

}

GOF的《设计模式》中关于工厂方法的定义如下:定义一个用户创建对象的接口,让子类决定实例化哪个类,在QueueFactory中使用Java的抽象类实现了接口和子类的合二为一。

QueueFactory是获得不同的队列Queue,使用修改SocketDataHandler一个类就可以了,修改SocketDataHandler如下:

public class SocketDataHandler {

  //使用QueueFactory

  private final static QueueFactory queueFactory = QueueFactory.getInstance();

  //更改构造方法,加入Queue类型参数,由外界决定使用Queue的类型

  public SocketDataHandler(int queueType){

     this.queueType = queueType;

  }

 

}

这样,通过少许变动就将整个接口层拓展为支持两种Socket同时运行的系统了,如果没有之前Socket I/O的读写重整,那么这段代码增加将在4个类中进行:UDPClientUDPHandlerTCPClientTCPHandler,不但琐碎麻烦,还可能在简单重复编码中出现人为的失误。

下页

  first