电梯是我们日常生活中经常看见和使用的运载工具,但其中也隐藏着一个精而小的程序,我们今天模拟一个电梯运行程序来开始我们面向对象之旅
电梯问题-抽取关键需求
1.电梯首先肯定是要可以上下不间断运行
2.可以达到顶层或底层之后向反方向继续运行
3.人员可以按钮然后等待电梯停在本楼层并进入
4.进入的人可以选择自己的目的层数
5.电梯可以在目的层数停止等待一定时间后继续运行
问题需求关键环节流程图
我们进入需求转化环境,我们来梳理一下需求并且转化为一个时序图,便于我们拆分对象以及确定逻辑顺序,流程图如下
初步领域对象分析,最小可用原则
我们创建对象以及方法要使用最小可用原则,既不需要的就不要加进对象,直到确定需要这个对象或 者属性或者方法再添加,所以可以如下分析
依据时序图,我们初步分为了对象人person与对象电梯elevator
对象电梯可以被启动,然后不断运行,所以我们觉得他应该有一个start方法
我们也许会觉得对象人是外部触发电梯start 方法的对象
电梯应该方法是判断是否到顶部isTop(),是否到底部isBottom(),并且有一个方法是wait() 等待人进入并且选择目标层数selectFloor(int floor)
那么我们的对象建模就结束了,领域建模图如下
对象建模思考
思考1:电梯运行是否内部维护运行状态,是否需要一个start方法
我个人认为都可以,这里我是按照不需要设计的,我这里把电梯运行和电梯本身认为是两个实体,你 可以用轿厢品牌1 带动电梯,第二天也可以用轿厢2带动电梯,
也就是说电梯是被外部轿厢发动机带动运行的,而电梯本身只负责内部运行逻辑处理
换句话说我的运行创建线程并不是电梯内部维护的,而是适用方,可以用线程池,也可以直接new thread
这样就把更大的自由度留给了电梯合作方:电机轿厢,也符合易扩展多方合作的共赢思维
思考2:是否需要人这个person对象
本程序我们真的需要人这个对象吗,
实际上人在这里的作用无非就是提供电梯需要停止的层数
而实际上这个程序的外部使用者是真正活着的实体人
因此,我们在程序里定义人这个对象是多余的,所以我们可以将对象人person这个类去掉,换成层数floor
重新分析后-最终领域对象图
面向对象程序设计核心思想原则
1.就是让人从外面看起来就知道你的代码是在干什么,每一个对象都要有意义,并且真实符合世界上实体事物运行规则
2.设计的对象没有绝对的对与错,只要让人觉得你的对象确实合理并且使用起来符合正常人的思维习惯就可以
经过以上分析建模,形成目前的初版代码如下
public class ElevatorTest { public static void main(String[] args){ //18层的电梯 int floor = 18; //电梯初始化 Elevator elevator = new Elevator(floor); //创建一个线程池,初始化两个线程,1:电梯运行线程,2:模拟随机层数人员进入电梯线程 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,2,10L, TimeUnit.SECONDS,new ArrayBlockingQueue(2)); //启动电梯 threadPoolExecutor.execute(elevator); //模拟随机楼层人员进电梯与出电梯 threadPoolExecutor.execute(()->{ while (true){ //模拟随机生成 1~18楼人 int enterRandom = new Random().nextInt(18); if(enterRandom ==0){ continue; }else { try { //人员进入 elevator.enter(enterRandom); Thread.sleep(3000); }catch (InterruptedException e){ } } } }); } } class Elevator implements Runnable { boolean start = false; int currentFloor ; boolean directionTop = true; int floor ; //保存目标层数map Map enterMap = new HashMap(18); ReentrantLock lock = new ReentrantLock(); public Elevator(int floor){ this.floor = floor; this.currentFloor = 1; System.out.println("初始化了一个"+floor+"层的电梯,当前电梯在第1层"); } /** * 人员进入电梯同时选择好目标层数 * */ public void enter(int outFloor){ lock.lock(); System.out.println("人员进入,按了"+outFloor+"层"); enterMap.put(outFloor,null); lock.unlock(); } /** * 是否是顶层 * */ public boolean isTop(){ return this.currentFloor == floor; } /** * 是否是底层 * */ public boolean isBottom(){ return this.currentFloor == 1; } @Override public void run() { System.out.println("电梯启动"); start = true; while (start){ if(!lock.isLocked()) { try { if (directionTop) { currentFloor++; System.out.println("当前在电梯第" + currentFloor + "层"); //该层人员出门出门后将该层移除 if(enterMap.containsKey(currentFloor)){ try { System.out.println(currentFloor+"人员出门"); enterMap.remove(currentFloor); System.out.println("剩余楼层"+enterMap.keySet()); Thread.sleep(5000); }catch (Exception e){ } } if (isTop()) { directionTop = false; System.out.println("到达顶层,开始向下运行"); } } else { currentFloor--; System.out.println("当前在电梯第" + currentFloor + "层"); //该层人员出门出门后将该层移除 if(enterMap.containsKey(currentFloor)){ try { System.out.println(currentFloor+"人员出门"); enterMap.remove(currentFloor); System.out.println("剩余楼层"+enterMap.keySet()); Thread.sleep(5000); }catch (Exception e){ } } System.out.println("当前在电梯第" + currentFloor + "层"); if (isBottom()) { directionTop = true; System.out.println("到达1层,开始向向运行"); } } Thread.sleep(1000); } catch (InterruptedException e) { } }else { try { System.out.println("进人中。。"); Thread.sleep(5000); }catch (Exception e){ } } } } }
运行输出结果
初始化了一个18层的电梯,当前电梯在第1层
电梯启动
当前在电梯第2层
人员进入,按了17
当前在电梯第3层
当前在电梯第4层
当前在电梯第5层
人员进入,按了13
当前在电梯第6层
当前在电梯第7层
当前在电梯第8层
人员进入,按了7
当前在电梯第9层
当前在电梯第10层
当前在电梯第11层
人员进入,按了6
当前在电梯第12层
当前在电梯第13层
13人员出门
剩余楼层[6, 7, 17]
人员进入,按了11
人员进入,按了4
当前在电梯第14层
人员进入,按了13
当前在电梯第15层
当前在电梯第16层
当前在电梯第17层
17人员出门
剩余楼层[4, 6, 7, 11, 13]
人员进入,按了7
人员进入,按了1
当前在电梯第18层
到达顶层,开始向下运行
人员进入,按了16
当前在电梯第17层
当前在电梯第17层
当前在电梯第16层
16人员出门
剩余楼层[1, 4, 6, 7, 11, 13]
人员进入,按了4
人员进入,按了3
当前在电梯第16层
当前在电梯第15层
当前在电梯第15层
当前在电梯第14层
当前在电梯第14层
人员进入,按了14
当前在电梯第13层
13人员出门
剩余楼层[1, 3, 4, 6, 7, 11, 14]
人员进入,按了10
当前在电梯第13层
人员进入,按了10
当前在电梯第12层
当前在电梯第12层
当前在电梯第11层
11人员出门
剩余楼层[1, 3, 4, 6, 7, 10, 14]
面向对象到底是节省时间还是浪费时间
其实使用面向对象角度建模并且进行程序开发并不是耗费时间,而是节省时间
因为设计的对象是充血模型,所以后续其他业务使用对应功能只需要对象.方法就可以了,这也是DDD 领域驱动设计的核心思想
而且面向对象设计开发也是利于大家对需求转化的加深,会加速开发效率,比如这个程序我建模可能用了30分钟,但编码只用了15分钟左右
结语
程序是依照我们小设计原则没有任何优化重构编写的,肯定细节上还有容错以及重用上有很多问题
但这就是程序设计,因为他满足了最小化的我们的需求
但是,问题终究是问题,我们还是要解决的,先把当前代码重构,然后我们要把缺失逻辑补充:
比如并没有实现人按电梯上下进入的操作,进入的人并没有实现同时允许多层数选择的操作等
没有使用层数floor对象,而是用map简单表示
下一篇我们将继续优化并完成这个电梯程序