设计模式系列的观点结合了《HeadFirst设计模式》(中文版)以及《设计模式:可复用面向对象软件的基础》两本书的知识,以及Sunny(刘伟)的博客
《HeadFirst设计模式》(中文版):
百度网盘链接:https://pan.baidu.com/s/1osvnUGZZREm8JbM12LiX5A?pwd=2024
提取码:2024
《设计模式:可复用面向对象软件的基础》
百度网盘链接:https://pan.baidu.com/s/1N5Tp6zlhIrzg9Led3VD7lA?pwd=2024
提取码:2024
一、面向对象设计模式
- 按目的分为创建型(creational)、结构型(structural)和行为型(behavioural);
- 创建型模式主要用于:创建对象;
- 结构型模式主要用于:处理类或对象的组合;
- 行为型模式主要用于:描述对类或对象怎样交互和怎样分配职责。
- 按范围分为类模式和对象模式。
- 类模式是指通过定义类来描述对象的行为和属性;
- 对象模式则是指通过创建对象实例来描述对象的行为和属性。本文将分别介绍类模式和对象模式。
- 类模式强调类的继承和组合,而对象模式强调创建对象实例和对象之间的相互作用。
二、反射机制与配置文件
- Java反射机制(Java Reflection)
- Java反射(Java Reflection)是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制, 包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等
- Class类的实例表示正在运行的Java应用程序中的类和接口,其forName(String className)方法可以返回与带有给定字符串名的类或接口相关联的Class对象,再通过Class对象的newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例
- Java事件处理:事件源都充当观察目标角色,事件监听器充当抽象观察者,事件处理对象充当具体观察者角色。如果事件源对象的某个事件触发,则调用事件处理对象中的事件处理程序来对事件进行处理。
- MVC(Model-View-Controller)架构:模型视图控制器。模型可对应于观察者模式中的观察模式,而视图对应于观察者,控制器可充当两者之间的中介者。当模型层的数据发生改变时,视图层将自动改变其显示内容。
三、设计原则
两大基础设计原则
- 程序设计的原则:模块内高内聚,模块间低耦合
- 开闭原则:开闭原则是面向对象的可复用设计的第一块基石,是最重要的面向对象设计原则。(十个字概括:对扩展开放,对修改关闭),也就是避免添加修改代码。
从基础原则出发,产生六个具体的原则
六大原则的目的:1)提高软件的可维护性(maintainability):指软件能够被理解、改正、适应及扩展的难易程度;2)可复用性(Reusability):之软件能够被重复使用的难易程度;
面向对象设计的目标之一:在于支持可维护性复用,一方面需要实现设计方案或者源代码的复用、另一方面要确保系统能够易于扩展和修改,具有良好的可维护性。面向对象设计原则为支持可维护性复用而诞生指导性原则,非强制性原则。每一个设计模式都符合一个或多个面向对象设计原则,面向对象设计原则是用于评价一个设计模式的使用效果的重要指标之一。
- (1) 单一职责原则
- 单一职责原则(Single Responsibility Principle SRP ):是最简单的面向对象设计原则,用于控制类的粒度大小。(一个方法或一个类只做一件事,为了模块内高内聚)
- 定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。就一个类而言,应该仅有一个引起它变化地原因。
- 单一原则分析:
- 1.一个类(大到模块,小到方法)承担的指责越多,它被复用的可能性就越小;
- 2.当一个职责变化时,可能会影响其他职责的运作;
- 3.将这些职责进行分离,将不同的职责封装在不同的类中;
- 4.将不同的变化原因封装在不同的类中;
- 5.单一职责原则是实现高内聚,低耦合的指导方针。
- (2) 迪米特法则
- 迪米特法则定义(LOD):又叫做最少知识原则(LKP),每一个软件单位对其他的单位都只是最少的知识,而且局限于那些与本单位密切相关的软件。(也叫最少知道原则,为了模块间低耦合)
- 迪米特法则分析:要求一个软件实体应当尽可能地与其他实体发生相互作用。应用迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
- 1.迪米特法则要求在设计系统时,应该尽量减少对象之间的交互;
- 2.如果两个对象之间不必彼此直接通信,那么这两个对象就不应该发生任何直接的相互作用;
- 3.如果其中一个对象需要调用另一个对象的方法,可以通过“第三者”转发这个调用;
- 4.通过引入一个合理的“第三者”来降低现有对象之间的耦合度。
- (3) 里氏替换原则
- 定义:所有引用基类的地方必须能透明地使用其子类对象。(就是继承原则,子类可以无缝替代父类。很好的符合了开闭原则)
- 通俗概念:在一个软件系统中,子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。
- 继承的优点:
- 1.子类拥有父类所有的方法和属性,从而减少创建类的工作量;
- 2.提高了代码的重用性;
- 3.提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。
- 继承的缺点:
- 1.继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
- 2.降低了代码的灵活性。因为继承时,父类会对子类有一种约束;
- 3.增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。有时修改了一点点代码都可能需要对打断程序进行重构。
- 里氏代换原则(继承的扬长避短)LSP:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换o2时,程序P的行为没有变化,那么类型S时类型T的子类型。里氏替换原则堆积成进行了规则上的约束:
- 1.子类必须实现父类的抽象方法,但不得重写(覆盖)父类得非抽象(已实现)方法;
- 2.子类中可以增加自己特有得办法;
- 3.当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松;
- 4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
- 里氏替换原则时实现抽象化的一种规范。违反里氏替换原则意味着违反了开闭原则,反之未必
- (4) 依赖倒置原则
- 依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。其核心思想是:要面向接口编程,不要面向实现编程。(类之间的依赖通过接口实现,低耦合的同时对扩展开放)
- 依赖倒置原则(DIP)的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转原则分析:
- 1.在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数声明、方法类型声明,以及数据类型的转换等。
- 2.在程序中尽量使用抽象进行编程,而将具体类写在配置文件中。
- 3.针对抽象层编程,将具体的对象通过依赖注入(Dependency Inection,DI)的方式注入到其他对象:构造注入、设值注入(Setter注入)、接口注入。
- 依赖倒置原则的主要作用如下:
- 1.依赖倒置原则可以提高系统的稳定性;
- 2.依赖倒置原则可以减少并行开发引起的风险;
- 3.依赖倒置原则可以提高代码的可读性和可维护性;
- 4.依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
- 1.每个类尽量提供接口或抽象类,或者两者都具备;
- 2.变量的声明类型尽量是接口或者是抽象类;
- 3.任何类都不应该从具体类派生;
- 4.使用继承时尽量遵循里氏替换原则。
- (5) 接口隔离
- 接口的定义1:一个类型所提供的所有方法特征的集合,一个接口代表一个角色,每个角色都有它特定的一个接口,“角色隔离原则”。(即把单个复杂接口拆分为多个独立接口,与上条共同实现面向接口编程)
- 接口的定义2:狭义的特定语言的接口。接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独接口,而不是提供大的总接口,每个接口中只包含一个客户端所需的方法,“定制服务”。
- 接口隔离原则:客户端不应该依赖那些它不需要的接口(ISP)。类间的依赖关系应该建立在最小的接口上,它要求是最小的接口,也是要求接口细化,接口纯洁。
- 接口隔离原则分析:
- 1.当一个街头太大时,需要将它分割成一些更细小的接口;
- 2.使用该接口的客户端仅需知道与之相关的方法即可;
- 3.每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
- 采用接口隔离原则约束接口注意事项:
- 1.接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度;
- 2.为依赖接口的类指定服务,只暴露给调用的类他需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系;
- 3.提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情;
- 接口隔离原则与单一职责原则比较:类和接口职责单一,注重的使职责,是业务逻辑的划分。而接口隔离原则需要接口的方法尽量少。
- (6) 合成复用原则
- 合成复用原则定义:又叫组合/聚合复用原则CARP/CRP,优先使用对象组合,而不是继承来达到复用的目的。(即尽量使用合成/聚合的方式,而不是使用继承。主要为了防止继承滥用而导致的类之间耦合严重。记住只有符合继承原则时才用继承)
- 合成复用原则分析:
- 1.合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;
- 2.新对象通过委派调用已有对象的方法达到复用功能的目的;
- 3.复用时要尽量使用组合/聚合关系(关联关系),少用继承。
- 继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用)
- 组合/聚合复用:耦合度相对较低,有选择性地调用成员对象的操作;可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。(“黑箱”复用)
四、UML
UML中类之间的关系:根据类与类之间的耦合度从弱到强排列,UML中的类图有以下几种关系:依赖、关联、聚合、组合、泛化、实现关系。其中泛化和实现的耦合度相等,他们是最强的。(各种关系的强弱顺序:泛化=实现>组合>聚合>关联>依赖)
- 1.依赖关系(Dependency):是一种使用关系,即一个类的实现需要另一个类的协助,所以要尽量不使用双向的互相依赖。
- 代码实现:局部变量、方法的参数或者对静态方法的调用
- 箭头及指向:带箭头的虚线,指向被使用者。
- 2.关联关系(association):是对象之间的一种引用关系,用于表示一类知道另一个类的属性和方法。对象与另一类对象之间的联系。使一个类关联关系西是类与类之间常用的一种关系,分为一般关联关系、聚合关系和组合关系。比如:老师与学生,丈夫与妻子。
- 代码实现:成员变量。
- 箭头及指向:带箭头的实心线,如果是双向关联就是实心线。
- 3.聚合(Aggregation)关系:使整体与部分的关系,且部分可以离开整体而单独存在。has-a的关系。
- 聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。
- 代码实现:成员变量
- 箭头及指向:带空心菱形的实心线,菱形指向整体。
- 4.组合(compositon)关系:是整体与部分的关系但部分不能离开整体而单独存在。
- 代码实现:成员变量
- 箭头及指向:带实心菱形的实线,菱形指向整体。
- 组合关系和聚合关系比较:
- 1.聚合与组合都是一种结合关系,具有整体-部分的意义。
- 2.部件的生命周期不同。(聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时间共享一个部件。)
- 3.聚合关系是“has-a”关系,组合关系是"contains-a"关系。
- 关联和依赖:
- 1.关联关系中,体现的是两个类、或者类与接口语义级别的一种强依赖关系,比如我和我的朋友;这个关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的们一般是长期性的,而且双方的关系一般是平等的。
- 2.依赖关系中,可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。
- 5.泛化(Generalization)关系:是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是is-a的关系
- 箭头及指向:带空心三角箭头的实线来表示,箭头从子类指向父类。
- 6.实现(Realization)关系:是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
- 箭头及指向:带空心三角箭头的虚线来表示,箭头从实现类指向接口。
如何利用Eclipse画UML?--->>>需要安装AmaterasUML插件。
1.导航栏点击帮助(help),选择Install New Software安装新软件
2.输网址:https://takezoe.github.io/amateras-update-site/,点击添加(Add)
3.点击全部选中(SelectAll)全选,一直点Next和接受
注:下载失败的话,可能与网络环境有关,多试几次
4.下载成功后重启Eclipse,就可以画类图了
5.选择想要生成UML图类,选择文件->>>新建(new)->>>选择其他(Other)找到Class Diagram新建,然后就把想要画的类拖入即可
五、设计模式
5.1 行为型模式(即方法及其调用关系)
- Observer:观察者模式(用订阅-发布实现的被观察者变化时回调)
- Strategy:策略模式(提供功能不同实现方式,且实现可选)
- Command:命令模式(将命令者与被命令者分离)
- Template Method:模板方法模式(相同流程用一个模板方法)
- Iterator:迭代器模式(一种内部实现无关的集合遍历模式)
- State:状态模式 (当对象两种状态差别很大时使用)
- Chain of Responsibility:责任链模式(事件处理的分层结构产生的责任链条)
- Memento:备忘录模式(需要撤销与恢复操作时使用)
- Visitor:访问者模式 (当对同一对象有多种不同操作时使用)
- Mediator:中介者模式(以中介为中心,将网状关系变成星型关系)
- Interpreter:解释器模式(常用于纯文本的表达式执行)
5.2 创建型模式(IOC:控制反转,就是创建分离的集大成)
- Factory Method:工厂方法模式(对象创建可控,隐藏具体类名等实现解耦)
- Abstract Factory:抽象工厂模式(解决对象与其属性匹配的工厂模式)
- Builder:建造者模式(封装降低耦合,生成的对象与构造顺序无关)
- Singleton:单例模式(全局只要一个实例)
- Prototype:原型模式(通过拷贝原对象创建新对象)
- 创建型模式的五种有各自的使用环境,单例和原型比较简单就不说了,工厂方法模式和建造者模式,都是封装和降低耦合有啥不同呢,其实工厂方法关注的是一个类有多个子类的对象创建(汽车类的各种品牌),而建造者模式关注的是属性较多的对象创建(能达到过程无关)。而抽象工厂模式关注的是对象和属性及属性与属性的匹配关系(如奥迪汽车与其发动机及空调的匹配)。
5.3 结构型模式(对象的组成以及对象之间的依赖关系)
- Decorator:装饰器模式(比继承更灵活,可用排列组合形成多种扩展类)
- Adapter:适配器模式(适配不同接口和类,一般解决历史遗留问题)
- Composite:组合模式(整体和部分相同时,如文件夹包含文件夹)
- Facade:外观模式(对模块或产品的封装,降低耦合)
- Proxy:代理模式(可以给类的每个方法增加逻辑,如身份验证)
- Bridge:桥接模式(就是接口模式,抽象与实现分离)
- Plyweight:享元模式(相同对象的重用)
- 我们可以看到适配器模式、装饰器模式、代理模式都可以用包装对象来实现(把对象作为一个属性放在用的对象里),所以模式关注的并不是实现,而是解决的问题。模式更多体现的是类与类之间的逻辑关系,比如代理模式和装饰器模式很像。但从字面就知道,代理是访问不了实际工作对象的,这是他们的区别。
这里借鉴了面向对象的23种设计模式博客。