一道面试题引发的思考
以下是某场的一道面试题(大概):
1、一个停车场,车辆入场时,摄像头记录下车辆信息
2、屏幕上显示所接收的车辆的信息情况(车牌号)以及各层车位的车位余量
3、停车场一共四层车位,其中的三层都为普通车位,还有一层为特殊车位(体现在停车计费价格上面的不同)
看到第一个条件的时候,我大概知道了这道题考察的应该是面向对象相关的,三个条件看完后以及基本确认了。
说到这里,简单地说一下面向对象,至于什么是面向对象我这里就不多说了,每个人也都有自己不同的理解,包括它的三要素(封装、继承、多态)。
简单地说一下为什么我们要面向对象,为什么要使用面向对象?说一下我的理解
先抛开面向对象,首先计算机的 程序执行 无非就是 顺序、判断、循环 我们现在所有用到的语言包括c、java、php... 没有其它的, 如js中的 if...else, switch...case...就是判断,for,while就是循环,也包括一些遍历、递归也都是基于这三种方式!我们每天所产出的代码,所有的这些都是通过顺序、判断和循环这三个方式都能轻松搞定,没有第四种方式,为什么呢?因为我们通过这三种方式来解决就实现了一个结构化的问题,也就是我们通过这三个就能满足所有的需求,也就不需要第四个了这样的化对我们的程序就已经结构化了,对于编译器也能很高效的执行、解析这些东西, 外插一嘴,goto语句其实就是游离在这三个之外,会导致程序、逻辑的混乱,很少有用基本被淘汰了,不能说他因为效率低,它的效率可能会很高,但是因为脱离了结构化,所以至少在业务代码上我们极少使用它。
说到这里,也就是程勋执行我们都简化成 这个结构,而面向对象也让我们的数据也简化了即结构化(这句话也不是我说的,而是ruby语言的作者),书里说了,面向对象是为了模拟、表示事件得万物,比如人、鸟类、到具体服务员类,有具体行为的类。其实js里面向对象意义根本不拘泥于此,书上的只是为了让我们好入门好学习而已,它的意义是将零散的数据进行结构化。计算机有个毛病,结构化的程序对于它来说是最简单的!
简单解释一下,我们浏览器加载网页加载的是什么?是流,流是什么呢?其实就是字符串,虽然我们看到之后是html,css,js代码,但是代码不就是字符串嘛,那我们字符串怎么办,浏览器拿到字符串首先就是dom节点的解析然后生成生成渲染树(渲染树与dom树不同的地方在于,dom树会把所有的dom节点都展示出来,渲染树只会展示display非none的元素),都是一步步顺序执行的。
相比来说,程序也是如此了,如果我们的程序中充满了散乱的数据,那我们还怎么按照结构化的要求去操作呢?比如说,通过人做一个对象,人有外貌,行为,状态(吃、传、喝、高矮胖瘦)这么多东西可以集成在一个对象中来操作,但是如果没有面向对象,这些特征,行为也就散乱了,一旦散乱了以后还怎么管理,并且这只是一个人,就有这么多东西,再来十个,一百个,再来只狗类呢?我们的程序就成了一盘散沙了,当然这些只是举例子,具体业务还要拿来具体分析。所以面向对象不仅符合计算机所喜爱的结构化,还能让我们管理起来,维护起来,都符合结构化。
再引用双越老师的一句话:编程就应该简单&抽象。
有一篇小说里面有一段话我觉得还蛮有意思的,早期的人们之所以没有设计出来计算机,就是因为他们想的不够简单,而不是不够复杂,其实计算机很简单,不就是0和1吗。
所有说我们编程,设计时候要做到 结构化,简单+抽象,抽象完之后才能简单,简单的前提是我们应该抽象好,我认为这就是我们为什么要面向对象最主要的原因,我们之所以面向对象编程就是因为它可以抽象,可以扩展。而不是面向具体,可能这些话对于初学前端的来说不好理解,但是随着你工作时间的提升,相信很快就会理解的。
**回到面试题,我们来分析** 这个面试题,如果我们没有面向对象,面向抽象的概念, 那我们应该很快就能拿起键盘了,一个停车场,三个普通车位,一个豪华车位,一个摄像头,一个大屏幕显示器,各种各样的车,可能我们很快就能做完,‘设计完’,毕竟面向具体是真的很简单,要求什么我就做什么。。。 不过说回来,如果面试官再让你减一个车位或者增加两个豪华车位,加两个摄像头?如果按照我们之前的来写,简单。。。改一下不就行了,加一个豪华车位,加两个摄像头。。。其实到这里想必已经凉凉了。 下面我们来使用面向对象 类图这里就不再画了 首先面试题中所提到的我们都可以看成类,比如停车场是一个类吧,它里面的车位是一个类吧,摄像头,屏幕。。。我们都可以看做是类 停车场,有层位,有摄像头,屏幕,所以我们先来创建一个停车场,这一类 class Park { constructor () { } } 停车场有层位,层位里面有若干车位 class Floor { constructor (num, places) { this.num = num this.places = places } } 车位也归为一类,车位有自己的状态,有车,无车 class Place { constructor () { this.empty = true } } 车位分为普通车位,豪华车位,但是不论是普通还是豪华,他们也都有自己的状态,有车,无车,所以我们直接让这两个类继承车位类 普通车位 class NormalPlace extends Place{ constructor () { super() } } 豪华车位 class SpecialPlace extends Place{ constructor () { super() } } 摄像头 class Camera { constructor () { } } 电脑,可以记录所有车的数据 class Computer { constructor () { this.symbols = {} } } // 屏幕 class Screen { } 这样一看我们所有类都创建完了,差的是来分析他们的行为,状态,联系,这里不再多做分析。只举一个简单的例子,然后上完整代码 停车场, 有层 摄像图 屏幕,有出入车时所发生的行为 class Park { constructor (floors) { this.floors = floors this.camera = new Camera() this.screen = new Screen() } in (car) { } out (car) { } } 我自己写了一个简单的demo,当然也没有考虑得特别细致 ***完整代码***
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> body,html{ height: 100%; } ul,li { list-style: none; } #inform { text-align: center; color: #9f9f9f; font-size: 18px; } #inform span { color: #ff4a3d; } #earch { text-align: center; font-size: 20px; color: #2963ff; } </style> </head> <body> <ul id="inform"> <li>第一层车位数量100,空余车位数量 <span data-num=1>100</span></li> <li>第二层车位数量100,空余车位数量 <span data-num=2>100</span></li> <li>第三层车位数量100,空余车位数量 <span data-num=3>100</span></li> <li>第四层车位数量 20,空余车位数量 <span data-num=4> 20</span></li> </ul> <p style="text-align: center; color: #5eb4ff">收入 <span id="price">0</span> 元</p> <ul id="earch"> <li>统计</li> </ul> <script> let priced = document.getElementById('price') let earch = document.getElementById('earch') class Camera { constructor () { this.computer = new Computer() } record (car, place, floor) { this.computer.show(car) return { num: car.num, symbol: car.symbol, inTime: new Date(), place, floor } } } class Computer { constructor () { this.symbols = {} } show (car) { console.log(`车牌号为${car.num}的${car.symbol}驶入`) if (this.symbols[car.symbol] !== undefined) { this.symbols[car.symbol] = this.symbols[car.symbol] + 1 document.getElementById(car.symbol).innerText = this.symbols[car.symbol] } else { this.symbols[car.symbol] = 0 earch.innerHTML = earch.innerHTML + `<li>${car.symbol} <span id="${car.symbol}">${0}</span> 辆<li>` } } } class Screen { reload (f, t) { let dom = Array.prototype.slice.apply(document.querySelectorAll('#inform span')) if (t === '1') { dom.map(el => { if (el.dataset.num === f + 1 + '') { el.innerText = --el.innerText } }) } else { dom.map(el => { if (el.dataset.num === f + 1 + '') { el.innerText = ++el.innerText } }) } } } class Park { constructor (floors) { this.floors = floors this.camera = new Camera() this.screen = new Screen() this.carList = {} this.fullFloor = [] this.wait = [] } in (car) { let canChoice = [] let floors = this.floors floors.map((el, index) => { if (el.emptys > 0) { canChoice.push(index) } }) if (canChoice.length === 0) { if (Math.random() > 0.2) { console.warn('没有找到车位,撤了') return 'back' } else { console.warn(`没有找到车位,继续等待`) this.in(car) return 'wait' } } let fi = canChoice[Math.floor(Math.random() * canChoice.length)] let canChoicePlaceIndex = [] let places = floors[fi].places places.map((el, index) => { if (el.empty) { canChoicePlaceIndex.push(index) } }) let pi = Math.floor(Math.random() * canChoicePlaceIndex.length) this.carList[car.num] = this.camera.record(car, pi, fi) places[pi].in(floors[fi]) this.screen.reload(fi, '1') let outs = Math.ceil(Math.random() * 100000 + 5000) setTimeout(() => { this.out(car) }, outs) } out (car) { let place = this.floors[this.carList[car.num].floor].places[this.carList[car.num].place] place.out(this.floors[this.carList[car.num].floor]) let price = (new Date() - this.carList[car.num].inTime)/1000 * place.price this.screen.reload(this.carList[car.num].floor) console.log(`车牌号为${car.num}驶出, 收费 ${price} 元`) priced.innerText = (Number(priced.innerText) + Number(price)).toFixed(2) delete this.carList[car.num] } } class Floor { constructor (num, places) { this.num = num this.places = places this.emptys = places.length } inP () { this.emptys-- } outP () { this.emptys++ } } class Place { constructor () { this.empty = true } in (floor) { this.empty = false floor.inP() } out (floor) { this.empty = true floor.outP() } } class NormalPlace extends Place{ constructor () { super() this.color = 'blue' this.price = 10 } } class SpecialPlace extends Place{ constructor () { super() this.color = 'yellow' this.price = 60 } } class Car { constructor (type, num, symbol) { this.permit = type === '1' this.num = num this.symbol = symbol } } // 以下部分全部为配合实现,请勿浪费时间观看 let Floors = [] for (let i = 0; i < 3; i++) { let places = [] for (let j = 0; j < 100; j++) { places.push(new NormalPlace()) } Floors.push(new Floor(i + 1, places)) } let specialPlace = [] for (let i = 0; i < 20; i++) { specialPlace.push(new SpecialPlace()) } Floors.push(new Floor(4, specialPlace)) let park = new Park(Floors) var str = [] for(var i = 65; i < 91; i++){ str.push(String.fromCharCode(i)) } let test = { s: ['京', '冀', '黑', '蒙', '陕', '户', '津', '赣', '浣', '深', '藏', '疆' ], n: str, u: ['奔驰', '劳斯莱斯', '路虎', '大众', '保时泰', '保时捷', '兰博基尼', 'QQ', '丰田', '本田', '吉利', '宝马', '迈巴赫', '公交车', '火车', '坦克', '红旗', '捷豹', '法拉利', '宾利'] } let enterTime = 100 setInterval( () => { let number = test.s[Math.ceil(Math.random() * test.s.length) - 1] + test.n[Math.ceil(Math.random() * test.n.length) - 1] + Math.floor(Math.random() * 100000) let na = test.u[Math.ceil(Math.random() * test.u.length) - 1] let car = new Car('1', number, na) park.in(car) enterTime = Math.floor(Math.random() * 50) }, enterTime) </script> </body> </html>
代码可以直接粘贴到html里,直接运行打开,大家只要看各个类之间的联系就可以,具体到行为的逻辑代码只是怎么简单想怎么写就怎么写的,当然也有没考虑全面的,比如各个车位的价格应该也是动态的,为了赶时间,好多东西我都直接定死了。
抽象好了之后,这样不论是增加减少变更价钱我们都可以灵活地处理,每个类最基础的行为和方法几乎是不变得,我们只需要变容易变得抽象起来,封装起来。之后, 就变得‘简单’
欢迎一起探讨,大家共同进步!
作者:易企秀——Yxaw