您现在的位置: J2ME开发网 >> Java技术 >> 语言基础 >> 用户界面 >> 文章正文
使用Java开发计算器(一)
作者:mydeman    文章来源:本站原创    点击数:    更新时间:2005-7-28

     以前一直做J2EE的程序,对J2SE的程序不很了解特别是UI设计。因此虽然如此小的一个程序,对我来说都是一个很大的挑战。可能网上的计算器很多,Java版本的也不少,但是好像实现的功能都是比较简单(应该是我没有发现),于是就想自己写一个。从上一个星期开始设计,花费了将近两个星期的时间,终于在昨天把它作完了。在这期间我遇到不少的问题,同时也学到不少的知识,在这里希望可以和大家共享。
    一、设计思想
    1、界面与逻辑相分离的原则。应用程序都需要有操作界面和用户互动,并且需要有后台逻辑对用户的输入作相应的处理。面向对象的编程语言最大的好处,就是提高了程序的可重用程度,降低维护成本。因此,如果我们把用户界面和业务逻辑混在一起的话,也同样可以实现的功能,但是到了最后,你会发现这个程序,就像一个摇摇欲坠的楼房一样,根本就不能碰,跟别说升级和维护。但是如果把界面和逻辑分开,界面或者逻辑的修改都不会干扰到对方,并且这个业务逻辑的程序同样可以在另外一个具有相同逻辑的系统中使用,因此大大提高了程序的可重用性和维护性。
    2、单一职责原则。我们在设计时总是在降提高内聚性,降低耦合性,而这个原则恰恰就是降低耦合性的一个很好的原则。每一个类只承担属于本身的责任,也就是仅有一个可以引起它变化的原因。在这个计算器的开发过程中,很好的遵循了这个原则。就用户界面的设计而言,它包含了很多不同功能的按钮,但是仔细研究就会发现,有些按钮的作用是相同的,例如对于数字键来说,它们的功能就是按下时在显示器显示相关的数字,虽然小数点和正负号不是数字键,但是它们和数字键的功能是一致的,因此就把它们放在了一个面板中(NumberPanel)。剩余的按键也是按照这个原则划分为了几个不同的面板,例如运算符面板(OperatorPanel)、设置位宽的面板(BitWidthPanel)、各进制之间的切换面板(SystemPanel)等等。
    3、开放—封闭原则(OCP)。这是援引Robert C. Martin所著的《敏捷软件开发——原则、模式与实践》一书中的话。它的意思是说,根据这个原则设计的模块有两个特征:对于扩展是开放的,也就是模块可以随时根据需求的变化进行扩展;对于修改是封闭的,对模块进行扩展时不需要修改模块的任何代码,并且模块内部任何修改都不应该影响到外部程序。因此在程序的设计中,一个类对于客户程序员无论何时都提供一个相同的接口,外部传入一个参数,这个类就像相应的传回一个结果。计算器中的业务逻辑程序,例如数学计算类(MathCompute)和各进制之间的转换类(Convertion)都使用了这个原则。
    4、保证提供给外部的每一个方法都可以正常运行。在以往的开发中,总是一直得写呀写呀,并不管写出的类能不能正确运行。于是到了最后的集成阶段,才忽然发现程序根本就不能通过。这时的程序已经是一个很庞大的怪物,要想跟踪一个小小的错误,要花费不少的心机。因此在开发过程一定注重单元测试,要保证提供给外部的每一个方法都可以正确的运行。在这个过程,JUnit帮了很大的忙,在后面再详细介绍它的使用方法。
    以上的这些设计思想和原则给整个开发过程带了很大的帮助,并且使开发变得有趣起来。
    二、详细开发过程
    这个开发的工程都是由设计、编码和测试三部分组成。下面就各进制之间的转化类Convertion来说明开发过程。
    1、设计。
    首先应该分析这个说要完成的功能,它的主要任务是完成十六进制、十进制、八进制和二进制之间的相互转化,需要包含负数,但不用考虑小数。
    其次,确定接口参数。根据功能可知,它传递给外部的就是一个转后的数据,而外部除了需要传递给转化前的数据以外,是否还需要其他参数呢?我们都知道,十进制的负数同样可以利用其他进制进行表示,例如二进制,转化方法是:求十进制数的绝对值对应的二进制数,最高位为1;然后对二进制逐位求反,最高位除外;接着最低一位加1,需要进位的话依次进位,最高位除外。如今的问题就摆在了我们面前,我们如何知道这个数的最高位是第几位呢,也许有人会说,那就用转化后的前面再加上一位,好,即使这样可以成立,那么-1转化后就是11,3转化后也为11,现在如果需要现在的把这个数二进制数再转化为十进制,那么我们如何知道原来的是正数还是负数呢,如果要转化为十六进制,八进制又当如何呢?经过以上的分析我们发现,还应该有另外一个参数——二进制时的宽度,只有这样才能知道谁是最高位,因此我们就称这个参数为位宽。
    最后,设计类。根据第二步的分析,因为位宽对每一个方法都是一样,所以可能把它作为类的成员变量,而另外两个参数转化前的值和转化后的值就分别作为输入参数和返回值。而类对外的接口只有各个转化的方法,因此这几个方法设计为public权限,而其它所有的辅助方法都为private权限。位宽通过构造函数传递,外部不能直接访问和修改,设置也设置为private权限。为了减少计算量,所有的转化都以二进制为中介。例如十六进制到十进制,是十六进制→二进制→十进制。
   2、编码。经过第一步的简单设计后就可以开始编码了。关于编码中的具体问题就不再说明,可以代码内的注释。代码只包含类中一部分。

