JavaScript 函数与对象 电话交换机测试

28 阅读3分钟

本文将修改一个电话交换机测试程序,通过这个具体的案例,把数据方法逐渐封装起来,体会js中函数与对象的使用。

为啥要封装?

首先一个未经过封装的电话交换机测试长这样:

for (let number = 1001; number < 1006; ++number) {
    switch (number) {
        case 1001:
            console.log('张三');
          break;    
        case 1002:
            console.log('李四');
            break;
        case 1003:
          console.log('王五');
          break;
        case 1004:
            console.log('赵六');
            break;
        default:
            console.log('你拨打的是空号!');
            break;
    }
}

上述程序用一个for循环和siwtch case语句遍历所有电话交换机号码,并完成测试。代码非常简单但非常笨拙,整个程序的电话机实现和测试部分混在一起。理论上这应该是两个彼此独立的任务,任务之间的依赖度太高会大大提高维护成本。

因此我们需要将这两个任务打包成独立的执行单元,下面我们对上述代码进行封装。

function telephoneExchange(number) {
  switch (number) {
      case 1001:
          console.log('张三');
          break;
      case 1002:
          console.log('李四');
          break;
      case 1003:
          console.log('王五');
          break;
      case 1004:
          console.log('赵六');
          break;
      default:
          console.log('你拨打的是空号!');
          break;
  }
}
function testTelephoneExchange (callback) {
    for (let number = 1001; number < 1006; ++number) {
        callback(number);
    }
}
testTelephoneExchange(telephoneExchange);

上述代码我们将不同的任务封装成独立的函数,解决了高耦合度的问题。但在实际中,只有四条线路的电话机没有任何意义,现实中的电话网络至少上百条,且通常应该具备查询、增加、修改和删除的功能。而switch case显然不是一个好的实现,它不具备可扩展性。

因此我们需要将执行任务的函数与其相关的数据进一步封装成一个整体。

class TelephoneExchange {
    constructor(names) {       // names 形参允许指定加入该电话交换机的初始名单
         this.mp = new Map();
         this.firstNum = 1001;    // 该电话交换机的第一个未被占用的号码
    for(let name of names) {
        this.firstNum++;
        this.mp.set(this.firstNum, name);    // 为初始名单分配电话号码 
    }
}
    // 为新客户添加线路
    add(name) {                   
        this.firstNum++;
        this.mp.set(this.firstNum, name);
    }
    // 删除线路
    delete(number) { 
        this.mp.delete(number);
    }
    // 修改已有线路的所属人
    update (number, name) {      
        if (this.mp.has(number)) {
            this.mp.set(number, name);
        } 
        else {
            console.log(number + '是空号!');
        }
    }
    // 拨打指定线路  
    call(number) {  
         if (this.mp.has(number)) {
             let name = this.mp.get(number);
             console.log('你拨打的用户是: ' + name);
         } 
         else {
             console.log(number + '是空号!');
         }
    } 
    
    callAll() {                
        for (let number of this.mp.keys()) {
            this.call(number);
        }  
    }
};
let phoneExch = new TelephoneExchange(['张三', '李四', '王五', '赵六']);
phoneExch.callAll();
console.log('-----------');
phoneExch.add('owlman');
phoneExch.callAll();
console.log('-----------');
phoneExch.delete(1002);
phoneExch.callAll();
console.log('-----------');
phoneExch.update(1003,'batman');
phoneExch.callAll();
console.log('--------------');

上述程序用map把数据封装进了TelephoneExchange类,并且实现了增加、查询、修改、删除等功能。测试部分依次执行增删查改的操作,并在每一步操作之后调用callAll呼叫一遍所有线路检查是否正常。但仍存在问题,上述测试代码只能测试TelephoneExchange类,不能重用。因此需要重写测试部分,这涉及操作接口与操作实现的耦合度问题。

我们将测试封装如下:

function testTelephoneExchange(phoneExch) {
  phoneExch.callAll();
  console.log('-----------');
  phoneExch.add('owlman');
  phoneExch.callAll();
  console.log('-----------');
  phoneExch.delete(1002);
  phoneExch.callAll();
  console.log('-----------');
  phoneExch.update(1003,'batman');
  phoneExch.callAll();
  console.log('-----------');}

这样一来,我们以后就只需要以TelephoneExchange类及其子类的对象为实参来调用该函数,即可完成不同电话交换机的测试,例如:

class TelephoneExchange{
    // 之前的实现,具体代码请参考上一章内容
};
testTelephoneExchange(new TelephoneExchange(['张三', '李四']));
class InternetExchange extends TelephoneExchange{
    // 保留基类的接口,但换一种实现方式
};
testTelephoneExchange(new InternetExchange(['张三', '李四']));

本文参考了《JavaScript全栈开发》凌杰