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

作业简介

这个作业属于哪个课程 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
}
点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00