public class Convertion {

  /**
   * 当前的存储位宽
   */
  private int bitWidth;

  /**
   * 构造函数,设置当前系统的位宽。
   * @param bitWidth int 位宽
   */
  public Convertion(int bitWidth) {
    this.bitWidth = bitWidth * 8;
  }

  /**
   * 十六进制到十进制的转化。注意:
   *

  • 十六进制没有小数,只能为整数;
       *
  • 转化后的最大值为Long.MAX_VALUE,大于这个值的任何数都被转化这个值;
       *
  • 如果值大于Double.MAX_VALUE时,将会抛出异常。
       * @param x String 需要转化的十六进制数字符串
       * @return String  转化后的十进制数字符串
       * @throws ComputeException
       */
      public String hexToDecimal(String strHex) throws ComputeException{
        //定义转化结果
        String strDec = "";

        //输入为空,返回空字符串
        if(strHex.length() == 0)
          return "";

        //转化
        String strBin = hexToBinary(strHex);
        strDec = binaryToDecimal(strBin);

        //返回结果
        return strDec;
      }

      /**
       * 将十六进制转化为二进制。
       * @param strHex String 十六进制数字符串
       * @return String 二进制数字符串
       * @throws ComputeException
       */
      public String hexToBinary(String strHex) throws ComputeException {
        //定义转化结果
        String strBin = "";

        //输入为空,返回空字符串
        if(strHex.length() <= 0) {
          return "";
        }

        //转化为字符数组
        char[] arrHex = strHex.toLowerCase().toCharArray();
        //得到二进制字符串
        for(int i = 0; i < arrHex.length; i++) {
          strBin += getBinstrFromHexchar(arrHex[i]);
        }

        //删除开头的0
        while(strBin.charAt(0) == '0') {
          strBin = strBin.substring(1);
        }

        return strBin;
      }

