!design-pattern

OOP 六大原则: https://zhuanlan.zhihu.com/p/64591313

架构整洁之道:

设计模式、重构、编程规范等的经典书籍书籍推荐 - 知乎

如何学习设计模式

设计模式网站: https://zhuanlan.zhihu.com/p/134050236

23 种设计模式是《设计模式:可复用面向对象软件的基础》提出来的,其他书都是在此基础上进行解释和演化,很少有完全推翻它再搞一套的。不推荐读别的。

还有两本推荐的,不过不是设计模式相关,是架构相关的。《重构 -- 改善既有代码的设计》和《企业应用架构模式》。

《大话设计模式》作者:程杰 描述语言:C# **推荐理由:**这本书小说的形式在大牛与小菜的谈笑风生间阐述一个个设计模式,在看故事的同时理解设计模式。

这个不用纠结,这两本书都可以入手,也没多少钱。

《Head First 设计模式》弗里曼写的,老外写的,要看建议看原装的,翻译过来还是变味的,老外对程序的理解更好,更深入,适合进阶。

《大话设计模式》是国内程杰写的,里面是以对话的形式写的,其中小菜是一枚菜鸟,所以整本书学习更适合刚入门的人去学习,对设计模式有更好的理解。

我的建议,先看看大话设计模式,再看看Head First 设计模式

10本设计模式入门学习书籍推荐 - 知乎

设计模式之禅

如何学习设计模式? - 知乎

MV*

https://www.runoob.com/design-pattern/mvc-pattern.html

http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

https://blog.csdn.net/victoryzn/article/details/78392128

MVC

组成部分

MVC 模式的意思是,软件可以分成三个部分。

视图(View):用户界面。

控制器(Controller):业务逻辑

模型(Model):数据保存

通信方式

View 传送指令到 Controller

Controller 完成业务逻辑后,要求 Model 改变状态

Model 将新的数据发送到 View,用户得到反馈

特点

所有通信都是 单向

互动模式

接受用户指令时,MVC 可以分成两种方式。一种是通过 View 接受指令,传递给 Controller。

img

另一种是直接通过 controller 接受指令。

img

优点

缺点

实例

在 web app 流行之初, MVC 就应用在了 java(struts2)和 C#(ASP.NET)服务端应用中,后来在客户端应用程序中,基于 MVC 模式,AngularJS 应运而生。

实际项目往往采用更灵活的方式,以 Backbone.js 为例。

img

  1. 用户可以向 View 发送指令(DOM 事件),再由 View 直接要求 Model 改变状态。
  2. 用户也可以直接向 Controller 发送指令(改变 URL 触发 hashChange 事件),再由 Controller 发送给 View。
  3. Controller 非常薄,只起到路由的作用,而 View 非常厚,业务逻辑都部署在 View。所以,Backbone 索性取消了 Controller,只保留一个 Router(路由器)

MVP

组成部分

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

img

通讯方式

各部分之间的通信,都是双向的。

View 与 Model 不发生联系,都通过 Presenter 传递。

View 非常薄,不部署任何业务逻辑,称为 " 被动视图 "(Passive View),即没有任何主动性,而 Presenter 非常厚,所有逻辑都部署在那里。

特点

M、V、P 之间双向通信。

View 与 Model 不通信,都通过 Presenter 传递。Presenter 完全把 Model 和 View 进行了分离,主要的程序逻辑在 Presenter 里实现。

Presenter 与具体的 View 是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更 View 时候可以保持 Presenter 的不变,这样就可以重用。不仅如此,还可以编写测试用的 View,模拟用户的各种操作,从而实现对 Presenter 的测试–从而不需要使用自动化的测试工具。

优点

缺点

MVP 应用

可应用与 Android 开发

MVVM

参考: https://www.zhihu.com/question/310674885

MVVM 是 Model-View-ViewModel 的简写。微软的 WPF(Windows Presentation Foundation–微软推出的基于 Windows 的用户界面框架) 带来了新的技术体验, 使得软件 UI 层更加细节化、可定制化。与此同时,在技术层面,WPF 也带来了 诸如:

