💠

💠 2024-08-09 15:23:34


软件设计的一些原则

tdd-training

思维原则

奥卡姆剃刀原理

首要原则

不要复制粘贴

DRY Don’t Repeat Yourself

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

减法优于加法

1
2
3
4
5
Keep It Simple & Stupid
Keep It Slim & Sexy
Keep It Short & Safe
Keep It Silly & Sound
Keep It Simple, Spiritually

KISS原则在设计上可能最被推崇的,在家装设计,界面设计,操作设计上,复杂的东西越来越被众人所BS了,而简单的东西越来越被人所认可

“宜家”(IKEA)简约、效率的家居设计、生产思路;“微软”(Microsoft)“所见即所得”的理念;“谷歌”(Google)简约、直接的商业风格,无一例外的遵循了“kiss”原则,

也正是“kiss”原则,成就了这些看似神奇的商业经典。而苹果公司的iPhone/iPad将这个原则实践到了极至。

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

抽象优于实现

Program to an interface, not an implementation

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

多思考,把业务归类,提取出规律:包括哪些是变化的,哪些是不会变化的。通过对这些内容的抽象的出实现流程,不变的内容就是代码框架, 变化的内容可以封装成接口

组合优于继承

Composition over inheritance

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

组合

  1. (对象)组合是一种通过创建一个组合了其它对象的对象,从而获得新功能的复用方法。
  2. 将功能委托给所组合的一个对象,从而获得新功能。
  3. 有些时候也称之为"聚合"(aggregation)或"包容"(containment),尽管有些作者对这些术语赋予了专门的含义

参考: 组合、委托与继承,面向对象中类之间的基本关系漫游 参考: 优先使用(对象)组合,而非(类)继承

查询与命令分离

CQS: Command-Query Separation

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

够用原则

YAGNI You Ain’t Gonna Need It

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

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

一般来说这是面向对象的五大设计原则, Solid(稳定的)

Single Responsibility Principle 单一职责原则
Open Closed Principle 开闭原则
Liskov Substitution Principle 里氏替换
Law of Demeter 迪米特法则
Interface Segregation Preciple 接口隔离原则
Dependence Inversion Principle 依赖倒置原则

单一职责原则

Single Responsibility Principle (SRP)

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

里氏代换原则

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协议。这个原则实在是太重要了,社会的分工化,标准化都是这个设计原则的体现。

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

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

迪米特法则

Law of Demeter

又称 “最少知识原则” (Principle of Least Knowledge)其来源于1987年荷兰大学的一个叫做Demeter的项目。

  • Craig Larman把Law of Demeter又称作“不要和陌生人说话”。在《程序员修炼之道》中讲LoD的那一章叫作“解耦合与迪米特法则”。
  • 关于迪米特法则有一些很形象的比喻:
    • 如果你想让你的狗跑的话,你会对狗狗说还是对四条狗腿说?
    • 如果你去店里买东西,你会把钱交给店员,还是会把钱包交给店员让他自己拿?

正式表述如下:

  • 对于对象 ‘O’ 中一个方法’M’,M应该只能够访问以下对象中的方法:
    • 对象O;
    • 与O直接相关的 Component Object;
    • 由方法 M 创建或者实例化的对象;
    • 作为方法 M 的参数的对象。

参考: 迪米特法则与重构

开闭原则

Open/Closed Principle (OCP)

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

为何使用

  1. 开闭原则对单元测试的影响, 如果一个模块不具备开闭原则, 那么在改动该模块的时候对应的单元测试就要重写了, 当然 没有单元测试就另说
  2. 开闭原则可以提高复用性
  3. 开闭原则可以提高可维护性

如何使用

  1. 抽象约束
  2. 元数据控制模块行为
  3. 制定项目章程
  4. 封装变化

衍生原则

共同封闭原则

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.

在好莱坞,演员和演艺公司建立合约后,只能在家等待,由演艺公司对整个娱乐项目完全控制,演员只能被动式的差使,在需要的环节完成自己的演出。
也就是说,所有的组件都是被动的,所有的组件初始化和调用都由容器负责。组件处在一个容器当中,由容器负责管理。

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

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

控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。
好莱坞原则就是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的核心思想是对软件系统中的元素之间相互合作以及“责任”与“义务”的比喻。这种比喻从商业活动中“客户”与“供应商”达成“契约”而得来。例如:

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

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

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

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

  1. 它期望的是什么?
  2. 它要保证的是什么?
  3. 它要保持的是什么?

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

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

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

换句话说,通过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。因此继承类不得要求用户提供比基类方法要求的更强的前提条件,亦即,继承类方法必须接受任何基类方法能接受的任何条件(参数)。

同样,继承类必须顺从基类的所有后续条件,亦即,继承类方法的行为和输出不得违反由基类建立起来的任何约束,不能让用户对继承类方法的输出感到困惑。

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

无环依赖原则

