目录 start

目录 end |2018-06-14| 码云 | CSDN | OSChina


设计模式之禅

软件设计的一些原则

思维原则

奥卡姆剃刀原理

首要原则

勿重复造轮子

-DRY Don’t Repeat Yourself 是一个最简单的法则,也是最容易被理解的。但它也可能是最难被应用的(因为要做到这样,我们需要在泛型设计上做相当的努力,这并不是一件容易的事)。它意味着,当我们在两个或多个地方的时候发现一些相似的代码的时候,我们需要把他们的共性抽象出来形一个唯一的新方法,并且改变现有的地方的代码让他们以一些合适的参数调用这个新的方法。

减法优于加法

  • KISS Keep It Simple, Stupid KISS原则在设计上可能最被推崇的,在家装设计,界面设计 ,操作设计上,复杂的东西越来越被众人所BS了,而简单的东西越来越被人所认可,比如这些UI的设计和我们中国网页(尤其是新浪的网页)者是负面的例子。 “宜家”(IKEA)简约、效率的家居设计、生产思路;“微软”(Microsoft)“所见即所得”的理念;“谷歌”(Google)简约、直接的商业风格,无一例外的遵循了“kiss”原则, 也正是“kiss”原则,成就了这些看似神奇的商业经典。而苹果公司的iPhone/iPad将这个原则实践到了极至。

把一个事情搞复杂是一件简单的事,但要把一个复杂的事变简单,这是一件复杂的事。

抽象优于实现

-Program to an interface, not an implementation

  • 这是设计模式中最根本的哲学,注重接口,而不是实现,依赖接口,而不是实现。接口是抽象是稳定的,实现则是多种多样的。
  • 以后面我们会面向对象的SOLID原则中会提到我们的依赖倒置原则,就是这个原则的的另一种样子。

组合优于继承

【Composition over inheritance】

  • 多使用组合而不是继承, 但是这个观点是存在一定的争议的, 还是要有度的,合理搭配最为重要
    • 组合就是将原来继承方式中的父类放到子类作为属性? TODO 再度思考

查询与命令分离

【CQS Command-Query Separation】

  • 查询命令分离原则
    • 查询:当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质;
    • 命令:当一个方法要改变对象的状态的时候,它就具有命令的性质;

够用原则

【YAGNI You Ain’t Gonna Need It 】

  • 这个原则简而言之为——只考虑和设计必须的功能,避免过度设计。只实现目前需要的功能,在以后您需要更多功能时,可以再进行添加。
    • 如无必要,勿增复杂性。
    • 软件开发先是一场沟通博弈。

最少知识原则

【Law of Demeter – 迪米特法则】

  • 迪米特法则(Law of Demeter),又称“最少知识原则” (Principle of Least Knowledge),其来源于1987年荷兰大学的一个叫做Demeter的项目。
  • Craig Larman把Law of Demeter又称作“不要和陌生人说话”。在《程序员修炼之道》中讲LoD的那一章叫作“解耦合与迪米特法则”。
  • 关于迪米特法则有一些很形象的比喻:
    • 如果你想让你的狗跑的话,你会对狗狗说还是对四条狗腿说?
    • 如果你去店里买东西,你会把钱交给店员,还是会把钱包交给店员让他自己拿?
  • 正式表述如下:
  • 对于对象 ‘O’ 中一个方法’M',M应该只能够访问以下对象中的方法:
    • 对象O;
    • 与O直接相关的Component Object;
    • 由方法M创建或者实例化的对象;
    • 作为方法M的参数的对象。

面向对象的S.O.L.I.D 原则

  • 一般来说这是面向对象的五大设计原则,但是,我觉得这些原则可适用于所有的软件开发。

单一职责原则

Single Responsibility Principle (SRP)

  • 关于单一职责原则,其核心的思想是:一个类,只做一件事,并把这件事做好,且只有一个引起它变化的原因。
    • Unix/Linux是这一原则的完美体现者。各个程序都独立负责一个单一的事。
    • Windows是这一原则的反面示例。几乎所有的程序都交织耦合在一起。

开闭原则

Open/Closed Principle (OCP)

  • 关于开发封闭原则,其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
    • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    • 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

里氏代换原则

Liskov substitution principle (LSP)

  • 软件工程大师Robert C. Martin把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for their base types”。
  • 也就是,子类必须能够替换成它们的基类。