MVVM 模式其实是 MV 模式与 WPF 结合的应用方式时发展演变过来的一种新型架构模式。它 立足于原有 MVP 框架并且把 WPF 的新特性糅合进去,以应对客户日益复杂的需求变化。

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致

img

唯一的区别是,它采用双向绑定(data-binding):View 的变动,自动反映在 ViewModel,反之亦然。AngularEmber 都采用这种模式。

组成部分

MVVM 典型特点是有四个概念:Model、View、ViewModel、绑定器。MVVM 可以是单向绑定也可以是双向绑定甚至是不绑定

MVVM 本质上是 M-V-C-VM,它是在 MVC 的基础上增加了一层 VM,只不过 C 变弱了,被并入到 M 概念中,VM 用于分离 V 和 M,并且让用户避免由于直接操作 V 层的 DOM 而带来的繁琐和效率低下,MVVM 使开发更高效,结构更清晰,增加代码的复用性。

在不同的 GUI(图形用户界面)上进行展示时,Model、Controller、View-Model 能够复用,只需把 View 层进行替换。

在不同类型的 UI(用户界面)上进行展示时,Model、Controller 能够复用,只需把 View-Model、View 层进行替换。比如:假设我们开发的是一款针对盲人的应用,那么输出设备或许我们需要考虑使用扬声器来代替显示器,输入设备使用麦克风,这时我们只需将上述的 View-Mode l 替换为 Audio-Model 作为语音模型,将 View 层替换为 Audio 层用于播放语音和接收语音输入。

个人认为:在基于 MVVM 框架的项目中,不管是双向数据绑定还是单向数据绑定,你在开发中实际要面对的都是 ViewModel 和 Model 层之前的通信,因为 View 和 ViewModel 层之间的映射和通信都是由框架自动完成的,

React 仅仅是 View

React 不是 MVVM,就算加上 React-router 和 Redux,也勉强是一个 MVC

MVVM 在 React 中对应关系

MVVM 的双绑和单绑区别

一般,只有 UI 表单控件才存在双向数据绑定,非 UI 表单控件只有单向数据绑定。

单向数据绑定是指:M 的变化可以自动更新到 ViewModel,但 ViewModel 的变化需要手动更新到 M(通过给表单控件设置事件监听)

双向数据绑定是指念:M 的变化可以自动更新到 ViewModel,ViewModel 的变化也可以自动更新到 M

双向绑定 = 单向绑定 + UI 事件监听。双向和单向只不过是框架封装程度上的差异,本质上两者是可以相互转换的。

优缺点:在表单交互较多的情况下,双向数据绑定的优点是数据更易于跟踪管理和维护,缺点是代码量较多比较啰嗦。

单向数据绑定的优缺点和单向绑定正好相反。

三大框架的异同

三大框架都是数据驱动型的框架

vue 及 angular 是双向数据绑定;react 是单向数据流,并没有实现绑定

Vuex、Redux 都是单项数据绑定的,即 M 的变化可以自动更新到 V,但 V 的变化必须手动触发事件更新到 M,这种单项数据绑定使数据更易于跟踪管理和维护。

单例模式

模块导出的都是同一个,无论是 commonjs 还是 es module

能实现多例吗?我想要每次 import require 导入的都是不同的对象,这样是不是有点偏离设计?

参考:https://www.runoob.com/design-pattern/singleton-pattern.html

概念

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。
  4. 自行实例化(主动实例化)
  5. 可推迟初始化,即延迟执行(与静态类/对象的区别)

主要解决:一个全局使用的类频繁地创建与销毁,控制实例数目,节省系统资源的时候

思路:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

应用实例:

  1. 一个班级只有一个班主任。
  2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  2. 避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

全局变量

JavaScript 是一门非正规面向对象的语言,并没有类的定义。而单例模式要求一个 “唯一” 和 “全局访问” 的对象,在 JavaScript 中类似全局对象,刚好满足单例模式的两个特点:“唯一” 和 “可全局访问”。虽然它不是正规的单例模式,但不可否认确实具备类单例模式的特点。

// 全局对象
var globaObj = {};

使用全局变量会有以下问题:

  1. 命名空间污染(变量名冲突)
  2. 维护时不方便管控(容易不小心覆盖)

