读书笔记----软件设计原则、设计模式

作业简介

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/homework/11833
这个作业的目标 深入思考理解设计原则,及其优缺点,思考在项目中如何使用设计模式

(博客园的代码渲染出来是歪的,我在我的博客上又发了一次,内容一样,不过这个样式会好看点点这里

书籍和参考资料

设计原则

单一职责原则 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方法,渲染Watcherupdate方法就是重新渲染组件,这就是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
}
posted @ 2021-03-13 13:51  sorena  阅读(169)  评论(0编辑  收藏  举报