Acyclic Dependencies Principle (ADP)

包之间的依赖结构必须是一个直接的无环图形,也就是说,在依赖结构中不允许出现环(循环依赖)。
无环依赖原则(ADP)为我们解决包之间的关系耦合问题。在设计模块时,不能有循环依赖。

如果包的依赖形成了环状结构,怎么样打破这种循环依赖呢? 有2种方法可以打破这种循环依赖关系:

  1. 第一种方法是创建新的包,如果 A->B->C->A 形成环路依赖,那么把这些共同类抽出来放在一个新的包D里。(->表示依赖)
    • 这样就把C->A变成了C->D以及A->D,从而打破了循环依赖关系。
  2. 第二种方法是使用DIP(依赖倒置原则)和ISP(接口分隔原则)设计原则。

设计模式

较为全面的教程: java design patterns | 菜鸟教程: 设计模式简介 | 《软件秘笈:设计模式那点事》

java-design-patterns

书籍:

  • 《设计模式之禅 第二版》
  • 《软件秘笈:设计模式那点事》

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

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

如何正确地使用设计模式?
设计模式有何不妥,所谓的荼毒体现在哪?

  • 如果你用的语言能把类型像变量一样赋值并传来传去,很多创建型模式就没用了。
  • 如果你用的语言能把函数像变量一样赋值并传来传去,很多行为模式就没用了。
  • 如果你用的语言 style 反对叠床架屋的 class hierarchy,很多结构模式就没用了。

模式和重构之间存在着天然联系,模式是你想到达的目的地,而重构则是从其他地方到达这个目的地的条条道路 —— Martin Fowler《重构》

设计模式的本质是语言的不足导致的,你实际上在手工架构一些由于语言表达能力不够而产生的并不属于业务逻辑本身的逻辑。

Python中无需存在的设计模式

面向设计模式来设计了,而没有面向问题本身来设计。面向问题本身来设计的时候,要面向这个领域问题。 应该考虑的是更普遍的原则,比如Single Point Of Truth,关注点分离这些。应用的应该是人类思维的规律,穷举能解决吗,状态机呢,能不能设计成递归的。 最后实现的时候才是设计模式,而这时候如果理解了程序设计语言静态类型,基本你都感觉不到你是在使用设计模式。因为一切自然就推理到了

在设计时分离稳定的部分和容易变化的部分,对易变的部分进行必要的抽象,这种变化有可能是一个过程,也有可能是一个类型。让稳定的部分依赖于这个稳定的抽象,把该抽象对应的实现的指定推迟到适合的时候,什么时候?有可能就在你的函数体外,也有可能在类外,还有可能在配置文件中。必须要结合当前的业务情景判断这样的指定位置是否稳定。而那些容易变化的部分应该作为一个一个的“输入”注入到稳定的模块中,呈现出各自不一样的形态。

变化,稳定,延迟和抽象。理解了这些真正的含义,再看23种设计模式你一定会发出惊叹,代码的可复用性和可拓展性这些东西根本就不需要特别关注,按照我的方法论,你会自然而然的发现自己的代码是高度可复用的。这样的设计可以快速的去匹配不断出现的变化。当你预测到没有变化时,如果再用设计模式就是愚蠢的行为了。因为它本身的目的只是依赖分离。

设计模式往往是前人在编程方面总是遇到类似问题,后来总结出来的一套方法论,没有人可以说这套方法论是绝对的对或者是绝对的错误。 他的对错都是相对的,可能跟具体场景有关,可以是与其他方法论相比来说更显优势。其实我倒是觉得把“设计模式”叫做“方法论”或者“编程思想”更贴切一点。

编程人员成长之路的例子:

第一阶段:刚刚入行不久,之前总是被某种(或者说某几类)问题困扰。后来通过学习了解到有一种设计模式可以解决这个问题,于是觉得设计模式这个东西很牛逼,并且认真学习各种设计模式,果然在编程方面有了进步,好多之前的问题都迎刃而解。随着你的成长,了解到有时候设计模式之间的搭配使用会更得心应手。好多问题通过单一模式解决不了,可以与其他模式配合。这个时候的状态在是在第一阶段的最佳时期,但还处于使用设计模式解决问题的阶段。

第二阶段:过了一段时间,要自己设计一个单独的相对复杂的模块,因为之前总是遇到问题解决问题,现在忽然感觉没有的入口,之前学习了那么模式完全不知道从何入手。后来经过高人指点,终于找到了一种设计模式作为这个复杂模块的主题编程思想,然后漂亮的完成这个任务。这个时候意识到当设计一个架构最主要的不是要了解多少设计模式,更多的是对问题的理解、抽象能力,能够在多种方案中找到适合当前需求的方案,并且如何与其他方案搭配。