全局变量问题折中的应对方案:

  1. 使用命名空间
  2. 闭包封装私有变量(利用函数作用域)
  3. ES6 的 const/symbol

虽然全局变量可以实现单例,但因其自身的问题,不建议在实际项目中将其作为单例模式的应用,特别是中大型项目的应用中,全局变量的维护该是考虑的成本。

模式实现

module 的特性:若同一个应用中的其他模块打算从 example.js 导入绑定,则那些模块都会使用这段代码中所用的同一个模块实例。

// mod.js
function C() {
  this.sum = 0;
  this.add = function () {
    this.sum += 1;
  };
  this.show = function () {
    console.log(this.sum);
  };
}

export let c = new C();

上面的脚本 mod.js,输出的是一个 C 的实例。不同的脚本加载这个模块,得到的都是同一个实例。

// x.js
import {c} from './mod';
c.add();

// y.js
import {c} from './mod';
c.show();

// main.js
import './x';
import './y';

现在执行 main.js,输出的是 1

$ babel-node main.js
1

这就证明了 x.jsy.js 加载的都是 C 的同一个实例。

完备单例实现

https://juejin.im/post/6844903874210299912

主从模式 Master-slave

https://www.cnblogs.com/dmego/p/9068734.html

更多模式

参考最开始的网站, 有空总结一下

Domain Driven Design

状态机

什么是状态机

当处理的情况特别多,我们把每种情况的处理逻辑封装成一个状态,然后不同情况之间的转换变成状态的转换。这种代码组织形式就是状态机。

当每个状态知道输入某一段内容时转到哪一个状态,在一个循环内自动进行状态的流转和不同状态的处理,这种叫做状态自动机(automation),如果一个状态在一种输入下只有一个后续状态,这种就叫做确定性有限状态自动机(DFA)。

状态之间的流转可以通过状态转换图来表示。

基础示例

Typescript 源码中的状态机

typescript compiler 就是通过状态机来组织整个编译流程的:

首先 tsc 划分了很多状态,每种状态处理一种逻辑。比如:

typescript 就通过这种状态的修改来完成不同处理逻辑的流转,如果处理到结束状态就代表流程结束。

这样使得整体流程可以很轻易的扩展和修改,比如想扩展一个阶段,只要增加一个状态,想修改某种状态的处理逻辑,只需要修改下状态机的该状态的转向。而不是大量的 if else 混杂在一起,难以扩展和修改。

可以看到,状态机使得 typescript 的编译步骤可以灵活的扩展和修改。

词法分析中的状态机

其实状态机最常用的地方是用于词法分析,因为每个 token 都是一种处理情况,自然会有很多 if else。

像下面这样用 if else 来做分词自然也可以,这是 wenyan 的词法分析逻辑,但是代码很难维护。

更好的做法是使用状态机(DFA)来做分词,把每一种 token 的处理封装成一个状态。通过边界条件的判断来做状态流转,比如某个 wxml parser 分了这些状态:

每种状态处理一种情况的 token 的识别:

通过状态的变化驱动处理逻辑的流转:

这样不断的进行各状态之间的流转,当处理到字符串的末尾的时候,就完成了所有的分词。

业务代码中的状态机

业务代码中当遇到各种 if else 的判断的时候同样可以用状态机来优化。把每种情况封装成一个状态,通过某一种条件触发状态的流转,然后在状态机里面选择不同的状态处理逻辑进行处理。

不管是游戏中不同状态做不同的处理逻辑,还是在 ui 项目中不同状态做不同的渲染,当代码逻辑复杂时,难免会有很多 if else,这时候都可以用状态机的思路来做优化。

这样,当后续扩展处理逻辑、修改不同条件下的处理逻辑都变得简单和清晰很多。

设计模式三大类

策略模式属于行为型的设计模式

在有多种算法相似的情况下,使用 if...else 所带来的代码复杂度提升和难以维护,此时可以使用策略模式。

感觉这个文章有点弱智, 不如 switch 如何用策略模式干掉 if-else - 掘金

没有看到有什么价值...

设计原则

多用组合, 少用继承

decorator-pattern

原则四: 为交互对象之间的松耦合设计而努力