即:子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。另外,不应该在代码中出现if/else之类对子类类型进行判断的条件。 里氏替换原则LSP是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。

  • 这么说来,似乎有点教条化,我非常建议大家看看这个原则个两个最经典的案例——“正方形不是长方形”“鸵鸟不是鸟”
  • 通过这两个案例,你会明白《墨子小取》中说的——“娣,美人也,爱娣,非爱美人也….盗,人也;恶盗,非恶人也。”——妹妹虽然是美人,但喜欢妹妹并不代表喜欢美人。
  • 盗贼是人,但讨厌盗贼也并不代表就讨厌人类。这个原则让你考虑的不是语义上对象的间的关系,而是实际需求的环境。
  • 在很多情况下,在设计初期我们类之间的关系不是很明确,LSP则给了我们一个判断和设计类之间关系的基准:需不需要继承,以及怎样设计继承关系。

接口隔离原则

Interface Segregation Principle (ISP)

  • 接口隔离原则意思是把功能实现在接口中,而不是类中,使用多个专门的接口比使用单一的总接口要好。
  • 举个例子,我们对电脑有不同的使用方式,比如:写作,通讯,看电影,打游戏,上网,编程,计算,数据等,如果我们把这些功能都声明在电脑的抽类里面,那么,我们的上网本,PC机,服务器,
  • 笔记本的实现类都要实现所有的这些接口,这就显得太复杂了。所以,我们可以把其这些功能接口隔离开来,比如:工作学习接口,编程开发接口,上网娱乐接口,计算和数据服务接口,这样,我们的不同功能的电脑就可以有所选择地继承这些接口。
  • 这个原则可以提升我们“搭积木式”的软件开发。对于设计来说,Java中的各种Event Listener和Adapter,对于软件开发来说,不同的用户权限有不同的功能,不同的版本有不同的功能,都是这个原则的应用。

依赖倒置原则

Dependency Inversion Principle (DIP) 高层模块不应该依赖于低层模块的实现,而是依赖于高层抽象。 举个例子,墙面的开关不应该依赖于电灯的开关实现,而是应该依赖于一个抽象的开关的标准接口,这样,当我们扩展程序的时候,我们的开关同样可以控制其它不同的灯,甚至不同的电器。 也就是说,电灯和其它电器继承并实现我们的标准开关接口,而我们的开关产商就可不需要关于其要控制什么样的设备,只需要关心那个标准的开关标准。这就是依赖倒置原则。 这就好像浏览器并不依赖于后面的web服务器,其只依赖于HTTP协议。这个原则实在是太重要了,社会的分工化,标准化都是这个设计原则的体现。

下面有几点指导意见,帮助你避免在面向对象设计中违反依赖倒置原则:

变量不能持有具体类的引用,就像订单方法代码中,你看不到new一样。
不要让派生自具体类,要派生就派生抽象类abstract
不要覆盖基类中已实现的方法,除非你要覆盖的是比较特殊的一部分代码。

其他原则

共同封闭原则

Common Closure Principle(CCP) 一个包中所有的类应该对同一种类型的变化关闭。一个变化影响一个包,便影响了包中所有的类。一个更简短的说法是:一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。CCP原则就是把因为某个同样的原因而需要修改的所有类组合进一个包里。如果2个类从物理上或者从概念上联系得非常紧密,它们通常一起发生改变,那么它们应该属于同一个包。

CCP延伸了开闭原则(OCP)的“关闭”概念,当因为某个原因需要修改时,把需要修改的范围限制在一个最小范围内的包里。

共同重用原则

Common Reuse Principle (CRP) 包的所有类被一起重用。如果你重用了其中的一个类,就重用全部。换个说法是,没有被一起重用的类不应该被组合在一起。CRP原则帮助我们决定哪些类应该被放到同一个包里。依赖一个包就是依赖这个包所包含的一切。当一个包发生了改变,并发布新的版本,使用这个包的所有用户都必须在新的包环境下验证他们的工作,即使被他们使用的部分没有发生任何改变。因为如果包中包含有未被使用的类,即使用户不关心该类是否改变,但用户还是不得不升级该包并对原来的功能加以重新测试。

CCP则让系统的维护者受益。CCP让包尽可能大(CCP原则加入功能相关的类),CRP则让包尽可能小(CRP原则剔除不使用的类)。它们的出发点不一样,但不相互冲突。

好莱坞原则

