转自:https://github.com/jacksplwxy/DesignPatterns_TypeScripthtml
*标题:
·前端开发者如何学习设计模式
·系统学习设计模式
·TypeScript版设计模式前端
*前言:
·背景:
①前端开发者广泛缺少设计模式相关知识,但网上相关资料基本都是基于java或c#,不利于前端学习。
②经典23种设计模式来源于静态语言,部分基于js的设计模式书籍每每又只是单单经过动态语言js来实现,不利于真正理解设计模式。
③由于设计模式很难验证对错,网络上存在大量错误的设计模式教程误导新人。
④设计模式资料广泛只讲述模式的实现步骤,缺少对理解这些步骤的基础知识进行讲解,致使对设计模式的学习只是表面招式。
·项目内容:
①帮助缺少经验的前端开发者了解设计模式的前因后果,补充学习设计模式须要了解的基础知识,如:设计原则、解耦、控制反转、依赖注入、IoC容器、反射、注解、装饰器等。
②着重实现TypeScript版设计模式代码。
③补充实现Java/JavaScript版设计模式代码。
·理解有误处欢迎指出。java
*什么是设计模式?
·狭义的设计模式:指的是GoF四人组在《Design Patterns: Elements of Reusable Object-Oriented Software》一书中提出的23种设计模式。
·广义的设计模式:最先的设计模式是美国著名建筑大师克里斯托夫·亚历山大在他的著做《建筑模式语言:城镇、建筑、构造》中描述了一些常见的建筑设计问题,并提出了253种关于对城镇、邻里、住宅、花园和房间等进行设计的基本模式。后来软件界也开始论坛设计模式的话题,由于这也是相似的。因此设计模式指的是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具备必定的广泛性,是能够反复使用的。git
*为何要学习设计模式?
在弄清楚这个问题以前,咱们先思考什么才是高质量的程序?高质量程序的特色:开发周期短、代码无bug、代码性能高、代码易阅读、代码够健壮、代码易扩展、代码易重用。
那咱们如何开发出高质量程序呢?一个重要的方式就是遵照前人总结的7大设计原则:①开闭原则:总纲,要对扩展开放,对修改关闭②里氏替换原则:不要破坏继承体系③依赖倒置原则:要面向接口编程④单一职责原则:实现类要职责单一⑤接口隔离原则:在设计接口的时候要精简单一⑥迪米特法则:要下降耦合度⑦合成复用原则:要优先使用组合或者聚合关系复用,少用继承关系复用。
好的程序须要靠7大设计原则完成,可是因为语言的缺陷致使程序须要按照必定复杂度的步骤去实现这些设计原则,而这些步骤一般都是固定的,就像武功中的套路招式同样,若是再给这些套路加上好听的名字,这就成了设计模式。也就是说23种设计模式就是7大设计原则在某些语言的具体实现的一种方式,每一个设计模式的背后咱们都能找到其依靠的一种或多种设计原则。换句话说就是,只要咱们写代码遵循设计原则,代码就会天然而然变成了某些设计模式,这也在王垠的《解密“设计模式”》中获得证实。
因为现实问题的复杂性,每每致使代码不可能同时知足全部的设计原则,甚至要违背部分设计原则,这里就会有一个最佳实践的问题了。而设计模式就为解决特定问题提供了最佳实践方案,以致于学习了设计模式后,在遇到特定问题时,咱们脑子很容易知道如何在知足设计原则最优解的基础上实现代码的编写。
虽然设计原则为开发出高质量程序指明了方向,但没有对程序的高性能、易用性等作出指示,而设计模式在这方面作了补充。
总结:①设计模式经过写代码的形式帮助咱们更好的学习理解设计原则;②为实现设计原则的招式统一命名;③为特定场景问题的提供最优解决方案;④补充了设计原则在构建高性能、易使用程序等方面的内容。es6
*解耦与高质量程序之间的是什么关系?
·解耦(低耦合)在某个角度上说属于高质量程序的一个重要体现,但不是所有。高质量程序具体还体如今代码易复用、代码安全稳定、代码层次结构清晰、代码易扩展、代码性能好等等许多细节方面。
·解耦是一个很模糊的定义,耦合性高低实际上能够用依赖性、正交性、紧凑性3个指标来衡量,具体参考:https://www.zhihu.com/question/21386172/answer/54476702 。实际上这3个指标大概分别对应着7大设计原则中的迪米特法则、单一职责原则、接口隔离原则等原则,而7大设计原则也就是高质量软件的编写原则,因此也印证了解耦是属于高质量程序的一部分体现。github
*设计模式补充:
·学习设计模式的核心是掌握设计模式的意图是什么:不一样的设计模式在不一样的语言中会有不一样表现形式,千万不要被模式中的各类概念迷惑,而只学到表面套路。我看到不少热门教程是示例代码甚至是错误的,例如这个超过2k star的设计模式项目https://github.com/torokmark/design_patterns_in_typescript ,他的工厂方法的实现就不对,若是要增长新的产品,他就必须必须修改createProduct代码,但这违背了开闭原则。致使这个错误的缘由就是由于做者没有理解工厂方法的目的:对简单工厂模式的进一步抽象化,使系统在不修改原来代码的状况下引进新的产品,即知足开闭原则。又如这个项目的原型模式的实现也是错误的,原型模式的场景是由于复制一个对象比new一个对象更高效。因此学习设计模式的正确姿式应该是:掌握该设计模式的意图,并在遵照设计原则的状况下去实现它。
·设计模式的招式并非一成不变的,它在不一样语言中会有不一样的表现,但背后的思想和设计原则都是同样的,例如装饰器模式在java中须要定义抽象构件、抽象装饰,而ts只需一个@便可搞定。例如Java8加入lambda后,不少设计模式的实现都会改变。厉害的语言不须要那么多设计模式也能知足设计原则,参考:https://zhuanlan.zhihu.com/p/19835717
·设计模式在不一样语言之间的实现原理:GoF的《设计模式》一书是针对面向对象语言提炼的技巧,但并不意味着设计模式只能用面向对象语言来写,实际上动态语言也是可使用设计模式的。例如Java这种静态编译型语言中,没法动态给已存在的对象添加职责,全部通常经过包装类的方式来实现装饰者模式。可是js这种动态解释型语言中,给对象动态添加职责是再简单不过的事情。这就形成了js的装饰者模式再也不关注给对象动态添加职责,而是关注于给函数动态添加职责。例若有人模拟js版本的工厂模式,而生硬地把建立对象延迟到子类中。实际上,在java等静态语言中,让子类来“决定”建立何种对象的缘由是为了让程序迎合依赖倒置原则。在这些语言中建立对象时,先解开对象类型之间的耦合关系很是重要,这样才有机会在未来让对象表现出多态性。而在js这类类型模糊的语言中,对象多态性是天生的,一个变量既能够指向一个类,又能够随时指向另外一个类。js不存在类型耦合的问题,天然也没有刻意去把对象“延迟”到子类建立,也就是说,js其实是不须要工厂方法模式的。模式的存在首先是为了知足设计原则的。
·分辨模式的关键是意图而不是结构:在设计模式的学习中,有人常常会发出这样的疑问:代理模式和装饰着模式,策略模式和状态模式,这些模式的类图看起来几乎如出一辙,他们到底有什么区别?实际上这种状况是广泛存在的,许多模式的类图看起来都差很少,模式只有放在具体的环境下才有意义。好比咱们的手机,用它当电话的时候它就是电话;用它当闹钟的时候它就是闹钟;用它玩游戏的时候他就是游戏机。有不少模式的类图和结构确实很类似,但这不过重要,辨别模式的关键是这个模式出现的场景,以及为咱们解决了什么问题。
·设计模式一直在发展,例如如今逐渐流行起来的模块模式、沙箱模式等,但真正获得人们的承认还须要时间的检验。
·设计模式的合理使用:
-- 整体来讲,使用设计模式的必要性的程度是逐级递增的:应用程序(Application) < 工具包/类库(ToolKit/Library) < 框架(Framework)
-- 具体来讲,咱们没必要刻意为了设计模式而设计模式,例如咱们当前只须要建立一个产品,而将来没有多大可能增长新产品时,咱们就用简单工厂模式,而无需为了符合开闭原则而去选择工厂方法模式。引用轮子哥的话:“为了合理的利用设计模式,咱们应该明白一个概念,叫作扩展点。扩展点不是天生就有的,而是设计出来的。咱们设计一个软件的架构的时候,咱们也要同时设计一下哪些地方之后能够改,哪些地方之后不能改。假若你的设计不能知足现实世界的须要,那你就要重构,把有用的扩展点加进去,把没用的扩展点去除掉。这跟你用不用设计模式不要紧,跟你对具体的行业的理解有关系。假若你设计好了每个扩展点的位置,那你就能够在每个扩展点上应用设计模式,你就不须要去想到底这个扩展点要怎么实现他才会真正成为一个扩展点,你只须要按照套路写出来就行了。
·设计模式命名:为了他人能快速理解你使用的设计模式,建议参考模式英文名进行命名,例如:建造者模式——xxxBuilder,单例模式——xxxSingleton,适配器模式——xxxAdapter,状态模式——xxxState,策略模式——xxxStratege等等。
·对设计模式的误解:
-- 习惯把静态语言的设计模式照搬到动态语言中
-- 习惯根据模式名称去臆测该模式的一切web
*开闭原则:
·定义:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽可能在不修改原有代码的状况下进行扩展。
·解释:
当软件系统须要面对新的需求时,咱们应该尽可能保证系统的设计框架是稳定的。若是一个软件设计符合开闭原则,那么能够很是方便地对系统进行扩展,并且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具有较好的稳定性和延续性。
为了知足开闭原则,须要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,能够为系统定义一个相对稳定的抽象层,而将不一样的实现行为移至具体的实现层中完成。在不少面向对象编程语言中都提供了接口、抽象类等机制,能够经过它们定义系统的抽象层,再经过具体类来进行扩展。若是须要修改系统的行为,无须对抽象层进行任何改动,只须要增长新的具体类来实现新的业务功能便可,实如今不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。
在软件开发中,通常不把对配置文件的修改认为是对系统源代码的修改。若是一个系统在扩展时只涉及到修改配置文件,而原有的代码没有作任何修改,该系统便可认为是一个符合开闭原则的系统。因此,配置文件+反射技术在java、c#、.net等各种后端语言中大量采用,以实现彻底开闭原则。
·开闭原则的实现由里氏替换原则和依赖倒置原则来完成。算法
*里氏替换原则:
·背景:
继承的优势:代码共享,减小建立类的工做量,每一个子类都拥有父类的方法和属性、提升代码的重用性、子类能够形似父类,但又异于父类、提升代码的可扩展性、提升产品或项目的开放性。
继承的缺点:继承是侵入性的:只要继承,就必须拥有父类的全部属性和方法、下降代码的灵活性:子类必须拥有父类的属性和方法、加强了耦合性:当父类的常量、变量和方法被修改时,必须要考虑子类的修改,并且在缺少规范的环境下,这种修改可能带来很是糟糕的结果大片的代码须要重构
里氏替换原则可以克服继承的缺点。
·定义:子类能够扩展父类的功能,但不能改变父类原有的功能。父类能出现的地方均可以用子类来代替,并且换成子类也不会出现任何错误或异常,而使用者也无需知道是父类仍是子类,但反过来则不成立。
·解释:
LSP的原定义比较复杂,咱们通常对里氏替换原则 LSP的解释为:子类对象可以替换父类对象,而程序逻辑不变。
里氏代换原则告诉咱们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,若是一个软件实体使用的是一个子类对象的话,那么它不必定可以使用基类对象。例如:我喜欢动物,那我必定喜欢狗,由于狗是动物的子类;可是我喜欢狗,不能据此判定我喜欢动物,由于我并不喜欢老鼠,虽然它也是动物。
里氏替换原则有至少如下两种含义:
①里氏替换原则是针对继承而言的,若是继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类从新定义。子类只能经过新添加方法来扩展功能,父类和子类均可以实例化,而子类继承的方法和父类是同样的,父类调用方法的地方,子类也能够调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,固然逻辑一致,相安无事。
②若是继承的目的是为了多态,而多态的前提就是子类覆盖并从新定义父类的方法,为了符合LSP,咱们应该将父类定义为抽象类,并定义抽象方法,让子类从新定义这些方法,当父类是抽象类时,父类就是不能实例化,因此也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
不符合LSP的最多见的状况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类从新定义,这一类的实现继承会形成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一块儿,不利于程序扩展和维护。
如何符合LSP?总结一句话 —— 就是尽可能不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。
·最佳实践:
①子类必须彻底实现父类的抽象方法,但不能覆盖父类的非抽象方法;
②子类中能够增长本身特有的方法;
③当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数要更宽松;
④当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
·里氏代换原则和依赖倒置原则同样,是开闭原则的具体实现手段之一。typescript
*依赖倒置原则:
·定义:高层模块不该该依赖低层模块,二者都应该依赖其抽象;抽象不该该依赖细节,细节应该依赖抽象。
·解释:
高层模块不该该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。换言之,模块间的依赖是经过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是经过接口或抽象类产生的。
接口和抽象类不该该依赖于实现类,而实现类依赖接口或抽象类。这一点其实不用多说,很好理解,“面向接口编程”思想正是这点的最好体现。
·解释:
常规咱们认为上层模块应该依赖下层,可是这也有个问题就是,下层变更将致使“牵一发动全身”。依赖倒置就是反常规思惟,将本来的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定须要什么,底层去实现这样的需求,可是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的状况。固然,严格的来讲上层不该该依赖下层,而依赖自身接口,经过注入的方式依赖其余接口。
·控制反转(Inversion of Control):就是依赖倒置原则的一种代码设计的思路。具体采用的实现方法就是所谓的依赖注入(Dependency Injection)。
·控制反转容器(IoC Container):,由于采用了依赖注入,在初始化的过程当中就不可避免的会写大量的new,而且还要要管理各个对象之间依赖关系,因此这里使用工厂方法仍是比较麻烦。而IoC容器就解决以上2个问题。这个容器能够自动对你的代码进行初始化,你只须要维护一个Configuration(能够是xml能够是一段代码),而不用每次初始化一实例都要亲手去写那一大段初始化的代码。另一个好处是:咱们在建立实例的时候不须要了解其中的依赖细节。
·java的反射机制:
-- 类也是对象,类是java.lang.Class类的实例对象
-- 编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。new建立对象是静态加载类,在编译时刻就要加载全部可能用到的类,这样致使某一个类(的类型)出现问题,其余全部类都将没法使用(没法经过编译)。经过动态加载类能够解决这个问题。动态加载的方法,即经过类的类类型建立该类的对象,示例:Class c=Class.forName(args[0]);c.newInstance();
-- 获取类的方法信息,代码示例:Class c=obj.getClass();Method[] ms=c.getMethods();//参数列表获取省略
-- 获取类的成员变量信息,代码示例:Class c=obj.getClass();Field[] fs=c.getFields();
-- 获取类的构造方法信息信息,代码示例:Class c=obj.getClass();Constructor[] cs=c.getConstructors();//参数列表获取省略
-- 方法的反射,操做步骤:①根据方法的名称和参数列表获取惟一的方法;②拿到方法后,method.invoke(对象,参数列表)。
-- 反射的操做都是编译成字节码之后的操做
-- java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了。(其实typescirpt也是这样的,类型检查只发生在编译时)
-- 由于反射的操做是编译后的,因此不存在类型检查问题了
-- 小结:Java反射机制容许程序在运行时透过Reflection APIs取得任意一个已知名称的class的内部信息,包括modifiers(如public、static等)、superclass(如Object)、实现的interfaces(如Serializable)、fields(属性)和methods(方法)(但不包括methods定义),可于运行时改变fields的内容,也可调用methods.
-- 配置文件+反射机制实现开闭原则:在引入配置文件和反射机制后,须要更换或增长新的具体类将变得很简单,只需增长新的具体类并修改配置文件便可,无须对现有类库和客户端代码进行任何修改,彻底符合开闭原则。在不少设计模式中均可以经过引入配置文件和反射机制来对客户端代码进行改进,如在抽象工厂模式中能够将具体工厂类类名存储在配置文件中,在适配器模式中能够将适配器类类名存储在配置文件中,在策略模式中能够将具体策略类类名存储在配置文件中等等。经过对代码的改进,可让系统具备更好的扩展性和灵活性,更加知足各类面向对象设计原则的要求。
·java自定义注解:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description{
String desc();
String author();
int age() default 18;
}
-- 使用@interface关键词定义注解
-- 成员以无参无异常方式声明
-- 能够用default为成员指定一个默认值
-- 成员类型是受限制的,合法的类型包括原始类型及String,Class,Annotation,Enumeration
-- 若是注解只有一个成员,则成员名必须取名为value(),在使用时能够忽略成员名和赋值号(=)
-- 注解类能够没有成员,没有成员的注解成为标识注解
-- @Target、@Retention、@Inherited、@Documented为注解Description的注解,即元注解
-- @Target(options)中的options包括注解的做用域,该例中表示注解能够在方法和类中使用。完整的做用域项包括:CONSTRUCTOR(构造方法)、FIELD(字段)、LOCAL_VARIABLE(局部变量)、METHOD(方法)、PACKAGE(包)、PARAMETER(参数)、TYPE(类,接口)
-- @Retention():注解生命周期,包括SOURCE(只在源码显示,编译时丢弃)、CLASS(编译时会记录到class中,运行时忽略)、RUNTIME(运行时存在,能够经过反射读取)
-- @Inherited:容许子注解继承,子类只能继承父类的类上的注解,不会继承成员上的注解
-- @Documented:生成javadoc时会包含注解信息
--使用自定义注解:
@Description(desc='I am eyeColor',author='Jacksplwxy',age=18)
public String eyeColor(){
return 'red';
}
·元数据:指用来描述数据的数据。
·元数据和注解:注解能够用来描述数据,全部注解是元数据的一种实现方式。
·注解的做用:注解有不少做用,其中最重要的一个就是搭配反射实现开闭原则。由于注解能够被反射解析出来,此时的注解至关于一个配置文件。另外,在java中,除了注解充当配置文件,还能够用xml做为配置文件,但注解优势明显:①在class文件中,能够下降维护成本,annotation的配置机制很明显简单;②不须要第三方的解析工具,利用java反射技术就能够完成任务;③编辑期能够验证正确性,差错变得容易;④ 提升开发效率
·ts中的注解:ts中其实没有注解的概念,可是前端界曾经仍是有语言借鉴了注解:Angular2的AtScript语言,它能完彻底全的单纯附加元数据。例如:
@Component({
selector: 'app'
})
class AppComponent {}
等价于
class AppComponent {}
AppComponent.annotations = [
new Component({
selector: 'app'
})
]
·注解和装饰器区别:
-- 注解(Annotation):java中元数据的一种实现方式。仅提供附加元数据支持,并不能实现任何操做。须要另外的 Scanner 根据元数据执行相应操做。
-- 装饰器(Decorator):ES6中增长的对装饰器模式的简单实现。其仅提供定义劫持,可以对类及其方法的定义并无提供任何附加元数据的功能。
他们语法类似,都是@符号。但注解仅仅为数据提供一些更加细节的属性描述,咱们能够利用反射等方式来获取这些描述再进行函数操做。而装饰器能够至关于直接附加函数操做。实际上,二者在实现上均可以相互模拟。
·ts的反射机制实现:
-- 回顾反射:反射就是根据类名获取其更详细信息
-- Function.prototype.toString实现反射:Function.prototype.toString这个原型方法能够帮助你得到函数的源代码,经过合适的正则, 咱们能够从中提取出丰富的信息。可是并不方便,也不优雅。
-- JavaScript自己为动态语言,天生具有反射能力,例如遍历对象内全部属性、判断数据类型。ES6中新增了新的api:Reflect,来把这些操做归结到一块儿。Reflect可以获取到类中的成员变量和方法,可是因为js迎合了web的压缩特色,因此其没法获取到参数名。解决方案就是经过在方法上添加定义了参数相关的装饰器,再解析装饰器便可获取参数名了。惋惜ES6的Reflect也没法获取到究竟有哪些装饰器添加到这个类/方法上。为了获取到装饰器,Reflect Metadata应运而生,它是ES7的一个提案,它主要用来在声明的时候添加和读取装饰器的。
·依赖注入的三种方式:
-- 构造函数注入
-- 属性注入
-- 接口注入
·ts实现IoC容器:
-- 《使用Typescript实现依赖注入(DI)》:https://blog.csdn.net/HaoDaWang/article/details/79776021
·里氏代换原则和依赖倒置原则同样,是开闭原则的具体实现手段之一。
·最佳实践:
-- 每一个类尽可能都有接口或抽象类,或者抽象类和接口二者都具有:这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。
-- 变量的显示类型尽可能是接口或者是抽象类:不少书上说变量的类型必定要是接口或者是抽象类,这个有点绝对化了,好比一个工具类,xxxUtils通常是不须要接口或是抽象类的。还有,若是你要使用类的clone方法,就必须使用实现类,这个是JDK提供一个规范。
-- 任何类都不该该从具体类派生:若是一个项目处于开发状态,确实不该该有从具体类派生出的子类的状况,但这也不是绝对的,由于人都是会犯错误的,有时设计缺陷是在所不免的,所以只要不超过两层的继承都是能够忍受的。特别是作项目维护的同志,基本上能够不考虑这个规则,为何?维护工做基本上都是作扩展开发,修复行为,经过一个继承关系,覆写一个方法就能够修正一个很大的Bug,何须再要去继承最高的基类呢?
-- 尽可能不要覆写基类的方法:若是基类是一个抽象类,并且这个方法已经实现了,子类尽可能不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生必定的影响。
-- 结合里氏替换原则使用:里氏替换原则要求父类出现的地方子类就能出现,再依赖倒置原则,咱们能够得出这样一个通俗的规则: 接口负责定义public属性和方法,而且声明与其余对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
·关于接口和抽象类:实际开发中90%的状况使用接口,由于其简洁、灵活。而抽象类只在既起约束做用又须要复用代码时才使用。
·文档:
-- 《Spring IoC有什么好处呢?》:https://www.zhihu.com/question/23277575/answer/169698662
-- 《依赖倒置原则》:https://www.cnblogs.com/cbf4life/archive/2009/12/15/1624435.html
-- 《小话设计模式原则之:依赖倒置原则DIP》:https://zhuanlan.zhihu.com/p/24175489编程
*单一职责原则:
·定义:一个类只负责一个功能领域中的相应职责,或者就一个类而言,应该只有一个引发它变化的缘由。
·解释:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,并且一个类承担的职责过多,就至关于将这些职责耦合在一块儿,当其中一个职责变化时,可能会影响其余职责的运做,所以要将这些职责进行分离,将不一样的职责封装在不一样的类中,即将不一样的变化缘由封装在不一样的类中,若是多个职责老是同时发生改变则可将它们封装在同一类中。
*接口隔离原则:
·定义:一个类对另外一个类的依赖应该创建在最小的接口上,即要为各个类创建它们须要的专用接口,而不要试图去创建一个很庞大的接口供全部依赖它的类去调用。
·解释:
-- 接口尽可能小,可是要有限度。一个接口只服务于一个子模块或业务逻辑。
-- 为依赖接口的类定制服务。只提供调用者须要的方法,屏蔽不须要的方法。
-- 了解环境,拒绝盲从。每一个项目或产品都有选定的环境因素,环境不一样,接口拆分的标准就不一样深刻了解业务逻辑。
-- 提升内聚,减小对外交互。使接口用最少的方法去完成最多的事情。
*迪米特法则:
·定义:一个软件实体应当尽量少地与其余实体发生相互做用。
·解释:
-- 保证类之间单向依赖(jacksplwxy)
-- 从依赖者的角度来讲,只依赖应该依赖的对象。
-- 从被依赖者的角度说,只暴露应该暴露的方法。
-- 在类的划分上,应该建立弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
-- 在类的结构设计上,尽可能下降类成员的访问权限。
-- 在类的设计上,优先考虑将一个类设置成不变类。
-- 在对其余类的引用上,将引用其余对象的次数降到最低。
-- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
-- 谨慎使用序列化(Serializable)功能。
*合成复用原则:
·定义:尽可能使用对象组合,而不是继承来达到复用的目的。
·解释:
在面向对象设计中,能够经过两种方法在不一样的环境中复用已有的设计和实现,即经过组合/聚合关系或经过继承,但首先应该考虑使用组合/聚合,组合/聚合可使系统更加灵活,下降类与类之间的耦合度,一个类的变化对其余类形成的影响相对较少;其次才考虑继承,在使用继承时,须要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,下降复杂度,而滥用继承反而会增长系统构建和维护的难度以及系统的复杂度,所以须要慎重使用继承复用。
经过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,由于继承会将基类的实现细节暴露给子类,因为基类的内部细节一般对子类来讲是可见的,因此这种复用又称“白箱”复用,若是基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;并且继承只能在有限的环境中使用(如类没有声明为不能被继承)。
因为组合或聚合关系能够将已有的对象(也可称为成员对象)归入到新对象中,使之成为新对象的一部分,所以新对象能够调用已有对象的功能,这样作可使得成员对象的内部实现细节对于新对象不可见,因此这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,能够在新对象中根据实际须要有选择性地调用成员对象的操做;合成复用能够在运行时动态进行,新对象能够动态地引用与成员对象类型相同的其余对象。
*设计模式:
·建立型模式(5种):
-- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
-- 原型(Prototype)模式:将一个对象做为原型,经过对其进行复制而克隆出多个和原型相似的新实例。
-- 工厂方法(FactoryMethod)模式:定义一个用于建立产品的接口,由子类决定生产什么产品。
-- 抽象工厂(AbstractFactory)模式:提供一个建立产品族的接口,其每一个子类能够生产一系列相关的产品。
-- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,而后根据不一样须要分别建立它们,最后构建成该复杂对象。
·结构型模式(7种):
-- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端经过代理间接地访问该对象,从而限制、加强或修改该对象的一些特性。
-- 适配器(Adapter)模式:将一个类的接口转换成客户但愿的另一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类能一块儿工做。
-- 桥接(Bridge)模式:将抽象与实现分离,使它们能够独立变化。它是用组合关系代替继承关系来实现的,从而下降了抽象和实现这两个可变维度的耦合度。
-- 装饰(Decorator)模式:动态地给对象增长一些职责,即增长其额外的功能。
-- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
-- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
-- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具备一致的访问性。
·行为型模式(11种):
-- 模板方法(Template Method)模式:定义一个操做中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在能够不改变该算法结构的状况下重定义该算法的某些特定步骤。
-- 策略(Strategy)模式:定义了一系列算法,并将每一个算法封装起来,使它们能够相互替换,且算法的改变不会影响使用算法的客户。
-- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
-- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。经过这种方式去除对象之间的耦合。
-- 状态(State)模式:容许一个对象在其内部状态发生改变时改变其行为能力。
-- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其余多个对象,从而影响其余对象的行为。
-- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,下降系统中对象间的耦合度,使原有对象之间没必要相互了解。
-- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
-- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每一个元素提供多种访问方式,即每一个元素有多个访问者对象访问。
-- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便之后恢复它。
-- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
*主要参考文献:·《Java设计模式:23种设计模式全面解析》,http://c.biancheng.net/design_pattern/·《设计模式|菜鸟教程》,http://www.runoob.com/design-pattern/design-pattern-tutorial.html·《刘伟技术博客》,https://blog.csdn.net/lovelion·Carson_Ho,《设计模式》,https://blog.csdn.net/carson_ho/column/info/14783·刘伟,《设计模式Java版》,https://gof.quanke.name/·曾探,《JavaScript设计模式与开发实践》,北京:人民邮电出版社,2015.5·Addy Osmani,《JavaScript设计模式》,徐涛 译,北京:人民邮电出版社,2013.6·codeTao,《23种设计模式全解析》,http://www.cnblogs.com/geek6/p/3951677.html·me115,《图说设计模式》,https://design-patterns.readthedocs.io/zh_CN/latest/index.html·vczh,《为何咱们须要学习(设计)模式》,https://zhuanlan.zhihu.com/p/19835717·杨博,《代码耦合是怎么回事呢?》,https://www.zhihu.com/question/21386172/answer/54476702·知乎问题,《为什么大量设计模式在动态语言中不适用》,https://www.zhihu.com/question/63734103·Mingqi,《Spring IoC有什么好处呢?》,https://www.zhihu.com/question/23277575/answer/169698662·阮一峰,《ECMAScript 6 入门》,http://es6.ruanyifeng.com/·萧萧,《设计模式在实际开发中用的多吗》,https://www.zhihu.com/question/29477933/answer/586378235·DD菜,《浅析Typescript设计模式》,https://zhuanlan.zhihu.com/p/43283016·semlinker,https://github.com/semlinker/typescript-design-patterns