观察者模式, 以松耦合方式在一系列对象之间沟通状态。

类应该对扩展开放, 对修改关闭

decorator-pattern

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配 新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性 可以应对改变,可以接受新的功能来应对改变的需求。

对扩展开放,对修改关闭?听起来很矛盾。设计如何兼顾两者?

乍听之下,的确感到矛盾, 毕竟, 越难修改的事物,就越难以扩展,不是吗?

但是,有一些聪明的 OO 技巧,允许系统在不修改代码的精况下,进行功能犷展。 想想观察者模式, 通过加 入新的观察者,我们可以在任何时候扩展 Subject (主题),而且不需向主题中添加代码。以后,你还会陆续看到更多的扩展行为的其他 OO 设计技巧。

如何将某件东西设计成可以扩展, 又禁止修改?

许多模式是长期经骏的实证, 可通过提供扩展的方法来保护代码免于被修改。在本章,将看到使用装饰者模式的 一个好例子,完全遵循开放 - 关闭原则.

我如何让设计的每个部分都遵循开放 - 关闭原则?

通常,你办不到。要让 OO 设计同时具备开放性和关闭性,又不修改现有的代码,需要花去许多时间和努力.一般来说,我们实在没有闱工夫把设计的每 个部分都这么设计(而且,就算做得到, 也可能只是一种浪费).

遵循开放 - 关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力臬中在设计中最有可能改变的地方,然后应用开放 - 关闭 原则。

怎么知道,哪些地方的改变 是更重要呢?

这牵涉到设计 OO 系统的经验

和对你工作领域的了解。多看一些其他的例子可以帮你学习如何讲别设计中的变化

昂然似乎花立不盾,但是的确有一些技术可以允许在不直接修改代码的情况下对其进行扩展。

在选择需要被炉屐的代码部分时要小心。每个地方都采用开放 - 关闭原则, 是一种浪费, 也没必要, 还会导致代 码变得复杂也难联理解

依赖倒置原则

首先,这个原则听起来很像是“针对接口编程,不针 对实现编程”,不是吗?的确很相似,然而这里更强调“抽象”。这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件, 都应该依赖于抽象。

示例

当你直接实例化•个对象时,就是在依赖它的具体类。清返回前 贞看看这个依赖性很高的比萨店例子,它由比薄店类来创建所有 的比萨对象,而不是委托给工厂。

如果把这个版本的比萨店和它依赖的对象画成 i 张图,看起来是 这样的:

PizzaStore 是“高层组件”,而比萨实现是“低层组件”, 很清楚地,PizzaStore 依赖这些具体比萨类。

所谓高层组件, 是由其他底层组件定义其行为的类. 例如, PizzaStore 是个高层组件, 因为它的行为是由披萨定义的, PizzaStore 创建所有不同的披萨对象, 准备, 烘烤, 切片, 装盒. 而披萨本身属于底层组件.

现在,这个原则告诉我们,应该重写代码以便于我们依赖抽象类,而不依赖具体类。对于高层及低层模块都应 该如此。

依赖倒置原则,究竟倒置在哪里?

在依赖倒置原则中的倒置指的是和一般 OO 设计的思考方式完全相反。看看前一页的图,你会注意到低层组件现在竟然依赖高层的抽象。同样地,高层组件现在也依赖相同的抽象。前几页所绘制的依赖图是由上而下的,现在却倒置了,而且高层与低层模块现在都依赖这个抽象。

让我们好好地回顾一个设计过程来看看,究竟使用了这个原则之后,对设计的思考方式会被怎样地倒置……

指导方针

变量不可以持有具体类的引用: 如果使用 new 就会持有具体的类, 可以使用工厂来避开这样的做法

不要让类派生自具体类: 如果派生自具体类, 你就会依赖具体类, 请派生自一个抽象 (接口或者抽象类)

不要覆盖基类中已实现的方法: 如果覆盖基类中已实现的方法, 那么你的基类就不是一个真正适合被继承的抽象. 基类中已实现的方法, 应该由所有子类共享.

你说的没错!正如同我们的许多原则一样,应该尽圻达到这个原则, 而不是随时都要遵循这个原则。我们都很清楚,任何 Java 程序都有