      /**
       * 将十进制数字符串转化为对应二进制数字符串。
       *


       *
  • 只能对于Long.MAX_VALUE的数值进行转化
       *
  • 只能对整数进行转化,输入小数时,就会只取小数点前面的数字。
       *
  • 对于负数,转化后的最高位表示符号位,1表示负,0表示负,求解过程:求其绝对值的二进制;
       * 各位求反;最后一位加1,即可得到负数的二进制数,例如:-5 设位宽为8位 5的二进制:101,求反:010,加1:
       * 011,结果为11111011。
       *

  •    * @param strDec String 十进制数字符串
       * @return String 二进制数字符串
       * @throws ComputeException 输入参数不合法
       */
      public String decimalToBinary(String strDec) throws ComputeException {
        //定义转化结果
        String strBin = "";
        //是否为负数
        boolean isNegative = false;

        //输入为空,返回空字符串
        if(strDec.length() <= 0) {
          return "";
        }

        //截取小数点以前的部分
        if(strDec.indexOf(".") != -1)
          strDec = strDec.substring(0, strDec.indexOf("."));

        //如果是负数,则去掉符号
        if(strDec.indexOf("-") != -1 && strDec.indexOf("-") == 0) {
          strDec = strDec.substring(1);
          isNegative = true;
        }

        //得到二进制数
        try {
          long num = Long.parseLong(strDec);
          long mod = 0;
          while (num != 0) {
            mod = num % 2;
            num /= 2;
            strBin = mod + strBin;
          }
        } catch(Exception e) {
          System.err.print(e.getMessage());
          throw new ComputeException(ComputeException.MSG_ARGUMENT_ILLEGAL);
        }

        //判断是否为负数
        if(isNegative) {
          //将字符串前加0补长,使其长度达到位宽,并且最高位为1--表示负数
          int difference = bitWidth - strBin.length();
          while(difference > 1) {
            strBin = "0" + strBin;
            difference--;
          }
          strBin = difference == 1 ? ("1" + strBin) : strBin;

          StringBuffer sbBinTemp = new StringBuffer(strBin);
          //除最高位外各位求反
          int index  = 1;
          while(index < sbBinTemp.length()) {
            if(sbBinTemp.charAt(index) == '1') {
              sbBinTemp.setCharAt(index, '0');
            } else {
              sbBinTemp.setCharAt(index, '1');
            }
            index++;
          }

          //求补,即在最后一位加1
          index--;
          while(index > 0) {
            if(sbBinTemp.charAt(index) == '1') {
              sbBinTemp.setCharAt(index, '0');
            } else {
              sbBinTemp.setCharAt(index, '1');
              break;
            }
            index--;
          }

          strBin = sbBinTemp.toString();
        }

        return strBin;
      }

      /**
       * 将二进制数转化为十六进制数。
       * @param strBin String 二进制字符串
       * @return String 十六进制字符串
       * @throws ComputeException
       */
      public String binaryToHex(String strBin) throws ComputeException {
        //定义转化结果
        String strHex = "";

        //输入为空,返回空字符串
        if(strBin.length() <= 0) {
          return "";
        }

        //将字符串前面补0,使长度成为4的整倍数
        int lengthOfBin = strBin.length();
        for(int i = 0; i < (4 - lengthOfBin % 4) && (lengthOfBin % 4) != 0; i++) {
          strBin = "0" + strBin;
        }

        //转化为十六进制数--将二进制数按照斯位划分,将四位转化为十进制数,
        //再得到十进制数对应的十六进制字符
        for(int i = 0; i < strBin.length(); i += 4) {
          String strDec = binaryToDecimal(strBin.substring(i, i + 4));
          strHex += getHexcharFromDecnum(Integer.parseInt(strDec));
        }

        //返回结果
        return strHex;

      }

