一、 枚举
1. 什么叫枚举?
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错,枚举可以让编译器在编译时就可以控制源程序中填写的非法值,通常变量的方式在开发阶段无法实现这一目标。
往枚举里添加构造方法
public enum WeekDay{
SUN(2,3),MON,TUE,WTO,TXI,FRI,SAT;
private WeekDay(){System.out.println("yi");}
private WeekDay(int day){System.out.println("er");}
private WeekDay(int day,int a){System.out.println("san");}
}
//枚举里有抽象方法!怎么去重写抽象方法?就是在元素后面加大括号就把元素变成了枚举类的子类,在子类中重写
public enum jiaoTongDeng{
//什么意思?red这个对象是个子类,加小括号里面加参数说明调用父类的有参的构造方法,加大括号说明red这个子类重写父类的抽象方法
red(30){
public jiaoTongDeng nextDeng(){
return blue;
}
},blue(45){
public jiaoTongDeng nextDeng(){
return yellow;
}
},yellow(5){
public jiaoTongDeng nextDeng(){
return red;
}
};
public abstract jiaoTongDeng nextDeng();
//成员变量
private int time;
private jiaoTongDeng(int time){this.time=time;}
}
枚举总结
1.枚举可以带有(必须是私有的)构造方法、抽象方法
2但注意的是枚举里不管有什么方法枚举的元素都必须在枚举里的第一位然后最后一个枚举元素后面加分号
3.只要用到枚举类,枚举类里的元素都会初始化,调用无参的构造方法
4.那如果要调用有参的呢?在元素后面加括号!里面传入相应的类型参数
5.枚举里只有一个元素时,就可以作为一种单例的实现方式。
二、 反射
1.什么叫字节码?
首先要从硬盘把类的字节码加载到内存中,然后再用这些字节码变成一个个对象。每一份字节码都是Class的实例对象
例:Class cls1 = Date.class;
获得字节码的方法,有三种:
1、对象.getClass();
2、Class.forName("类名");>Class.forName("java.lang.String");
有种加载过 , 有种没加载过
3、类名.class
Class有九个实例对象!
2.什么叫反射?
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类,表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的实例对象来表示,他们是Field,Method,Constructor,Package等等。
1.反射变量Field
bianLiangFanShe blfs = new bianLiangFanShe(3,5);
//利用getField(name)获得参数指定的变量名
Field fieldY = blfs.getClass().getField("y");
//fieldY的值是多少?5?错 fieldY不代表对象值 是类个变量
//用get(对象)方法获得指定对象fieldY这个变量的值
System.out.println(fieldY.get(blfs));
//获得私有的变量
//用getDeclaredField(变量名)获得私有变量的变量名。可以看到变量名了
Field fieldX = blfs.getClass().getDeclaredField("x");
//可以看到变量名 但拿不到值怎么办?用setAccessible(true)强行拿到值
fieldX.setAccessible(true);
System.out.println(fieldX.get(blfs));
//扫描一个对象的变量 把含有"b"的字符串改成"a"
//定义了一个方法把要改的对象传进去
saoMiaoBianLiang(blfs);
System.out.println(blfs);
private static void saoMiaoBianLiang(Object obj)throws Exception {
// TODO Auto-generated method stub
//获得传进来那个对象所有的变量
Field[] fields = obj.getClass().getFields();
//遍历这个装有变量的数组
for(Field ff: fields){
//判断遍历过程中发现这个变量的类型是String类型就进这个if语句
if(ff.getType()==String.class)
{
//然后就用get(对象)把这个变量ff的值取出来
String oldValue = (String)ff.get(obj);
//用变量名.replace(被替换值,替换值)把这个变量的值改后给一个新的变量
String newValue = oldValue.replace("b", "a");
//然后用set(要操作的那个对象,新值)重新赋值回那个变量
ff.set(obj, newValue);
}
}
}
2. 方法的反射Method
//用getMethod("方法名",那个参数的类型)因为一个类中方法可以重载,所以要判断你要得到那个方法
Method method = String.class.getMethod("charAt", int.class);
//当你得到了这个方法后,因为charAt(index)这个方法时必须有参数的所以你要给他参数
//用invoke(对象名,参数2)这个方法
//如果invoke()方法的第一个参数为null表示这个方法时静态方法
System.out.println(method.invoke(str1, 1));
//用数组的方式表示
System.out.println(method.invoke(str1, new Object[]{2}));
3. 构造方法的反射Constructor
//用getConstructor(参数1,参数2,...)获得相应的构造方法
//new String(new StringBuffer("abc"));下面的方式替代这种
//根据传入的参数获得指定String类型的哪种构造方法
Constructor constructor = String.class.getConstructor(StringBuffer.class);
//得到这个构造方法后用newInstance()方法重新初始化new一个新的对象。
//得到某一个构造方法时要用到类型,调用获得的方法时要用到上面相应类型的实例对象
String str = (String)constructor.newInstance(new StringBuffer("abc"));
System.out.println(str1.charAt(2));
4. 数组的反射
1.数组反射的话具有相同数据类型的字节码是同一个
例:
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());
System.out.println(a1.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());
//一维数组是Object
Object o1 = a1;
//String是Object
Object o2 = a4;
// Object[] o3 = a1;//int型不是Object
//a3的意思是 有一个数组 里面是整型的一维数组他属于Object
Object[] o4 = a3;
Object[] o5 = a4;
System.out.println(a1);
System.out.println(a4);
//想一眼看到数组里的东西 怎么看?有个Arrays这个工具类它有个asList(数组名)方法 字符串可以 int型不可以
//为什么String可以int不可以 因为asList()方法里面的参数是Object[] obj 所以String[]属于Object[] 而int[]不属于
2.判断一个对象是否是数组?
定义一个方法
Public static void panDuanShuZu(Object obj){
//先获得这个对象的字节码进行反射
Class clazz = obj.getClass();
//对这个字节码进行判断是否是数组类型
If(clazz.iaArray()){
//获得这个数组的长度进行遍历
Int len = Array.getLength(obj);
For(int i=0;i<len;i++){
System.out.println(Array.get(obj,i));
}
}else{
System.out.println(obj);
}
}
三、ArrayList_HashSet的比较及Hashcode分析
//ArrayList是个有顺序的集合 相当于数组 对象是有顺序的填进集合中,不会进行比较 而HashSet他会进行对象比较 当有HashCode值时会进行对象里的值比较
//知识点:HashCode的作用?而且只有用到HashSet集合时,HashCode方法才有意思
/*如果说 有一个HashSet集合 里有面一万个对象 现在 又要加一个对象 那HashSet集合重复的对象是添不进去的,是不是这个对象要跟集合里一万个对象进行比较啊,那会相当的麻烦,那怎么办呢?
这就要用到HashCode了 把集合里分成若干个区域 每个区域存进来的对象会有个值 根据这个值 就会把你放到相应值的那个区域里面去*/
//那如果没有HashCode方法呢?没有的话 就比不了HashCode值 所以就可能一个集合里放有值相等的对象
//什么叫内存泄露?
/*Collection con = new HashSet();*/
bianLiangFanShe bian1 = new bianLiangFanShe(3,3);
bianLiangFanShe bian2 = new bianLiangFanShe(5,5);
bianLiangFanShe bian3 = new bianLiangFanShe(3,3);
con.add(bian1);
con.add(bian2);
con.add(bian3);
con.add(bian1);
//bian1.y = 7;//更改y的值 但是 已经把HashCode值也更改了
/*con.remove(bian1);*//*因为存bian1对象的时候就有个相对应HashCode值,当把bian1对象的变量值改了以后他的HashCode值也更改了,那在想删除这个对象时,由于HashCode值更改,删除时找不到对象,所以就没有删除成功,就产生了内存泄露!
那什么叫内存泄露?Java里有内存泄露吗? 当然有,就是有一个对象没有用了,但也没有被释放掉,占有者内存空间,这就叫内存泄露!举例说明 就上面那个例子!*/
四、了解JavaBean
bianLiangFanShe bb = new bianLiangFanShe(3,5);
String xZhi = "x";
//属性描述符 PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性
//他的构造方法(你要描述的变量,那个变量属于哪个类)
PropertyDescriptor pd = new PropertyDescriptor(xZhi,bb.getClass());
//利用这个被描述的变量可以调用他的只读或只写方法
Method methodGetX = pd.getReadMethod();
//调用这个方法,因为这个方法没有参数,所以invoke方法就一个参数
Object obj = methodGetX.invoke(bb);
System.out.println(obj);
//获得描述那个变量的只写方法
Method methodSetX = pd.getWriteMethod();
methodSetX.invoke(bb, 7);
System.out.println(bb.getX());
//还有两外一种方法 用Introspector.getBeanInfo()方法获得BeanInfo对象
BeanInfo bi = java.beans.Introspector.getBeanInfo(bb.getClass());
//利用BenInfo这个对象通过getPropertyDescriptors()方法获得这个对象所有的属性
PropertyDescriptor[] pd1 = bi.getPropertyDescriptors();
for(PropertyDescriptor pp: pd1){
if(pp.getName().equals("x")){
Method method = pp.getWriteMethod();
method.invoke(bb, 9);
System.out.println(bb.getX());
break;
}
}
五、注解
1.什么叫注解?
注解相当于一种标记,在程序中加了注解就等于为程序打上了了某种标记。没加,就等于没有某种标记,以后javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,就去干相应的事。标记可以加在包、类、字段、方法,方法的参数以及局部变量上。*/
@Override这个注解表示对方法的重写(覆盖)
//在某个方法上加@Deprecated表示这个方法过时了
/@Retention表示这个注解在哪个阶段分三种
/*1.@Retention(RetentionPolicy.SOURCE)源文件阶段
2.@Retention(RetentionPolicy.ClASS)表示class文件阶段
3.@Retention(RetentionPolicy.RUNTIME)内存中的字节码阶段*/
2.注解的属性和反射调用
@ceshi(value="aaa",shuZu={1,2,3},yuan=@yuanZhuJie("bbbb"))
if(zhuJieTest.class.isAnnotationPresent(ceshi.class)){
//如果有的话就得到这个注解,用这个方法getAnnotation()
ceshi ce = (ceshi)zhuJieTest.class.getAnnotation(ceshi.class);
System.out.println(ce.color());
System.out.println(ce.value());
System.out.println(ce.shuZu().length);
System.out.println(ce.deng().nextDeng().name());
System.out.println(ce.yuan().value());
}
六、泛型集合
//?号通配符可以引用任何参数化类型
//泛型中的?通配符的扩展
//限定通配符的上边界:
ArrayList<? extends Number> x1 = new ArrayList<Integer>();//正确 因为?号通配符表示任意类型但他继承了Number,表示只要是Number这个范围的就行!而Integer是Number的子类,所以可以
//错误: ArrayList<? extends Number> x2 = new ArrayList<String>();因为String不属于Number的子类
//限定通配符的下边界:
ArrayList<? super Integer> x3 = new ArrayList<Number>();//可以?号必须是Integer的父类
编译完的泛型集合已经去类型化了(就是可以忽略这个泛型集合里装的是什么类型,所以可以往这个泛型集合里加别的类型的对象)
//泛型案例
HashMap<String,Integer> maps = new HashMap<String, Integer>();
maps.put("a", 1);
maps.put("b", 2);
maps.put("c", 3);
//对他进行迭代用Set
Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
for(Map.Entry<String,Integer> ss: entrySet){
System.out.println(ss.getKey() + " " + ss.getValue());
}
交通管理系统
面向对象的思想:谁拥有数据,那么谁才有对这些数据操作的方法!
1. 拿一个自己最熟悉的十字路口想! 分析车行驶的路线有多少条?12条,有一个灯的控制器
2. 有哪些对象?路、灯、灯的控制器
3.对象的分析
路:一共12条路线
先考虑一个方向的线比如:南到北的直线、南到西的拐弯。那么北到南的路线正好相反
东到西这个路线:东到西的直线、东到南德拐弯。反之西到东、西到北正好相反。加上各条路线向右拐弯的路线共12条。但只考虑4条就可以。
路跟灯绑定 汽车看自己所在的这条路上的灯是什么颜色 判断是否过路口,而且还要判断前面是否有车(怎么问自己前面有没有车 问路)路(是个集合)。所以,就应该有汽车有增加和减少的方法,因为只是捕捉车减少的过程。所以说,车不用时对象 用字符串就可以了。
路这个类的代码:
//因为要考虑路上一段时间后 就要增加一辆车 所以可以用线程来控制
//newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
//ExecutorService可安排在给定的延迟后运行或定期执行的命令
ExecutorService pool = Executors.newScheduledThreadPool(1);
//用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法
pool.execute(new Runnable(){
public void run() {
for(int i=1;i<1000;i++){
try {
//用一个随机数来控制 因为不知道什么时候路上增加一辆车
Thread.sleep((new Random().nextInt(10)+1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//内部类访问外部类的变量 用法
list.add(Road.this.name + "_" +i);
}
}
});
//定时器 第一个参数new Runnable()是个接口加上大括号表示实现这个接口的类要重写run这个方法,第二个参数是表示什么时间开始,第三个参数表示多久再次执行,第四个参数表示用什么时间单位
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run() {
if(list.size()>0){
boolean lighted =
lamp.valueOf(Road.this.name).isLighted();
if(lighted){
System.out.println(list.remove(0)+"过马路了");
}
}
}
},
1,
1,
TimeUnit.SECONDS);
}
灯:12个灯(包括路线右拐的灯)可以用枚举。考虑灯亮的顺序 他对应的灯和他下一个灯。
所以只设计4个灯比如说:南到北的路线,南到北直线的灯然后是南到西的灯在执行时把他对象的路线一起执行,然后再考虑东到西的路线用上,所以只考虑4个灯。
灯里有三个属性:自己的状态、对应的灯、下一个灯
灯的代码:
S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),
N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),
S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);
private lamp(String opposite,String next,boolean lighted){
this.opposite = opposite;
this.next = next;
this.lighted = lighted;
}
private lamp(){
}
//灯的状态
private boolean lighted;
//对象的灯
private String opposite;
//下一个灯
private String next;
//获得灯的状态方法
public boolean isLighted(){
return lighted;
}
//把灯变成绿的方法
public void light(){
this.lighted = true;
if(opposite != null ){
//枚举有一个静态方法可以把 你给他的字符串返回一个枚举的对象
lamp.valueOf(opposite).light();
}
System.out.println(name()+"lamp is green 下面总共应该有6个方向能看到汽车");
}
//把灯变红的方法
public lamp blackOut(){
this.lighted = false;
if(opposite != null)
lamp.valueOf(opposite).blackOut();
lamp nextLamp= null;
if(next != null){
nextLamp = lamp.valueOf(next);
System.out.println("绿灯从"+name()+"--------切换为"+next);
lamp.valueOf(next).light();
}
return nextLamp;
}
灯控制器:进行灯的红绿切换。控制4租灯的切换
有个变量:当前绿的灯是哪一个 有个定时器,时间一到把当前等变红 当这个变红的同时返回下一个绿灯
灯控制器代码:
private lamp dangQianDeng;
public lampKongZhi(){
dangQianDeng = lamp.S2N;
dangQianDeng.light();
//定时器
ScheduledExecutorService time = Executors.newScheduledThreadPool(1);
time.scheduleWithFixedDelay(
new Runnable(){
//这个方法很有意思!把灯变成红的同时返回下一个绿灯
public void run() {
dangQianDeng = dangQianDeng.blackOut();
}
},
10,
10,
TimeUnit.SECONDS);
}
项目总结
1. 用到的知识点
线程的运用
/*ExecutorService这个接口可安排在给定的延迟后运行或定期执行的命令
Executors这个类调用newScheduledThreadPool这个方法创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行*/
ExecutorService pool = Executors.newScheduledThreadPool(1);
//定时器 第一个参数new Runnable()是个接口加上大括号表示实现这个接口的类要重写run这个方法,第二个参数是表示什么时间开始,第三个参数表示多久再次执行,第四个参数表示用什么时间单位
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
枚举的用法
S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),
N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),
S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);
//枚举有一个静态方法valueOf()可以把你给他的相应字符串返回一个枚举的对象
lamp.valueOf(opposite).light();
//内部类访问外部类的变量 用法 外部类名.this.变量名
list.add(Road.this.name + "_" +i);
项目不了解的知识点
最不了解的是线程、枚举的应用(以前用枚举很简单)
不过听张老师讲解以后都能理解
银行业务调度系统
做项目之前 首先要身临其境回忆去银行办理手续的过程!
1.办理手续之前 是通过取号机获得号码生成客户 真正的客户是通过取号机来决定的
要有一个号码管理对象 这个对象不段产生新号码 就等于产生了客户
2.由于有三类客户,每类客户的号码编排都是完全独立的,所以,想到本系统一共要产生三个号码管理器。
各自管理一类用户的排队号码,这三个号码管理器对象统一由一个号码机器进行管理,这个号码机器在整个系统中始终只能有一个,所以,它要
设计成为单例。
3.各个类型客户在其对应窗口办理业务,也就说应该是窗口依次叫号。客户听到好了后去对应得窗口办理业务
那问题是?各个窗口怎么知道要叫哪一个号呢?
所以他就是问号码管理器,服务窗口每次要找号码管理器要号
/*注意:
由于客户跟窗口分为两个线程调用这个对象的两个方法,那两个线程访问相同的数据就会出问题,
这时候我们要考虑同步,所以那要实现互斥就要用
synchronized*/
/*这两个方法为什么用Integer作为返回对象 如果用int的话 如果没有人取号
也就说集合为空时会报空指针异常,所以用一个Integer对象来作为
返回对象集合为空时返回null*/
1.号码类
//客户获得号码的方法
public synchronized Integer getNewNumber(){
haoMaJiHe.add(lastNumber);
return lastNumber++;
}
//窗口获得号码的方法
public synchronized Integer fetchNumber(){
Integer number = null;
if(haoMaJiHe.size()>0){
haoMaJiHe.remove(0);
}
return number;
}
2.号码机器管理类
private NumberManager VIP = new NumberManager();
private NumberManager puTong = new NumberManager();
private NumberManager kuaiSu = new NumberManager();
public NumberManager getvipNum(){
return VIP;
}
public NumberManager getpuTongNum(){
return puTong;
}
public NumberManager getkuaiSuNum(){
return kuaiSu;
}
//由于这个机器只能有一个所以要用单例
/*单例的创建的步骤
首先:把构造方法私有化 别人就没法创建它 那么别人要搞到这个对象只能
调用他的静态方法*/
private numberGuanLi(){}
public static numberGuanLi getInstance(){
return num;
}
private static numberGuanLi num = new numberGuanLi();
2. 服务窗口类
private enumType type = enumType.PUTONG;
private int windowsId = 1;
public void setType(enumType type) {
this.type = type;
}
public void setWindowsId(int windowsId) {
this.windowsId = windowsId;
}
/*窗口叫号的方法
它一直不听的叫号 所以他是一个线程*/
public void start(){
//创建线程
Executors.newSingleThreadExecutor().execute(new Runnable(){
//因为new Runnable这个对象实现了一个接口所以要重写这个接口
public void run(){
//因为要不停地取号所以用while循环
while(true){
//枚举的判断也可以用switch
switch(type){
//注意这里 枚举的话省略类名直接写元素
case PUTONG:
puTongviece();
break;
case KUAISU:
kuaiSuviece();
break;
case VIP:
VIPviece();
break;
}
}
}
});
}
private void puTongviece() {
String windowName = "第"+windowsId+"号"+type+"窗口";
System.out.println(windowName+"正在获取任务");
//普通用户取得号码
Integer puTongHaoMa = numberGuanLi.getInstance().getpuTongNum().fetchNumber();
if(puTongHaoMa!=null){
//获得系统当前开始的毫秒
long beginTime = System.currentTimeMillis();
int maxRand = changLiang.MAXSERVTIME-changLiang.MINSERVTIME;
long servTime = new Random().nextInt(maxRand)+1+changLiang.MINSERVTIME;
try {
//服务的时间
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权
//参数的意思 以毫秒为单位的休眠时间
Thread.sleep(servTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//再次获得当前系统时间减去前面那个时间获得耗时时间
long costTime = System.currentTimeMillis()-beginTime;
System.out.println(windowName+"为第"+puTongHaoMa+"个"+type+"客户完成服务,耗时"+costTime/1000+"秒");
}else{
System.out.println(windowName+"没有取到服务任务,先休息1秒钟");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void kuaiSuviece() {
String windowName = "第"+windowsId+"号"+type+"窗口";
System.out.println(windowName+"正在获取任务");
Integer kuaiSuHaoMa = numberGuanLi.getInstance().getkuaiSuNum().fetchNumber();
if(kuaiSuHaoMa!=null){
long beginTime = System.currentTimeMillis();
//int maxServeTime = changLiang.MAXSERVTIME-changLiang.MINSERVTIME;
//long servTime = new Random().nextInt(maxServeTime)+1+changLiang.MINSERVTIME;
try {
Thread.sleep(changLiang.MINSERVTIME);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long costTime = System.currentTimeMillis()-beginTime;
System.out.println(windowName+"为第"+kuaiSuHaoMa+"个"+type+"客户完成服务,耗时"+costTime/1000+"秒");
}else{
System.out.println(windowName+"没有取到服务任务");
puTongviece();
}
}
private void VIPviece() {
String windowName = "第"+windowsId+"号"+type+"窗口";
System.out.println(windowName+"正在获取任务");
Integer VIPHaoMa = numberGuanLi.getInstance().getvipNum().fetchNumber();
if(VIPHaoMa!=null){
long beginTime = System.currentTimeMillis();
int maxRand = changLiang.MAXSERVTIME-changLiang.MINSERVTIME;
long servTime = new Random().nextInt(maxRand)+1+changLiang.MINSERVTIME;
try {
Thread.sleep(servTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long costTime = System.currentTimeMillis()-beginTime;
System.out.println(windowName+"为第"+VIPHaoMa+"个"+type+"客户完成服务,耗时"+costTime);
}else{
System.out.println(windowName+"没有取到服务任务");
kuaiSuviece();
}
}
以前不了解的知识点
1. 线程的运用
2. 也可以用枚举进行switch判断
自己思考:在服务窗口这个类中由于要区分客户的类型所以用了switch来进行判断,写了3个取号的方法,这三个方法有很多代码的重复,每个方法都有自己要做的事情,所以我想把这些公共的部分抽出来,做成3个服务窗口的子类,继承父类的一个公共部分的方法,重写每个这个方法,把不同的地方以参数的形式传进来。