读书笔记----软件设计原则、设计模式
序
作业简介
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/ |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/homework/11833 |
这个作业的目标 | 深入思考理解设计原则,及其优缺点,思考在项目中如何使用设计模式 |
(博客园的代码渲染出来是歪的,我在我的博客上又发了一次,内容一样,不过这个样式会好看点点这里)
书籍和参考资料
- 大话设计模式:https://book.douban.com/subject/2334288/
- JavaSrcipt设计模式:https://book.douban.com/subject/3329540/
设计原则
单一职责原则 SRP
单一职责原则表示一个模块的组成元素之间的功能相关性。从软件变化的角度来看,就一个类而言,应该仅有一个让它变化的原因;通俗地说,即一个类只负责一项职责。
开放-关闭原则 OCP
开放-关闭原则表示软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改。(Open for extension, close for modification)
里氏替换原则 LSP
在编程中常常会遇到这样的问题:有一功能 P1, 由类 A 完成,现需要将功能 P1 进行扩展,扩展后的功能为 P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
依赖倒转原则 DIP
高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象。
接口隔离原则 ISP
接口隔离原则,其 "隔离" 并不是准备的翻译,真正的意图是 “分离” 接口(的功能)
接口隔离原则强调:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
迪米特法则 LOD
迪米特法则又称为 最少知道原则,它表示一个对象应该对其它对象保持最少的了解。通俗来说就是,只与直接的朋友通信。
组合/聚合复用原则 CRP
组合/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分; 新的对象通过向这些对象的委派达到复用已有功能的目的。
设计模式
刚好最近看了一下Vue,所以就用Vue源码为例聊聊设计模式的具体实践吧
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
我们都知道,Vue在数据响应式中使用了观察者模式来降低耦合关系
这里的Watcher代表渲染Watcher
,渲染时如果调用了数据,数据可以在get
函数中收集到对应的Watcher实例,并把它存放到deps
里,然后在set
函数中,数据更新会取出deps
里的Watcher
来调用update
方法,渲染Watcher
的update
方法就是重新渲染组件,这就是Vue的数据响应式的大致原理
康康代码
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// getter收集依赖
get: function reactiveGetter() {
// 属性对应的值
const value = getter ? getter.call(obj) : val;
// Dep.target是全局的一个watcher
if (Dep.target) {
// 这里把Watcher保存起来
dep.depend();
}
return value
},
set: function reactiveSetter(newVal) {
// setter派发更新
const value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
// 判断新旧值是否相等
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新的值也是对象,也会进行观测
childOb = !shallow && observe(newVal);
// 这里触发deps里保存的依赖
dep.notify()
}
})
}
// 大部分代码都省了
export default class Watcher {
update() {
// 普通的派发更新
queueWatcher(this)
}
}
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
* 保存watcher
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor() {
this.id = uid++;
this.subs = []
}
addSub(sub: Watcher) {
this.subs.push(sub)
}
removeSub(sub: Watcher) {
remove(this.subs, sub)
}
depend() {
if (Dep.target) {
// watcher.addDep
Dep.target.addDep(this)
}
}
notify() {
// stabilize the subscriber list first
// 通知所有的watcher
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
// 回调update方法
subs[i].update()
}
}
}
Dep.target = null;
const targetStack = [];
发布-订阅模式
Vue组件的事件流和平时开发时使用的EventBus(事件总线,用于全局传递数据)都用了这个设计模式
export function eventsMixin(Vue: Class<Component>) {
const hookRE = /^hook:/
// 添加事件
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on() {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
// 解绑事件
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
if (fn) {
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
}
// 触发事件
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// 事件参数
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
}
参与者模式
JavaScript
中的参与者模式,就是在特定的作用域中执行给定的函数,并将参数原封不动的传递,参与者模式不属于一般定义的23
种设计模式的范畴,而通常将其看作广义上的技巧型设计模式。参与者模式实现的在特定的作用域中执行给定的函数,并将参数原封不动的传递,实质上包括函数绑定和函数柯里化。
在源码中是这么用的
// 判断是不是HTML标签
export const isHTMLTag = makeMap(
'html,body,base,head,link,meta,style,title,' +
'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
'embed,object,param,source,canvas,script,noscript,del,ins,' +
'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
'output,progress,select,textarea,' +
'details,dialog,menu,menuitem,summary,' +
'content,element,shadow,template,blockquote,iframe,tfoot'
)
// 判断是不是SVG标签
export const isSVG = makeMap(
'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +
'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +
'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
true
)
// 生成函数
export function makeMap(
str: string,
expectsLowerCase?: boolean
): (key: string) => true | void {
const map = Object.create(null)
const list: Array<string> = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
节流模式
节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数
其实我在选例子时有在纠结queueWatcher
到底是节流还是防抖,最后我觉得防抖要有一个取消之前的事件执行,等待最后一次事件被触发的过程,但是queueWatcher
这里没有,所以应该是节流了,虽然它的规定时间是不定的(要根据具体执行情况嘛,也可以理解)
queueWatcher
可以让避免在一个tick内多处刷新组件
比如下面的情况
<button onclick="handleClick">click me {{a}} {{b}} {{c}}</button>
<script>
let vm = {
handleClick() {
this.a = 10;
this.b = 20;
this.c = 30;
}
}
</script>
如果不使用节流,那么在这个函数里,会在a更新时,b更新时,c更新时一共触发3次组件渲染,使用了节流后就只会触发一次了
export function queueWatcher(watcher: Watcher) {
const id = watcher.id;
// 判断watcher在不在queue里面
// 即使一个watcher在一个tick内多次触发update,也不会造成多次更新
// 节流实现,一个tick内只触发一次
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
// 如果正在刷新
let i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true;
// 在下一个tick去执行组件渲染
nextTick(flushSchedulerQueue)
}
}
}
代理模式
为其他对象提供一种代理以控制对这个对象的访问
Vue3中使用了代理来完成数据响应式,除了效率比Vue2有所提升,代码的耦合程度和安全性,可扩展性都变好了
// 创建响应式对象,传入要代理的对象,返回proxy
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
// 代理对象的get,set,deleteProperty...操作
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 缓存
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 开发者访问的是这个proxy对象,而不是原来的数据对象
const proxy = new Proxy(
target,
// collectionHandlers,baseHandlers都是proxy的配置
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}