但是,如果你深入体验这些方针,将这些方针内化成你思考的一部 分,那么在设计时,你将知道何时有足够的理由违反这样的原则。 比方说,如果有 i 个不像是会改变的类,那么在代码中直接实例化 具体类也就没什么大得。想想看,我们平常还不是在程序中不假思 索地就实例化字符串对象吗?就没有违反这个原则?当然有!可以 这么做吗?可以!为什么?因为字符串不

另一方面,如果有个类可能改变,你可以采用一些好技巧(例如工厂 方法)来封装改变。

最少知识原则

最少知识 (Least Knowledge) 原则告诉我们要减少对象之 间的交互,只留卜几个“密友”。这个原则通常是这么说 的:这是说,当你正在设计一个系统,不 管是任何对象.你都要注意它所交互的类有哪些,并注意 它和这些类是如何交互的这个原则希望我们在设计中,不要让太多的类耦合在一 起,免得修改系统中一部分,会影响到其他部分。如果许 多类之间相互依赖,那么这个系统就会变成一个易碎的系 统,它需要花许多成本维护,也会因为太复杂而不容易被 其他人了解

究竟要怎样才能避免这样呢?这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法

如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识 的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了(让我们的朋友圈子维持在最小的状态)

缺点

研究显示这会 减少软件的维护成本;但是采用这个 原则也会导致更多的“包装”类被制 造出来,以处理和其他组件的沟通, 这可能会导致复杂度和开发时间的增 加,并降低运行时的性能

模式

外观模式遵循最少知识原则

好莱坞原则

别调用 (打电话) 我们, 我们会调用 (打电话给) 你

好莱均原则可以给我们一种防止“依赖腐败”的方法。 高层组件依赖低层组件,而低层组件乂依赖高层组件,

高层组件乂依赖边侧组件,而边侧组件又依赖低层组件时, 依赖腐败就发生了。在这种情况下,没有人可以轻易地搞 懂系统是如何设计的

在好莱坞原则之下,我们允许低层组件将白己挂钩到系统 上,但是高层组件会决定什么时候和怎样使用这些低层组 件。换句话说,高层组件对待低层组件的方式是“别倜用 我们,我们会调用你”

模式

模板方法模式遵循好莱坞原则

FAQ

好莱坞原则和依赖倒置原则之间的关系

依赖倒置原则教我们尽量避免使用具体类, 而多使用抽象

而好莱坞原则是用在创建框架或姐件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件, 同时底层组件也不会直接调用高层组件

两者的目标都是在于解耦, 但是依赖倒置原则更加注重如何在设计中避免依赖。 好莱坞原则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖它们

单一职责

一个类应该只有一个引起变化的 原因

类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域

这个原则告诉我们, 尽量让每个类保持单一责任

内聚 (cohesion) 这个术语你应该听过, 它用来度量一个类或者模块紧密达到单一目的或责任.

当个模块或一个类被设计成只支持一组相关的功能时,我们说 它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

内聚是一个比单一责任原则更普遍的概念,但两若其实关系是很密切的。 遵守这个原则的类容易具有很离的凝聚力,而且比背负许多责任的低内聚类更容易维护。

FAQ

运行时的行为说的是啥?

指的是 java 的 运行时. 是一个相对于编译时的概念.

不通过继承 又能如何达到复用呢?

利用组合 (composition) 和委托 (delegation) 可以在 运行时具有继承行为的效果

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会 继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在 运行时动态地进行扩展。

我可以利用此技巧把多个新职责,甚至是设计超类时还没有想到的 职责加在对象上。而且,可以不用修改原来的代码。

通过动态地组合对象,可以写新的代码添加新功能,而 无须修改现有代码。既然没有改变现有代码,那么引进 bug 或产生意外副作用的 机会将大幅度减少。

实现一个接口

并不一定表示写一个类, 并利用 implement 关键词类实现某个 interface. 实现一个接口泛指实现某个超类型的某个方法

善于利用空对象

noop 函数可以节省很多特殊判断逻辑

如何判断两个类之间是否有耦合?

只需要看这两个类是否有对方的引用就行了.