Hollywood Principle 好莱坞原则就是一句话——“don’t call us, we’ll call you.”。意思是,好莱坞的经纪人们不希望你去联系他们,而是他们会在需要的时候来联系你。也就是说,所有的组件都是被动的,所有的组件初始化和调用都由容器负责。组件处在一个容器当中,由容器负责管理。

简单的来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:

不创建对象,而是描述创建对象的方式。
在代码中,对象与服务没有直接联系,而是容器负责将这些联系在一起。

控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。

好莱坞原则就是IoC(Inversion of Control)或DI(Dependency Injection)的基础原则。这个原则很像依赖倒置原则,依赖接口,而不是实例,但是这个原则要解决的是怎么把这个实例传入调用类中?你可能把其声明成成员,你可以通过构造函数,你可以通过函数参数。但是 IoC可以让你通过配置文件,一个由Service Container 读取的配置文件来产生实际配置的类。但是程序也有可能变得不易读了,程序的性能也有可能还会下降。

高内聚低耦合

【 High Cohesion & Low/Loose coupling & – 高内聚, 低耦合 】

  • 这个原则是UNIX操作系统设计的经典原则,把模块间的耦合降到最低,而努力让一个模块做到精益求精。
    • 内聚:一个模块内各个元素彼此结合的紧密程度
    • 耦合:一个软件结构内不同模块之间互连程度的度量
  • 内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身

凝聚>松耦合>重用 参考博客: 为什么我停止使用Spring?

惯例优于配置原则

Convention over Configuration(CoC) 简单点说,就是将一些公认的配置方式和信息作为内部缺省的规则来使用。例如,Hibernate的映射文件,如果约定字段名和类属性一致的话,基本上就可以不要这个配置文件了。你的应用只需要指定不convention的信息即可,从而减少了大量convention而又不得不花时间和精力啰里啰嗦的东东。配置文件很多时候相当的影响开发效率。

Rails 中很少有配置文件(但不是没有,数据库连接就是一个配置文件),Rails 的fans号称期开发效率是 java 开发的 10 倍,估计就是这个原因。Maven也使用了CoC原则,当你执行mvn -compile命令的时候,不需要指源文件放在什么地方,而编译以后的class文件放置在什么地方也没有指定,这就是CoC原则。

关注点分离

Separation of Concerns (SoC)

 是计算机科学中最重要的努力目标之一。这个原则,就是在软件开发中,通过各种手段,将问题的各个关注点分开。如果一个问题能分解为独立且较小的问题,就是相对较易解决的。 问题太过于复杂,要解决问题需要关注的点太多,而程序员的能力是有限的,不能同时关注于问题的各个方面。正如程序员的记忆力相对于计算机知识来说那么有限一样, 程序员解决问题的能力相对于要解决的问题的复杂性也是一样的非常有限。在我们分析问题的时候,如果我们把所有的东西混在一起讨论,那么就只会有一个结果——乱。

我记得在上一家公司有一个项目,讨论就讨论了1年多,项目本来不复杂,但是没有使用SoC,全部的东西混为一谈,再加上一堆程序员注入了各种不同的观点和想法,整个项目一下子就失控了。 最后,本来一个1年的项目做了3年。

实现关注点分离的方法主要有两种,一种是标准化,另一种是抽象与包装。标准化就是制定一套标准,让使用者都遵守它,将人们的行为统一起来,这样使用标准的人就不用担心别人会有很多种不同的实现,使自己的程序不能和别人的配合。 JavaEE就是一个标准的大集合。每个开发者只需要关注于标准本身和他所在做的事情就行了。就像是开发镙丝钉的人只专注于开发镙丝钉就行了,而不用关注镙帽是怎么生产的,反正镙帽和镙丝钉按标来就一定能合得上。不断地把程序的某些部分抽像差包装起来,也是实现关注点分离的好方法。 一旦一个函数被抽像出来并实现了,那么使用函数的人就不用关心这个函数是如何实现的,同样的,一旦一个类被抽像并实现了,类的使用者也不用再关注于这个类的内部是如何实现的。 诸如组件,分层,面向服务,等等这些概念都是在不同的层次上做抽像和包装,以使得使用者不用关心它的内部实现细节。 说白了还是“高内聚,低耦合”。

参考博客: 理论篇:关注点分离(Separation of concerns, SoC)

契约式设计

Design by Contract (DbC) DbC的核心思想是对软件系统中的元素之间相互合作以及“责任”与“义务”的比喻。这种比喻从商业活动中“客户”与“供应商”达成“契约”而得来。例如:

供应商必须提供某种产品(责任),并且他有权期望客户已经付款(权利)。
客户必须付款(责任),并且有权得到产品(权利)。
契约双方必须履行那些对所有契约都有效的责任,如法律和规定等。

同样的,如果在程序设计中一个模块提供了某种功能,那么它要:

期望所有调用它的客户模块都保证一定的进入条件:这就是模块的先验条件(客户的义务和供应商的权利,这样它就不用去处理不满足先验条件的情况)。
保证退出时给出特定的属性:这就是模块的后验条件——(供应商的义务,显然也是客户的权利)。
在进入时假定,并在退出时保持一些特定的属性:不变式。

契约就是这些权利和义务的正式形式。我们可以用“三个问题”来总结DbC,并且作为设计者要经常问:

它期望的是什么?
它要保证的是什么?
它要保持的是什么?

根据Bertrand Meyer氏提出的DBC概念的描述,对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;同时后续条件用来说明这个方法完成时的状态,如果一个方法的执行会导致这个方法的后续条件不成立,那么这个方法也不应该正常返回。

现在把前提条件以及后续条件应用到继承子类中,子类方法应该满足:

前提条件不强于基类.
后续条件不弱于基类.

换句话说,通过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。因此继承类不得要求用户提供比基类方法要求的更强的前提条件,亦即,继承类方法必须接受任何基类方法能接受的任何条件(参数)。同样,继承类必须顺从基类的所有后续条件,亦即,继承类方法的行为和输出不得违反由基类建立起来的任何约束,不能让用户对继承类方法的输出感到困惑。

这样,我们就有了基于契约的LSP,基于契约的LSP是LSP的一种强化。

无环依赖原则

Acyclic Dependencies Principle (ADP) 包之间的依赖结构必须是一个直接的无环图形,也就是说,在依赖结构中不允许出现环(循环依赖)。如果包的依赖形成了环状结构,怎么样打破这种循环依赖呢?有2种方法可以打破这种循环依赖关系:第一种方法是创建新的包,如果A、B、C形成环路依赖,那么把这些共同类抽出来放在一个新的包D里。这样就把C依赖A变成了C依赖D以及A依赖D,从而打破了循环依赖关系。第二种方法是使用DIP(依赖倒置原则)和ISP(接口分隔原则)设计原则。

无环依赖原则(ADP)为我们解决包之间的关系耦合问题。在设计模块时,不能有循环依赖。


设计模式

基础

设计模式基础
参考博客: GoF解释
参考博客: 设计模式专栏

23种经典设计模式UML类图汇总
参考博客: 23种设计模式UML表示形式
参考博客: 23中设计模式类图和原理详解 参考博客: 23种设计模式类图总结

策略者模式

定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的用户。
参考博客: 设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 参考博客: Java消除ifelse

也就是说将一种需求的多种实现算法分别封装起来, 然后利用多态, 让调用方选择任一实现

1.创建型设计模式

单例模式(Singleton)
原型模式(Prototype)
建造者(Builder)
抽象工厂(Abstract Factory)
工厂方法(Factory Method)

  • 抽象了实例化过程,它们帮助一个系统独立于如何创建,组合和表示它的那些对象。
  • 一个类创建型模型使用继承改变被实例化的类,而一个对象创建型模型将实例化委托给另一个对象
  • 将一组固定行为的硬编码转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂性的行为。这样创建有特定行为的对象要求的不仅仅是实例化一个类

Builder

  • 生成器 Builder,是一种对象构建模式,模式通常包含Builder,ConcreteBuilder。Director 和 Product四部分

Abstract Factory

  • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

Factory Method

  • 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类

Prototype

  • 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

Singleton

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.结构型设计模式

适配器模式(Adapter)
桥接模式(Bridge)
组合模式(Compontent)
代理模式(Proxy)
享元模式(Flyweight)
外观模式(Facade)
装饰模式(Decorator)

  • 结构型设计模式涉及如何组合类和对象以获得更大的结构
  • 结构型模式采用继承机制来组合接口或实现。
  • 结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能

  • Composite模式 它将对象组合成树形结构以表示“部分-整体”
  • Flyweight模式 该模式为共享对象定义了一个结构,强调对象的空间效率,自由共享
  • Facade模式(外观模式) 描述了如何用单个对象表示整个子系统(外部与其内部通信必须通过一个统一的对象进行交互),模式中的facade用来表示一组对象, 外观设计模式提供一个高层次的接口是的子系统易于使用。 适用情况:

1.为复杂的子系统提供一个简单的接口 2.客户程序与抽象类的实现部分有很大依赖性 3.构建一个层次结构的子系统时,适用外观模式定义子系统每层的入口

  • Bridge模式 将对象的抽象和实现分离,从而可以独立的改变他们。
  • Decorator模式 描述如何动态地为对象添加职责,模式采用递归方式组合对象,从而允许添加任意多的对象职责。

3.行为设计模式

策略模式(Strategy)
命令模式(Command)
状态模式(State)
解释器模式(Interpreter)
模板方法(Template Method)
责任链模式(Chain of Responsibility)
迭代器模式(Iterator)
中介者模式(Mediator)
备忘录模式(Memento)
观察者模式(Observe)
访问者模式(Visitor)

  • 行为设计模式涉及算法和对象间职责的分配,行为模式描述对象和类的模式以及其通信模式
  • 行为模式使用继承机制在类间派发行为

【常见设计模式】

适配器模式

  • 适配器 模式(Adapter):适配器是的一个接口与其他接口兼容,从而给出了多个不同接口的同一抽象。一般分类结构和对象结构两种:
  • 类适配器:适配器类继承被适配类,实现某个接口,在客户端类中可以根据需求来建立子类
  • 对象适配器:适配器不是继承,是使用直接关联,或称委托方式

中介者模式

  • 中介者 模式:包装了一系列对象相互作用的方式,使得对象间的相互作用是独立变化,但是与中介者紧密联系

观察者模式

  • 观察者 模式 Observer:一个目标物件管理相依于它的管理物件,并且在它本身的状态发生改变时发出通知,这种模式常用来实现事件处理系统。(也称发布-订阅,模型-视图,源-收听者模式)
    • 观察者(接口):更新信息,展示信息,给 被观察者(形参) 注册上观察者
    • 被观察者(接口):发出更新通知(遍历观察者集合并注册),当自身发生改动时发出通知消息

单例模式

Singleton 一个类只有一个实例易于外界访问 Spring将该模式运用的出神入化

参考博客: 单例模式和Static的区别!

  •  static 有可能被实例化多个出来么
装饰器模式
  • 装饰器模式 创建一个新类为某一个类动态添加新功能或增强原有的功能,避免代码重复或具体子类的数量增加

  • 策略模式 优点:灵活添加同一问题的不同解决方案
  • 状态模式 允许对象在内部状态时变更其行为,并且修改其类:
    • 环境类(Context):定义客户感兴趣的接口,维护一个子类的实例,这个实例就是当前状态
    • 抽象状态类(State):定义一个接口以封装与Context的一个特定状态相关的行为
    • 具体状态类(concreteState):每一子类实现与Context的一个状态相关的行为
    • 例题:纸巾售卖机:有四个状态!
      • 【状态图】
      • 【类图】
    • 例题:TCP连接状态:
  • 命令模式 command
    • 行为请求者 与 请求实现者 之间 紧耦合 的关系
    • 将一个请求封装成一个对象,从而可用不同的请求对客户进行参数化,支持可撤销的操作
    • 下例:使用了接口来实现多态,子类是多个的,方法同名并功能多样的
      • 代码复用好,代码结构清晰【参数类表最好不要出现标志变量,最好分离出另一个方法】

  • 桥接模式 : 便于扩展,实现与抽象分离(解耦)对一个模块修改不影响别的模块

  • 抽象工厂模式 : 提供一个创建一系列相关实例相互依赖的对象。
    • 当一个系统要独立于它的产品的创建,组合和表示时
    • 当一个系统要由多个产品系列中的一个来配置时
    • 当需强调一系列相关的产品对象的设计以便进行联合使用时
    • 想提供一组对象而不显示他们的实现过程,只显示他们的接口

原型模式

struts2 就是采用该模式

  • 原型模式 : 对象创建模型: 允许一个对象创建另一个可定制的对象,封装实例化细节。
    • 实现Cloneable接口(Java自带接口),重写clone方法(在这里实例化对象,new或反射,按需求来修改)
    • 该例,组合关系,在对方使用clone来代替构造器来实例化对象,并做好了绑定操作,大量减少代码量

生成器模式

  • 生成器模式

实践

经验之谈


书籍推荐