第三阶段:慢慢的开始意识这一路走来,自己在编程思想上可能有了一些提高,但是总是有头疼的问题,或想不出的设计方案。感觉所有的模式都是前人设计出来的,正如我前面说到的他是前人针对自己遇到的这一类问题作的一套比较通用的方案,或许这个方案可以完美解决他的问题,但是问题总是不同的。开始意识到其实没有什么设计模式可言,能优雅解决问题与设计模块才是王道。

  • 其实所有的编程思想,如工厂模式、装饰器、交换机等等它们都是跟现实生活有相对应的内容。前人就是发现了这种相似的地方,所以构想出来了能够解决自己问题的方法论。当有一个你在生活中看到一种什么现象,然后把他的思想编写成计算机语言,这也属于你自己的设计模式。
  • 再说到过度设计,其实过度设计也没有一个明显的界定。随心吧,自己如果把问题理解深刻了以后,感觉没有就是没有吧。

前面说了“是否精通设计模式”这个问题在我看来总是模棱两可的,为什么呢? 精通不在于所有别人想出来的方法论你都记在脑子里面了,更多的是说所有问题都能通过这些方法论迎刃而解,这怎么可能呢?还有很多复杂的设计方案或让人头疼的问题解决方案是自己“悟”到的,用自己的理解、提炼并且映射到编程语言中的。 所有对于以后会遇到什么问题,谁又能清楚呢?感觉有点像“悟道”,只有参透万物才能所有问题迎刃而解。


设计模式概览

创建型设计模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 关键字直接实例化对象。
这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

单例模式(Singleton)

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

原型模式(Prototype)

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

建造者(Builder)

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

抽象工厂(Abstract Factory)

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

工厂方法(Factory Method)

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

结构型设计模式

这些设计模式关注类和对象的组合。结构型模式采用继承机制来组合接口或实现, 使对象获得新功能的方式。
结构型设计模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能.

适配器模式(Adapter)

  • 原有接口无法适应当前需要,加一个适配器来做兼容

桥接模式(Bridge)

  • 将对象的抽象和实现分离,从而可以独立的改变他们。

组合模式(Composite)

  • 它将对象组合成树形结构以表示“部分-整体”

代理模式(Proxy)

  • 倾向于从一个代理对象调用真实对象,但是实现上和装饰器模式一样,但是目的不同

享元模式(Flyweight)

  • 该模式为共享对象定义了一个结构,强调对象的空间效率,自由共享

外观模式(Facade)

  • 描述了如何用单个对象表示整个子系统(外部与其内部通信必须通过一个统一的对象进行交互),模式中的facade用来表示一组对象,外观设计模式提供一个高层次的接口是的子系统易于使用。

装饰模式(Decorator)

  • 描述如何动态地为对象添加职责,模式采用递归方式组合对象,从而允许添加任意多的对象职责。

行为设计模式

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

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


设计模式详述

工厂模式

抽象工厂模式

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

单例模式

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

建造者模式

使用场景: Protobuf消息类的创建都是使用的建造者模式

原型模式

struts2 就是采用该模式

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


策略模式

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

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

优点:灵活添加同一问题的不同解决方案

中介者模式

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

观察者模式

一个目标物件管理相依于它的管理物件,并且在它本身的状态发生改变时发出通知,这种模式常用来实现事件处理系统。(也称发布-订阅,模型-视图,源-收听者模式)

  • 观察者(接口):更新信息,展示信息,给 被观察者(形参) 注册上观察者
  • 被观察者(接口):发出更新通知(遍历观察者集合并注册),当自身发生改动时发出通知消息

状态模式

允许对象在内部状态时变更其行为,并且修改其类:

  • 环境类(Context):定义客户感兴趣的接口,维护一个子类的实例,这个实例就是当前状态

  • 抽象状态类(State):定义一个接口以封装与Context的一个特定状态相关的行为

  • 具体状态类(concreteState):每一子类实现与Context的一个状态相关的行为

  • 例题:纸巾售卖机:有四个状态!

    • 【状态图】
    • 【类图】
  • 例题:TCP连接状态:

命令模式

  • 行为请求者 与 请求实现者 之间 紧耦合 的关系
  • 将一个请求封装成一个对象,从而可用不同的请求对客户进行参数化,支持可撤销的操作
  • 下例:使用了接口来实现多态,子类是多个的,方法同名并功能多样的
    • 代码复用好,代码结构清晰【参数类表最好不要出现标志变量,最好分离出另一个方法】


适配器模式

适配器是的一个接口与其他接口兼容,从而给出了多个不同接口的同一抽象。一般分类结构和对象结构两种:

  • 类适配器:适配器类继承被适配类,实现某个接口,在客户端类中可以根据需求来建立子类
  • 对象适配器:适配器不是继承,是使用直接关联,或称委托方式

桥接模式

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


实践

经验之谈

反模式

  • 末日金字塔: if while 代码块多层嵌套
  • 继承关系超过3层
  • 父类和子类同名成员属性