1. 何为设计?
设计即按照哪一种思路或者标准来实现功能
功能相同,可以有不同设计方案来实现
伴随着需求增加,设计到作用才能体现出来
《UNIX / LINUX 设计哲学》
准则1:小即是美
准则2:让每个程序只做好一件事
准则3:快速建立原型
准则4:舍弃高效率而取可移植性
准则5:采用纯文本来存储数据
准则6:充分利用软件的杠杆效应(软件复用)
准则7:使用shell脚本来提高杠杆效应和可移植性
准则8:避免强制性的用户界面
准则9:让每个程序都称为过滤器
《UNIX / LINUX 设计哲学》 - 小准则
允许用户定制环境
尽量使操作系统内核小而轻量化
使用小写字母并尽量简短
沉默是金
各部分只和大于整体
寻求90%的解决方案
2. 五大设计原则(SOLID)?
- S - single 单一职责原则
- O - open 开放封闭原则(对扩展开放,对修改封闭)
- L - Liskov 里氏置换原则
- I - interface 接口独立原则
- D - dependency 依赖倒置原则
(1)S - 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
(2) O - 开放封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
- 这是软件设计的终极目标
(3) L - 里氏置换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
- JS中使用较少(弱类型 & 继承使用较少)
(4) I - 接口独立原则
- 保持接口的单一独立,避免出现“胖接口”
- JS中没有接口(typescript例外),使用较少
- 类似于单一职责原则,这里更关注接口
(5)D - 依赖倒置原则
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
- JS中使用较少(没有接口 & 弱类型)
用Promise来说明SO
- 单一职责原则:每个then中的逻辑只做好一件事
- 开放封闭原则:如果新增需求,扩展then
- 对扩展开放、对修改封闭
3. 23种设计模式
(1)创建型
- 工厂模式(工厂方法模式,抽象工厂模式,建造者模式)
- 单例模式
- 原型模式
(2)结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
(3)行为型
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代器模式
- 职责连模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
4. 设计原则面试题
(1)打车时,可以打专车或者快车,任何车都有车牌号和名称。不同车价格不同,快车每公里1元,专车每公里2元。行程开始时,显示车辆信息。行程结束时,显示打车金额(假定行程就5公里)。画出UML类图,用ES6语法写出该示例
class Car{
constructor(num, name){
this.num = num
this.name = name
}
}
class Trip{
constructor(car){
this.car = Car
}
start(){
console.log(`车辆信息:车牌号为${
this.car.num},名称为${
this.car.name}`)
}
end(){
console.log(`价格:${
this.car.amount * 5}`)
}
}
class Kuai extends Car{
constructor(num, name){
super(num, name)
this.amount = 1
}
}
class Zhuan extends Car{
constructor(num, name){
super(num, name)
this.amount = 2
}
}
var kuai = new Kuai(123, '快车')
let trip = new Trip(kuai)
trip.start()
trip.end()
(2)某停车场,分3层,每层100车位。每个车位都能监控到车辆的驶入和离开,车辆进入前,显示每层的空余车位数量。车辆进入时,摄像头可识别车牌号和时间,车辆出来时,出口显示器显示车牌号和停车时长。画出UML类图。
// 车辆
class Car{
constructor(num){
this.num = num
}
}
// 停车场
class Park{
constructor(floors){
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {
} // 存储摄像头拍摄返回的车辆信息
}
in(car){
// 通过摄像头获取信息
const info = this.camera.shot(car)
// 停到某个停车位
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
// 记录信息
this.carList[car.num] = info
}
out(car){
// 获取信息
const info = this.carList[car.num]
// 将车位清空
const place = info.place
place.out()
// 显示时间
this.screen.show(car, info.inTime)
// 清空记录
delete this.carList[car.num]
}
emptyNum(){
return this.floors.map(floor => {
return `${
floor.index} 层还有${
floor.emptyPlaceNum()}个空闲车位}`
}).join('\n')
}
}
// 层
class Floor{
constructor(index, places){
this.index = index
this.places = places || []
}
emptyPlaceNum(){
let num = 0
this.places.forEach(p => {
if(p.empty){
num = num + 1
}
})
return num
}
}
// 停车位
class Place{
constructor(empty){
this.empty = true
}
in(){
this.empty = false
}
out(){
this.empty = true
}
}
// 屏幕
class Screen{
show(car, inTime){
console.log('车牌号', car.num)
console.log('停车时间', Date.now() - inTime)
}
}
// 摄像机
class Camera{
shot(car){
return {
num: car.num,
inTime: Date.now()
}
}
}
// 初始化停车场
const floors = []
for(let i = 0; i < 3; i++){
const places = []
for(let j = 0; j < 100; j++){
places[j] = new Place()
}
floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)
// 初始化车辆
const car1 = new Car(100)
const car2 = new Car(200)
const car3 = new Car(300)
console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)
5. 工厂模式
- 将new操作单独封装
- 遇到new时,就要考虑是否该使用工厂模式
(1)使用场景:
- jQuery - $(‘div’)
- React.createElement
- vue异步组件
(2)设计原则验证
- 构造函数和创建者分离
- 符合开放封闭原则
6. 单例模式
- 系统中被唯一使用
- 一个类只有一个实例
(1)使用场景:
- 登录框
- 购物车
(2)说明:
- 单例模式需要用到java的特性(private)
- ES6中没有(typescript除外)
(3)JS使用单例模式
class SingleObject{
login(){
console.log('login...')
}
}
SingleObject.getInstance = (function(){
let instance
return function(){
if(!instance){
instance = new SingleObject();
}
return instance
}
})()
// 测试
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log('obj1 === obj2', obj1 === obj2) // true
let obj3 = new SingleObject()
obj3.login()
console.log('obj1 === obj3', obj1 === obj3) // false
(4)场景
- jQuery只有一个$
- 模拟登录框
- vuex和redux中的store
(5)设计原则验证
- 符合单一职责原则,只实例化唯一的对象
- 没法具体体现开放封闭原则,但是绝对不违反开放封闭原则
7. 适配器模式
- 旧接口格式和使用者不兼容
- 中间加一个适配转换接口
class Adaptee(){
specificRequest(){
return '德国标准插头'
}
}
class Target{
constructor(){
this.adaptee = new Adaptee()
}
request(){
let info = this.adaptee.specificRequest()
return `${
info} - 转换器 - 中国标准插头`
}
}
// 测试
let target = new Target()
let res = target.request()
console.log(res)
(1)场景
- 封装旧接口
// 自己封装的ajax,使用方式如下:
ajax({
url: 'getData',
type: 'Post',
dataType: 'json',
data: {
id: '123'
}
})
.done(function(){
})
// 但因为历史原因,代码中全都是:$.ajax({...})
// 做一层适配器
var $ = {
ajax: function(options){
return ajax(options)
}
}
- vue computed
<div id='example'>
<p>Original message: {
{message}}</p>
<p>Computed reversed message: {
{reversedMessage}}</p>
</div>
var vm = new Vue({
el: 'example',
data: {
message: 'Hello',
},
computed: {
// 计算属性的 getter
reversedMessage: function(){
// 'this' 指向vm实例
return this.message.split('').reverse().join('')
}
}
})
(2)设计原则验证
- 将旧接口和使用者进行分离
- 符合开放封闭原则
8. 装饰器模式
- 为对象添加新功能
- 不改变其原有的结构和功能
class Circle{
draw(){
console.log('画一个圆形')
}
}
class Decorator{
constructor(circle){
this.circle = circle
this.setRedBorder(circle)
}
setRedBorder(circle){
console.log('设置红色边框')
}
}
// 测试
let circle = new Circle()
circle.draw()
let dec = new Decorator(circle)
dec.draw<