第二章 面向对象设计原则
软件的可维护性和可复用性
- 软件的复用(Reuse)或重用拥有众多优点,恰当的复用还可以改善系统的可维护性。
- 复用的目标在于实现支持可维护性的复用
- 可维护性复用都是以面向对象设计原则为基础的
重构(Refactoring)是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
面向对象设计原则
-
单一职责原则(Single Responsibility Principle, SRP)
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
单一职责原则是实现高内聚、低耦合的指导方针
-
开闭原则(Open-Closed Principle, OCP)
一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
抽象化是开闭原则的关键。
-
里氏代换原则(Liskov Substitution Principle, LSP)
所有引用基类(父类)的地方必须能透明地使用其子类的对象。也就是说在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常。
-
依赖倒转原则(Dependence Inversion Principle, DIP)
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。即要针对接口编程,不要针对实现编程。
如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段
依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中
依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键
-
接口隔离原则(Interface Segregation Principle, ISP)
客户端不应该依赖那些它不需要的接口。接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干
-
合成复用原则(Composite Reuse Principle, CRP)又称为组合/聚合复用原则(Composition/ Aggregate Reuse Principle, CARP)
尽量使用对象组合,而不是继承来达到复用的目的
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用
-
迪米特法则(Law of Demeter, LoD)又称为最少知识原则(Least Knowledge Principle, LKP)
不要和“陌生人”说话;只与你的直接朋友通信;每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
简单地说,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。
迪米特法则的主要用途在于控制信息的过载。在类的划分上,应当尽量创建松耦合的类;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低
第三章 设计模式概述
模式是在特定环境中解决问题的一种方案
软件模式是软件开发的总体指导思路或参照样板,是对软件开发这一特定“问题”的“解法”的某种统一表示
软件模式的基础结构由4个部分构成:问题描述、前提条件(环境或约束条件)、解法和效果。
设计模式
是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结
设计模式一般有如下几个基本要素:模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式(加粗的为关键元素)
根据其目的(模式是用来做什么的)可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种
创建型模式主要用于创建对象;结构型模式主要用于处理类或对象的组合;行为型模式主要用于描述对类或对象怎样交互和怎样分配职责
根据范围,即模式主要是用于处理类之间关系还是处理对象之间的关系,可分为类模式和对象模式两种
设计模式的优点
- 融合了众多专家的经验,以一种标准的形式供开发人员所用,提供了一套通用的设计词汇和一种通用的语言以方便开发人员之间沟通和交流,设计模式可以降低开发人员理解系统的复杂度
- 使人们可以更加简单方便地复用成功的设计和体系结构
- 使得设计方案更加灵活,且易于修改
- 可以提高软件系统的开发效率和软件质量,且在一定程度上节约设计成本
- 有助于初学者更深入地理解面向对象思想
第四章 简单工厂模式
简单工厂模式是创建型模式
创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离
创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的
简单工厂模式(Simple Factory Pattern)又称为静态工厂方法(Static Factory Method)模式。可以根据参数的不同返回不同类的实例(即不需要知道具体类的名字,只需要知道表示需要创建对象的类的一个参数就可得到相应的类对象)。被创建的实例通常都具有共同的父类
简单工厂模式包含三个角色:工厂角色负责实现创建所有实例的内部逻辑;抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口;具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例
简单工厂模式分析
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度
- 由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可
- 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的
- 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节
简单工厂模式的优点
- 实现了对责任的分割,它提供了专门的工厂类用于创建对象
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类
简单工厂模式的缺点
- 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响
- 会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护
- 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构
简单工厂模式的适用环境
- 工厂类负责创建的对象比较少
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心
第五章 工厂方法模式
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象
工厂方法模式包含如下角色:抽象产品、具体产品、抽象工厂、具体工厂
工厂模式分析
- 在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品
- 当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体产品对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好地符合了“开闭原则”
工厂方法模式的优点
- 用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名
- 工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类
- 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了
工厂方法模式的缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度
工厂方法模式的适用环境
- 一个类不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可)
- 一个类通过其子类来指定创建哪个对象
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定
工厂方法模式的扩展
- 使用多个工厂方法:在抽象工厂角色中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同的产品对象的需求
- 产品对象的重复使用:工厂对象将已经创建过的产品保存到一个集合(如数组、List等)中,然后根据客户对产品的请求,对集合进行查询。如果有满足要求的产品对象,就直接将该产品返回客户端;如果集合中没有这样的产品对象,那么就创建一个新的满足要求的产品对象,然后将这个对象再增加到集合中,再返回给客户端
第六章 抽象工厂模式
当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
抽象工厂模式的优点
- 抽象工厂模式隔离了具体类的生成。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。应用抽象工厂模式可以实现高内聚低耦合的设计目的
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
抽象工厂模式的缺点
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品
- 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)
抽象工厂模式的适用环境
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
- 系统中有多于一个的产品族,而每次只使用其中某一产品族
- 属于同一个产品族的产品将在一起使用
- 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
第七章 建造者模式
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象
建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式属于对象创建型模式。
指挥者类Director类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程
建造者模式包含如下四个角色:抽象建造者为创建一个产品对象的各个部件指定抽象接口;具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象;产品角色是被构建的复杂对象,包含多个组成部件;指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。
建造者模式的优点
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关。用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
建造者模式的缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大
建造者模式的适用环境
- 需要生成的产品对象有复杂的内部结构
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
建造者模式与抽象工厂模式的比较
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车
第八章 原型模式
在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象
原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象
原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆
原型模式包含三个角色:抽象原型类是定义具有克隆自己的方法的接口;具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象;让一个原型克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。
原型模式的优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
- 可以动态增加或减少产品类
- 原型模式提供了简化的创建结构
- 可以使用深克隆的方式保存对象的状态
原型模式的缺点
- 需要为每一个类配备一个克隆方法
- 实现深克隆时需要编写较为复杂的代码
原型模式的适用环境
-
创建新对象成本较大。对已有对象进行复制来获得,如果是相似对象,则可以对其属性稍作修改
-
系统要保存对象的状态
-
需要避免使用分层次的工厂类来创建分层次的对象
第九章 单例模式
对于系统中的某些类来说,只有一个实例很重要
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式是一种对象创建型模式。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建
单例模式的优点
- 提供了对唯一实例的受控访问
- 由于在系统内存中只存在一个对象,因此可以节约系统资源
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
单例模式的缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
- 滥用单例将带来一些负面问题。如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出
单例模式的适用环境
- 系统只需要一个实例对象
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例
单例模式的扩展
- 饿汉式单例类:在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
- 懒汉式单例类:比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
第十章 适配器模式
结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构
结构型模式可以分为类结构型模式和对象结构型模式:
- 类结构型模式关心类的组合
- 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类
适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
适配器模式包含四个角色:目标抽象类定义客户要用的特定领域的接口;适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心;适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法
适配器模式的优点
- 将目标类和适配者类解耦
- 增加了类的透明性和复用性
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”
类适配器模式还具有如下优点
- 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强
类适配器模式的缺点如下
- 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口
对象适配器模式还具有如下优点
- 同一个适配器可以把适配者类和它的子类都适配到目标接口
对象适配器模式的缺点如下
- 与类适配器模式相比,要想置换适配者类的方法就不容易
适配器模式的适用环境
- 系统需要使用现有的类,而这些类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
适配器模式的扩展
-
默认适配器模式(Default Adapter Pattern)或缺省适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况
-
双向适配器
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器
第十一章 交接模式
桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化
桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法
桥接模式的优点
- 分离抽象接口及其实现部分
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则,桥接模式是比多继承方案更好的解决方法
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
- 实现细节对客户透明,可以对用户隐藏实现细节
桥接模式的缺点
- 桥接模式的引入会增加系统的理解与设计难度
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性
桥接模式的适用环境
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以使用
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
- 不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
第十二章 组合模式
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象
组合模式(Composite Pattern):组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性
组合模式又可以称为“整体-部分”(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理
同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构
组合模式包含三个角色:抽象构件为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现;叶子构件在组合结构中表示叶子节点对象,叶子节点没有子节点;容器构件在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为。
组合模式的优点
- 可以清楚地定义分层次的复杂对象
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象
- 定义了包含叶子对象和容器对象的类层次结构,,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构
- 更容易在组合体内加入对象构件
组合模式的缺点
- 使设计变得更加抽象,对象的业务规则如果很复杂
- 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制
组合模式的适用环境
- 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们
- 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节
- 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们
UML图
虚线表示依赖
UML的组件图用于表示系统的软件体系结构
聚集(聚合)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70nyRqHv-1577157885439)(https://s2.ax1x.com/2019/12/23/lpEGJP.png)]
一般化(泛化关系)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ken7kZzf-1577157885441)(https://s2.ax1x.com/2019/12/23/lpEnMD.png)]
三角箭头指向父类
感觉聚合组合还是有差的,组合在构造函数内部实例化,聚合在构造函数接收参数。人和手是组合应该用黑色实心菱形。因为手不能拆下给同类对象用。
协作(交互)图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dFxUZovI-1577157922090)(https://s2.ax1x.com/2019/12/23/lp0mqg.png)]