      /**
       * 将二进制数转化为十进制数,转化结果不能超过Long.MAX_VALUE。
       * 特殊情况:
       *

  • 如果二进制长度达到系统位宽,并且最高位1的话,将会被转为负数,例如:位宽是8,
       * 则 11111011 --> -5
       * @param strBin String 二进制数字符串
       * @return String 十进制数字符串
       * @throws ComputeException
       */
      public String binaryToDecimal(String strBin) throws ComputeException {
        //定义转化结果
        long dec = 0;
        //标记是否为负数
        boolean isNegative = false;

        //输入为空,返回空字符串
        if(strBin.length() <= 0) {
          return "";
        }

        //判断正负数
        if(strBin.length() == bitWidth && strBin.charAt(0) == '1') {
          //将符号为去掉
          strBin = strBin.substring(1);
          isNegative = true;

          StringBuffer sbBinTemp = new StringBuffer(strBin);
          //减1
          int index = strBin.length() - 1;
          while(index >= 0) {
            if (sbBinTemp.charAt(index) == '1') {
              sbBinTemp.setCharAt(index, '0');
              break;
            } else {
              sbBinTemp.setCharAt(index, '1');
              index--;
            }
          }

          //求反
          for(index = 0; index < sbBinTemp.length(); index++) {
            if (sbBinTemp.charAt(index) == '1') {
              sbBinTemp.setCharAt(index, '0');
            } else {
              sbBinTemp.setCharAt(index, '1');
            }
          }
          strBin = sbBinTemp.toString();
        }

        //转化
        MathCompute mathCompute = new MathCompute();
        if(strBin.indexOf("1") == -1) {
          dec = (long)mathCompute.power(2, strBin.length());
        } else {
        for(int i = 0; i < strBin.length(); i++) {
          try {
            dec += Long.parseLong(strBin.substring(i, i + 1)) *
              mathCompute.power(2, strBin.length() - i - 1);
          } catch (ComputeException e) {
            throw e;
          }
        }
        }

        //返回结果
        String strDec = String.valueOf(dec);
        if(isNegative) strDec = "-" + strDec;
        return strDec;
      }

      /**
       * 根据不同的十六进制字符,得到其对应的二进制字符串。
       * @param cHex char 十六进制字符
       * @return String  二进制字符串
       */
      private String getBinstrFromHexchar(char cHex) {
        String strBin = "";

        //获取不同的二进制字符串
        switch (cHex) {
          case '0':
            strBin = "0000"; 
            break;
          case '1':
            strBin = "0001";
            break;
          case '2':
            strBin = "0010";
            break;
          case '3':
            strBin = "0011";
            break;
          case '4':
            strBin = "0100";
            break;
          case '5':
            strBin = "0101";
            break;
          case '6':
            strBin = "0110";
            break;
          case '7':
            strBin = "0111";
            break;
          case '8':
            strBin = "1000";
            break;
          case '9':
            strBin = "1001";
            break;
          case 'a':
            strBin = "1010";
            break;
          case 'b':
            strBin = "1011";
            break;
          case 'c':
            strBin = "1100";
            break;
          case 'd':
            strBin = "1101";
            break;
          case 'e':
            strBin = "1110";
            break;
          case 'f':
            strBin = "1111";
            break;
        }

        return strBin;
      }

      /**
       * 根据十进制数得到相对应的十六进制字符。
       * @param iDec int 十进制数
       * @return char 十六进制字符
       */
      private char getHexcharFromDecnum(int iDec) {
        char cHex = 'x';

        //得到十进制字符
        switch (iDec) {
          case 0:
            cHex = '0';
            break;
          case 1:
            cHex = '1';
            break;
          case 2:
            cHex = '2';
            break;
          case 3:
            cHex = '3';
            break;
          case 4:
            cHex = '4';
            break;
          case 5:
            cHex = '5';
            break;
          case 6:
            cHex = '6';
            break;
          case 7:
            cHex = '7';
            break;
          case 8:
            cHex = '8';
            break;
          case 9:
            cHex = '9';
            break;
          case 10:
            cHex = 'A';
            break;
          case 11:
            cHex = 'B';
            break;
          case 12:
            cHex = 'C';
            break;
          case 13:
            cHex = 'D';
            break;
          case 14:
            cHex = 'E';
            break;
          case 15:
            cHex = 'F';
            break;
        }

        return cHex;
      }
    }