01-java设计模式知识点-上篇
目录
一:GoF 的 23 种设计模式的分类和功能
1. 根据目的来分
2. 根据作用范围来分
3.23种设计模式的功能
二:设计模式的目的及六大原则
1.设计模式的目的
2.设计模式六大原则
三:UML
1.基本构件
2.三种常用的UML图示
3.建模工具
4.类之间的关系及实例
四:23种设计模式详解
1.工厂方法模式(Factory Method)
2.抽象工厂模式(Abstract Factory)
3.单例模式(Singleton)
4.建造者模式(Builder)
5.原型模式(Prototype)
6.适配器模式(Adapter)
7.装饰模式(Decorator)
8.代理模式(Proxy)
9.外观模式(Facade)
10.桥接模式(Bridge)
11.组合模式(Composite)
12.享元模式(Flyweight)
13.策略模式(strategy)
14.模板方法模式(Template Method)
15.观察者模式(Observer)
16.迭代器模式(Iterator)
17.责任链模式(Chain of Responsibility)
18.命令模式(Command)
19.备忘录模式(Memento)
20.状态模式(State)
21.访问者模式(Visitor)
22.中介者模式(Mediator)
23.解释器模式(Interpreter)
一:GoF 的 23 种设计模式的分类和功能
1. 根据目的来分
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式结构型模行为型模式
1).创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
2).结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
3).行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
2. 根据作用范围来分
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式
1).类模式 :用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
2).对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
3.23种设计模式的功能
- 工厂方法(Factory Method)模式 :定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式 :提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 单例(Singleton)模式 :某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 建造者(Builder)模式 :将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 原型(Prototype)模式 :将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 适配器(Adapter)模式 :将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 装饰(Decorator)模式 :动态的给对象增加一些职责,即增加其额外的功能。
- 代理(Proxy)模式 :为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 外观(Facade)模式 :为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 桥接(Bridge)模式 :将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
- 组合(Composite)模式 :将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 享元(Flyweight)模式 :运用共享技术来有效地支持大量细粒度对象的复用。
- 策略(Strategy)模式 :定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 观察者(Observer)模式 :多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 迭代器(Iterator)模式 :提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 命令(Command)模式 :将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 备忘录(Memento)模式 :在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 状态(State)模式 :允许一个对象在其内部状态发生改变时改变其行为能力。
- 访问者(Visitor)模式 :在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 中介者(Mediator)模式 :定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 解释器(Interpreter)模式 :提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
二:设计模式的目的及六大原则
1.设计模式的目的
编写软件过程中,程序员面临着来自耦合性、内聚性、可维护性、可扩展性、重用性及灵活性等多方面的挑战,设计模式是为了让程序(软件)具有下列好处:
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
2.设计模式六大原则
设计模式原则是在编程时,应当遵守的原则,也是各种设计模式的基础
1).开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,为了使程序的扩展性好,易于维护和升级,不能去修改原有的代码,实现一个热插拔的效果。想要达到这样的效果,我们需要使用接口和抽象类。
2).里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)是面向对象设计的基本原则之一。 里氏代换原则指,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3).依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4).接口隔离原则(Interface Segregation Principle)
这个原则指:使用多个隔离的接口,比使用单个接口要好。是降低类之间耦合度的意思,设计模式是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5).迪米特法则(最少知道原则)(Demeter Principle)
最少知道原则就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6).合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
三:UML
UML(Unified Modeling Language)是一种统一建模语言,为面向对象开发系统的产品进行说明、可视化、和编制文档的一种标准语言。
1.基本构件
UML 建模的核心是模型,模型是现实的简化、真实系统的抽象。UML 提供了系统的设计蓝图。当给软件系统建模时,需要采用通用的符号语言,这种描述模型所使用的语言被称为建模语言。在 UML 中,所有的描述由事物、关系和图这些构件组成。下图完整地描述了所有构件的关系。
事物
事物是抽象化的最终结果,分为结构事物、行为事物、分组事物和注释事物。
1.结构事物
结构事物是模型中的静态部分,用以呈现概念或实体的表现元素,如下表所示。
事物 | 解释 | 图例 |
---|---|---|
类(Class) | 具有相同属性、方法、关系和语义的对象集合 |
![]() |
接口(Interface) | 指一个类或构件的一个服务的操作集合,它仅仅定义了一组操作的规范,并没有给出这组操作的具体实现 |
![]() |
用例(User Case) | 指对一组动作序列的描述,系统执行这些动作将产生一个对特定的参与者(Actor)有价值且可观察的结果 |
![]() |
协作(Collaboration) | 定义元素之间的相互作用 |
![]() |
组件(Component) | 描述物理系统的一部分 |
![]() |
活动类(Active Class) | 指对象有一个或多个进程或线程。活动类和类很相象,只是它的对象代表的元素的行为和其他元素是同时存在的 |
![]() |
节点(Node) | 定义为运行时存在的物理元素 |
![]() |
2.行为事物
行为事物指 UML 模型中的动态部分,如下表所示。
事物 | 解释 | 用例 |
---|---|---|
交互(Interaction) | 包括一组元素之间的消息交换 |
![]() |
状态机(State Machine) | 由一系列对象的状态组成 |
![]() |
3.分组事物
目前只有一种分组事物,即包。包纯碎是概念上的,只存在于开发阶段,结构事物、行为事物甚至分组事物都有可能放在一个包中,如下表所示。
事物 | 解释 | 用例 |
---|---|---|
包(Package) | UML中唯一的组织机制 |
![]() |
4.注释事物
注释事物是解释 UML 模型元素的部分,如下表所示。
事物 | 解释 | 用例 |
---|---|---|
注释(Note) | 用于解析说明 UML 元素 |
![]() |
图
UML2.0 一共有 13 种图(UML1.5 定义了 9 种,UML2.0 增加了 4 种),分别是类图、对象图、构件图、部署图、活动图、状态图、用例图、时序图、协作图 9 种,以及包图、组合结构图、时间图、交互概览图 4 种。
图名称 | 解释 |
---|---|
类图(Class Diagrams) | 用于定义系统中的类,包括描述类的结构和类之间的关系。类图的主要作用于描述系统的静态结构。 |
对象图(Object Diagrams) | 类图的一个实例,描述了系统在具体时间点上所包含的对象及各个对象之间的关系 |
构件图(Component Diagrams) | 构件图用来描述代码构件的物理结构以及构件之间的依赖关系。一个构件可以是一个资源文件、一个二进制文件或者已给可执行文件。 |
部署图(Deployment Diagrams) | 用来定义了系统中硬件的物理体系结构,用来描述实际的物理设备以及它们之间的连接关系。 |
活动图(Activity Diagrams) | 概述系统的动态行为,包括活动状态,活动状态是指业务用例的一个执行步骤或一个操作,不是普通对象的状态。活动图适合描述在没有外部事件触发的情况下,系统内部的逻辑执行过程,否则状态图更容易描述类似与传统意义上的流程图。业务建模时,用于详述业务用例,描述一项业务的执行过程设计时,描述操作的流程。 |
状态图(State Chart Diagrams) | 状态图说明对象在它的生命周期中响应事件所经历的状态序列,以及它们对那些事件的响应。 |
用例图(Usecase Diagrams) | 用来描述用户的需求,从用户的角度描述系统的功能,并指出各功能的执行者,强调谁在使用系统、系统为执行者完成哪些功能 |
时序图(Sequence Diagrams) | 描述对象之间的交互顺序,着重体现对象间消息传递的时间顺序,强调对象之间消息的发送顺序,同时显示对象之间的交互过程 |
协作图(Collaboration Diagrams) | 协作图是一种交互图,强调的是发送和接受消息的对象之间的组织结构。协作图主要描述协作对象的交互和链接,显示对象间的连接以及对象之间如何发送消息, 协作图可以表示类操作的实现。 |
包图(Package Diagrams) | 对构成系统的模型元素进行分组整理的图 |
组合结构图(Composite Structure Diagrams) | 表示类或者构建内部结构的图 |
时间图(Timing Diagrams) | 用来显示随时间变化,一个或多个元素的值或状态的更改,也显示时间控制事件之间的交互及管理它们的时间和期限约束 |
交互概览图(Interaction Overview Diagrams) | 用活动图来表示多个交互之间的控制关系的图 |
2.三种常用的UML图示
1):用例图
以可视化的方式表达系统如何满足所收集的业务规则,以及特定用户需求等信息(通常是用来描述在我们系统中出现的角色他所需要一些什么功能)
例:
在餐馆里厨师和顾客,普通用户可以吃东西,喝东西,支付;厨师需要做饭。通过这个图可以看到,厨师和普通用户这两个角色,还有中间系统提供的功能。这样就把角色和系统的功能关联在了一起。
2):序列图
序列图用于按照交互发生的一系列顺序,显示对象之间的这些交互
例:
如果一个用户去一个系统取钱,取钱的过程使用户和系统发生的一个关系,
- 表明身份
- 柜台接待
- 请求取钱
- 取钱
- 确认
- 授权合法
- 分发货币
(用户与系统的交互)
3):类图或类型图
主要用于描述业务逻辑和结构化的信息,(通常来讲就是用来描述类和类之间的关系,类中有哪些属性,方法)
上图描述的是Dog类和Animal类的关系,Dog类是继承Animal类的
类三部分:
第一部分:类名
第二部分:属性(共有/私有 属性名称 属性类型)
第三部分:方法(共有/私有 [<<Override>>]方法名称 方法类型) [<<Override>>]表示重写了父类的方法
3.建模工具
三种常用的建模工具:Visio,Rational Rose,PowerDesign
Visio:office 的一个组件(微软的官方网站有他的使用说明)
Rational Rose:是IBM公司的
建模是资深程序员的工作,入门程序员需要会看
4.类之间的关系及实例
在软件系统中,类不是孤立存在的,类与类之间存在各种关系。根据类与类之间的耦合度从弱到强排列,UML 中的类图有以下几种关系:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系。其中泛化和实现的耦合度相等,它们是最强的。
1):UML的类图基本规则
1. 斜体表示 抽象
2. + 表示 public # 表示 protected (default) 表示 默认 - 表示 private
3. 下划线 表示 构造方法
4. 接口 用 《interface》 标注
5. 类型 总是写在 变量 后面,用 : 引出
6.继承的方法 不用写出来,如果是覆盖了的方法,需要写出来
2):类图关系
接口:空心圆+直线(唐老鸭类实现了‘讲人话’)
依赖:虚线+箭头(动物和空气的关系),eg: - - - - - - - - >
关联:实线+箭头(企鹅需要知道气候才迁移),eg
聚合:空心四边形+实线+箭头(雁群和大雁的关系),eg:
合成/组合:实心四边形+实线+箭头(鸟和翅膀的关系)eg:
泛化/继承:空心三角形+实线(动物和鸟的继承关系),eg:
实现:空心三角形+虚线(实现大雁飞翔的接口),eg:
依赖(Dependency):“动物”、“氧气”与“水”之间。动物有几大特征,比如有新陈代谢,能繁殖。而动物要有生命,需要氧气,水以及食物等,即动物依赖于氧气和水。
//动物
public abstract class Animal
{//新陈代谢public void bolism(Oxygen oxygen,Water water){}
}//氧气
public class Oxygen{}//水
public class Water{}
关联(association):企鹅与气候有很大的关系,企鹅需要“知道”气候的变化,需要“了解”气候规律。当一个类“知道”另一个类时,可以用关联关系。
//在企鹅Penguin中,引用到气候Climate对象
public class Penguin extends Bird
{private Climate climate;
}//气候类
public class Climate{}
聚合(Aggregation):“大雁”和“雁群”这两个类。大雁是群居动物,每只大雁都属于一个雁群,一个雁群可以有多只大雁。所以它们之间就满足聚合(Aggregation)关系。聚合表示一种弱 的 “ 拥 有 ” 关 系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。
//在雁群WideGooseAggregate类中,有大雁数组对象arrayWideGoose
public class WideGooseAggregate
{private WideGoose[] arrayWideGoose;
}public class WideGoose{}
组合(composition):“鸟”和“翅膀”这两个类。鸟和翅膀是整体和部分的关系,并且翅膀和鸟的生命周期是相同的,在这里鸟和其翅膀就是组合关系。组合(composition)是一种强 的 “ 拥 有 ” 关 系 ,体现了严格的部分和整体的关系,部分和整体的生命周期一样。另外,合成关系的连线两端还有一个数字“1”和数字“2”,,这被称为基数。表明这一端的类可以有几个实例,很显然,一个鸟应该有两支翅膀。如果一个类可能有无数个实例,则就用“n”来表示。关联关系,聚合关系也可以有基数的。
//鸟类
public class Bird
{private Wing wing;public Bird(){//在鸟Bird类中,初始化时,实例化翅膀Wing,它们之间同时生成wing=new Wing();}
}public class Wing{}
继承(extend):动物,鸟,鸭,唐老鸭他们之间都是继承的关系。
//动物
public abstract class Animal
{public abstract void bolism(Oxygen oxygen,Water water);}//鸟类
public class Bird extends Animal{@Overridepublic void bolism(Oxygen oxygen,Water water){System.out.println("鸟开始新陈代谢!");} }
实现(implement):大雁”实现了“飞翔”接口。
//大雁
public class WideGoose implements Ifly
{ @Overridepublic void fly(){System.out.println("大雁南飞!");}} //飞翔
public interface Ifly{public void fly();
}
四:23种设计模式详解
创建型模式详解
1.工厂方法模式(Factory Method)
工厂方法模式分为三种:简单工厂方法模式、多个工厂方法模式、静态工厂方法模式
1).普通工厂方法模式:指建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
见关系图:
举例如下:(我们举一个发送邮件和短信的例子)
创建二者共同的发送接口
public interface Sender {public void Send();
}
创建实现类
//邮件发送类
public class MailSender implements Sender {@Overridepublic void Send() {System.out.println("this is mail sender!");}
}//短信发送类
public class SmsSender implements Sender {@Overridepublic void Send() {System.out.println("this is sms sender!");}
}
建工厂类
//工厂类
public class SendFactory {public Sender produce(String type) {if ("mail".equals(type)) {return new MailSender();} else if ("sms".equals(type)) {return new SmsSender();} else {System.out.println("请输入正确的类型!");return null;}}
}
测试
public class FactoryTest {public static void main(String[] args) {SendFactory factory = new SendFactory();Sender sender = factory.produce("sms");sender.Send();}
}
输出:this is sms sender!
2).多个工厂方法模式:是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
关系图如:
将上面的代码做下修改,改动下SendFactory类,如下:
public class SendFactory {public Sender produceMail(){return new MailSender();}public Sender produceSms(){return new SmsSender();}
}
测试
public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produceMail(); sender.Send(); }
}
输出:this is mailsender!
3).静态工厂方法模式:指将多个工厂方法模式里的方法置为静态的,不需要创建工厂类实例,直接调用即可。
类图如:
代码
//工厂类
public class SendFactory {public static Sender produceMail(){return new MailSender();}public static Sender produceSms(){return new SmsSender();}
}//测试
public class FactoryTest { public static void main(String[] args) { Sender sender = SendFactory.produceMail(); sender.Send(); }
}
输出:this is mail sender!
总体来说,工厂模式适合:需要创建大量具有共同接口的对象,可以通过工厂方法模式进行创建。在工厂方法的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
2.抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,如果想要拓展程序,必须对工厂类进行修改,则违背了闭包原则,所以,从设计角度考虑,有一定的问题,解决此问题就要用到抽象工厂模式。
抽象工厂模式指创建多个工厂类,一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
关系图如:
例子:
发送接口
//发送接口
public interface Sender {public void Send();
}
两个实现类
//邮件发送类
public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mail sender!"); }
} //短信发送类
public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); }
}
工厂接口
public interface Provider {public Sender produce();
}
两个工厂类
public class MailSendFactory implements Provider {@Overridepublic Sender produce(){return new MailSender();}
}public class SmsSendFactory implements Provider{@Overridepublic Sender produce() {return new SmsSender();}
}
测试
public class Test {public static void main(String[] args) {Provider provider = new MailSendFactory();Sender sender = provider.produce();sender.Send();}
}
这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就可以了,无需去改动现成的代码,拓展性较好!
3.单例模式(Singleton)
单例对象能保证在一个JVM中,该对象只有一个实例存在,并提供一个访问它的全局访问点。
好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
场景:
需要频繁创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象或工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
3.1几种实现方式
1):懒汉式
示例如:
public class Singleton {//2.本类内部创建对象实例private static Singleton instance = null;/*** 1.构造方法私有化,外部不能new*/private Singleton() {}//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {if (null==instance) {instance = new Singleton();}return instance;}}
调用顺序时序图:
懒汉式体现了延时加载的思想,就是一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了,才去加载。
懒汉式是时间换空间,不加同步的懒汉式是线程不安全的,如下示例:
实现线程安全的懒汉式(双重校验加锁),对懒汉式单例模式进行线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。
//线程安全的懒汉式单例模式
public class Singleton {private volatile static Singleton instance = null;// 私有化构造方法private Singleton() {}public static Singleton getInstance() {if (null==instance) {synchronized (Singleton.class) {if (null==instance) {instance = new Singleton();}}}return instance;}}
2):饿汉式
饿汉式是空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要判断了,节省了运行时间。
缺点:可能在还不需要此实例的时候就已经把实例创建出来了,没起到懒加载的效果
优点:实现简单,安全可靠
//饿汉式
public class Singleton {private static Singleton instance = new Singleton();// 私有化构造方法private Singleton() {}public static Singleton getInstance() {return instance;}}
3):使用静态内部类实现单例模式
public class Singleton {private static class SingletonHoler {/*** 静态初始化器,由JVM来保证线程安全*/private static Singleton instance = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHoler.instance;}}
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性
通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。似乎静态内部类看起来已经是完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。
4):使用枚举来实现单例(推荐使用)
//枚举单例模式public class Singleton {//构造方法私有化private Singleton(){}//返回实例public static Singleton getInstance() {return InnerSingletonEnum.INSTANCE.getInstance();}/**内部枚举类*使用枚举方法实现单利模式*/private enum InnerSingletonEnum {INSTANCE;private Singleton instance;//JVM保证这个方法绝对只调用一次InnerSingletonEnum() {instance = new Singleton();}public Singleton getInstance() {return instance;}}}
3.2破坏单例模式的几种方式
1):反射
2):序列化
3):克隆
/*** 对单例的破坏的示例*/
public class SingletonTest09 {public static void main(String[] args) throws Exception{System.out.println("-----------序列化--------------------");Singleton originSingleton = Singleton.getInstance();ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(Singleton.getInstance());ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);Singleton serializeSingleton = (Singleton) ois.readObject();System.out.println(originSingleton == serializeSingleton);//falseSystem.out.println("-----------反射----------------------");//通过反射获取Constructor<Singleton> cons = Singleton.class.getDeclaredConstructor();cons.setAccessible(true);Singleton reflextSingleton = cons.newInstance();System.out.println(reflextSingleton == originSingleton);//falseSystem.out.println("-----------克隆----------------------");Singleton cloneSingleton = (Singleton) originSingleton.clone();System.out.println(cloneSingleton == originSingleton);//false}//内部类private static class Singleton implements Serializable,Cloneable{//1.构造方法私有化,外部不能newprivate Singleton() {} //2.本类内部创建对象实例private static volatile Singleton instance;//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {if(null == instance) {synchronized (Singleton.class) {if(null == instance) {instance = new Singleton();}}}return instance;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}}
解决方案如下:
1):防止反射破坏
定义一个全局变量,当第二次创建的时候抛出异常
2):防止序列化破坏
添加readResolve(),返回Object对象
3):防止克隆破坏
重写clone(),直接返回单例对象
代码如下
*** 对单例的破坏的解决方案*/
public class SingletonTest10 {public static void main(String[] args) throws Exception{System.out.println("-----------序列化--------------------");Singleton originSingleton = Singleton.getInstance();ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(Singleton.getInstance());ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);Singleton serializeSingleton = (Singleton) ois.readObject();System.out.println(originSingleton == serializeSingleton);//falseSystem.out.println("-----------反射----------------------");//通过反射获取Constructor<Singleton> cons = Singleton.class.getDeclaredConstructor();cons.setAccessible(true);Singleton reflextSingleton = cons.newInstance();System.out.println(reflextSingleton == originSingleton);//falseSystem.out.println("-----------克隆----------------------"); Singleton cloneSingleton = (Singleton) originSingleton.clone();System.out.println(cloneSingleton == originSingleton);//false}//内部类private static class Singleton implements Serializable,Cloneable{//默认是第一次创建private static volatile boolean isCreate = false;//1.构造方法私有化,外部不能newprivate Singleton() {if(isCreate) {throw new RuntimeException("已然被实例化一次,不能在实例化");}isCreate = true;}//2.本类内部创建对象实例private static volatile Singleton instance;//3.提供一个公有的静态方法,返回实例对象public static Singleton getInstance() {if(null == instance) {synchronized(Singleton.class) {if(null == instance) {instance = new Singleton();}}}return instance;}@Overrideprotected Object clone() throws CloneNotSupportedException {return instance;}//防止序列化破环private Object readResolve() {return instance;} }}
3.3单例模式在JDK的应用
在JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
4.建造者模式(Builder)
工厂模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。
关系图如:
创建发送接口
public interface Sender {public void Send();
}
创建实现类
//邮件发送类
public class MailSender implements Sender {@Overridepublic void Send() {System.out.println("this is mail sender!");}
}//短信发送类
public class SmsSender implements Sender {@Overridepublic void Send() {System.out.println("this is sms sender!");}
}
建造者类
public class Builder {private List<Sender> list = new ArrayList<Sender>();public void produceMailSender(int count){for(int i=0; i<count; i++){list.add(new MailSender());}}public void produceSmsSender(int count){for(int i=0; i<count; i++){list.add(new SmsSender());}}
}
测试
public class Test {public static void main(String[] args) {Builder builder = new Builder();builder.produceMailSender(10);}
}
从这点看出,建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工厂模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建复合对象。因此,是选择工厂模式还是建造者模式,依实际情况而定。
5.原型模式(Prototype)
将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
关系图如:
创建一个原型类
public class Prototype implements Cloneable {public Object clone() throws CloneNotSupportedException {Prototype proto = (Prototype) super.clone();return proto;}
}
一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的。
对象深、浅复制的概念:
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
此处,写一个深浅复制的例子
public class Prototype implements Cloneable, Serializable {private static final long serialVersionUID = 1L;private String string;private SerializableObject obj;/* 浅复制 */public Object clone() throws CloneNotSupportedException {Prototype proto = (Prototype) super.clone();return proto;}/* 深复制 */public Object deepClone() throws IOException, ClassNotFoundException {/* 当前对象写入二进制流 */ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);/* 读出二进制流产生的新对象 */ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return ois.readObject();}public String getString() {return string;}public void setString(String string) {this.string = string;}public SerializableObject getObj() {return obj;}public void setObj(SerializableObject obj) {this.obj = obj;}}//依赖的其他对象
class SerializableObject implements Serializable {private static final long serialVersionUID = 1L;
}
要实现深复制,需要采用流的形式对象进行读写。
结构型模式详解:适配器模式是结构型模式的起源
6.适配器模式(Adapter)
适配器模式将某个类转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
1):类的适配器模式
类图如:
核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里,看代码:
//source类
public class Source { public void method1() { System.out.println("this is original method!"); }
} //目标接口
public interface Targetable { /* 与原类中的方法相同 */ public void method1(); /* 新类的方法 */ public void method2();
} //适配类
public class Adapter extends Source implements Targetable { @Override public void method2() { System.out.println("this is the targetable method!"); }
}
测试
public class AdapterTest { public static void main(String[] args) { Targetable target = new Adapter(); target.method1(); target.method2(); }
}
输出:
this is original method!
this is the targetable method!
这样Targetable接口的实现类就具有了Source类的功能
2):对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
关系图如:
修改Adapter类的代码
public class Wrapper implements Targetable { private Source source; public Wrapper(Source source){ super(); this.source = source; } @Override public void method2() { System.out.println("this is the targetable method!"); } @Override public void method1() { source.method1(); }
}
测试
public class AdapterTest { public static void main(String[] args) { Source source = new Source(); Targetable target = new Wrapper(source); target.method1(); target.method2(); }
}
输出:
this is original method!
this is the targetable method!
输出与第一种一样,只是适配的方法不同而已。
3):接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现该接口并实现所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
类图如:
代码
public interface Sourceable { public void method1(); public void method2();
}
抽象类Wrapper
public abstract class Wrapper implements Sourceable{ public void method1(){} public void method2(){}
} //实现类1
public class SourceSub1 extends Wrapper { @Override public void method1(){ System.out.println("the sourceable interface's first Sub1!"); }
} //实现类2
public class SourceSub2 extends Wrapper { @Override public void method2(){ System.out.println("the sourceable interface's second Sub2!"); }
}
测试
public class WrapperTest { public static void main(String[] args) { Sourceable source1 = new SourceSub1(); Sourceable source2 = new SourceSub2(); source1.method1(); source1.method2(); source2.method1(); source2.method2(); }
}
测试输出:
the sourceable interface's first Sub1!
the sourceable interface's second Sub2!
达到了我们的效果!
三种适配器模式的应用场景:
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
7.装饰模式(Decorator)
装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
类图如下:
Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能。
代码如下
//接口类
public interface Sourceable { public void method();
} //source类
public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); }
} //装饰类
public class Decorator implements Sourceable { private Sourceable source; public Decorator(Sourceable source){ super(); this.source = source; } @Override public void method() { System.out.println("before decorator!"); source.method(); System.out.println("after decorator!"); }
}
测试
public class DecoratorTest { public static void main(String[] args) { Sourceable source = new Source(); Sourceable obj = new Decorator(source); obj.method(); }
}
输出:
before decorator!
the original method!
after decorator!
装饰器模式的应用场景:
1):需要扩展一个类的功能。
2):动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
缺点:产生过多相似的对象,不易排错!
8.代理模式(Proxy)
代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
类图如:
代码
//接口类
public interface Sourceable { public void method();
} //source类
public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); }
} //代理类
public class Proxy implements Sourceable { private Source source; public Proxy(){ super(); this.source = new Source(); } @Override public void method() { before(); source.method(); atfer(); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); }
}
测试
public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); } }
输出:
before proxy!
the original method!
after proxy!
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
9.外观模式(Facade)
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。(以一个计算机的启动过程为例)
类图如:
代码
//cpu
public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); }
} //memory
public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); }
} //disk
public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); }
} //computer
public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ System.out.println("start the computer!"); cpu.startup(); memory.startup(); disk.startup(); System.out.println("start computer finished!"); } public void shutdown(){ System.out.println("begin to close the computer!"); cpu.shutdown(); memory.shutdown(); disk.shutdown(); System.out.println("computer closed!"); }
}
测试
public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); }
}
输出:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这就是外观模式。
10.桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
类图如:
代码
//接口
public interface Sourceable { public void method();
}
两个实现类
public class SourceSub1 implements Sourceable { @Override public void method() { System.out.println("this is the first sub!"); }
} public class SourceSub2 implements Sourceable { @Override public void method() { System.out.println("this is the second sub!"); }
}
定义一个桥,持有Sourceable的一个实例
public abstract class Bridge { private Sourceable source; public void method(){ source.method(); } public Sourceable getSource() { return source; } public void setSource(Sourceable source) { this.source = source; }
} public class MyBridge extends Bridge { @Overridepublic void method(){ getSource().method(); }
}
测试
public class BridgeTest { public static void main(String[] args) { Bridge bridge = new MyBridge(); /*调用第一个对象*/ Sourceable source1 = new SourceSub1(); bridge.setSource(source1); bridge.method(); /*调用第二个对象*/ Sourceable source2 = new SourceSub2(); bridge.setSource(source2); bridge.method(); }
}
输出:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。
JDBC连接类图
11.组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便.
类图如:
代码
public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>(); public TreeNode(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } //添加孩子节点 public void add(TreeNode node){ children.add(node); } //删除孩子节点 public void remove(TreeNode node){ children.remove(node); } //取得孩子节点 public Enumeration<TreeNode> getChildren(){ return children.elements(); }
}
public class Tree { TreeNode root = null; public Tree(String name) { root = new TreeNode(name); } public static void main(String[] args) { Tree tree = new Tree("A"); TreeNode nodeB = new TreeNode("B"); TreeNode nodeC = new TreeNode("C"); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished!"); }
}
使用场景:将多个对象组合在一起进行操作,常用于表示树形结构,例如二叉树,树等。
12.享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,尽可能的减少内存的使用量,当系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象,提高系统性能。
场景:
如果在一个系统中有很多个完全相同或相似的对象,可以使用享元模式,让他们共享一份内存,不必每个都去实例化对象,从而节省内存,实现共享的关键在于区分内部和外部状态,内部可共享,外部不可共享,从而实现高效重用。
享元模式的主要角色由享元工厂、抽象享元、具体享元类几部分组成。
与单例模式区别:
1):单例模式是一个类仅一个对象,享元设计模式是一个类有很多对象,但是每个对象只有一个实例而已。
2):享元模式是为了节约内存空间,提升程序性能,而单例模式则主要是出于共享状态的目的。
关系图如:
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
数据库连接类图:
代码
public class ConnectionPool { private Vector<Connection> pool; /*公有属性*/ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; Connection conn = null; /*构造方法,做一些初始化工作*/ private ConnectionPool() { pool = new Vector<Connection>(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } /* 返回连接池中的一个数据库连接 */ public synchronized Connection getConnection() { if (pool.size() > 0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; } }
}
通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!
优点:
1):节省内存空间,对于可重复的对象只会被创建一次,对象比较多时,就会极大的节省空间。
2):提高效率,由于创建对象的数量减少,所以对系统内存的需求也减小,使得速度更快,效率更高。
缺点:
1):享元类有内部状态和外部状态,其区分就是内部状态可以共享,外部状态不可共享,两个状态的处理逻辑有些复杂。
行为型模式
13.策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。
关系图如:
代码
public interface ICalculator { public int calculate(String exp);
}
辅助类
public abstract class AbstractCalculator { public int[] split(String exp,String opt){ String[] arrayStr = exp.split(opt); int[] arrayInt = new int[2]; arrayInt[0] = Integer.parseInt(arrayStr[0]); arrayInt[1] = Integer.parseInt(arrayStr[1]); return arrayInt; }
}
三个实现类
//加
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int[] arrayInt = split(exp,"\\+"); return arrayInt[0]+arrayInt[1]; }
}//减
public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int[] arrayInt = split(exp,"-"); return arrayInt[0]-arrayInt[1]; } }//乘
public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int[] arrayInt = split(exp,"\\*"); return arrayInt[0]*arrayInt[1]; }
}
测试
public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); }
}
输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14.模板方法模式(Template Method)
指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。
关系图如:
在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用。
代码
public abstract class AbstractCalculator { /*主方法,实现对本类其它方法的调用*/ public final int calculate(String exp,String opt){ int[] arrayInt = split(exp,opt); return calculate(arrayInt[0],arrayInt[1]); } /*被子类重写的方法*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String[] arrayStr = exp.split(opt); int[] arrayInt = new int[2]; arrayInt[0] = Integer.parseInt(arrayStr[0]); arrayInt[1] = Integer.parseInt(arrayStr[1]); return arrayInt; }
}
//加法
public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; }
} //减法
public class Minus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 - num2; }
}
测试
public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "\\+"); System.out.println(result); }
}
输出:16
15.观察者模式(Observer)
观察者模式类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就是这意思,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!
类图如:
MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。
代码:
Observer接口
public interface Observer { public void update();
}
两个实现类
public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); }
} public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); } }
Subject接口及实现类
public interface Subject { /*增加观察者*/ public void add(Observer observer); /*删除观察者*/ public void del(Observer observer); /*通知所有的观察者*/ public void notifyObservers(); /*自身的操作*/ public void operation();
}
public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } }
}
public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); } }
测试
public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); } }
输出:
update self!
observer1 has received!
observer2 has received!
16.迭代器模式(Iterator)
迭代器模式就是顺序访问聚集中的对象,包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。
关系图如:
MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例。
代码
public interface Collection { public Iterator iterator(); /*取得集合元素*/ public Object get(int i); /*取得集合大小*/ public int size();
} public interface Iterator { //前移 public Object previous(); //后移 public Object next(); //下一个元素是否存在public boolean hasNext(); //取得第一个元素 public Object first();
}
两个实现
//集合实现类
public class MyCollection implements Collection { public String[] strs = {"A","B","C","D","E"}; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return strs[i]; } @Override public int size() { return strs.length(); }
} //迭代器实现类
public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; }else{ return false; } } @Override public Object first() { pos = 0; return collection.get(pos); } }
测试
public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); } }
}
输出:A B C D E
17.责任链模式(Chain of Responsibility)
责任链模式:有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。
关系图如:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
public interface Handler { public void operation();
} public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } }
public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operation() { System.out.println(name+"deal!"); if(getHandler()!=null){ getHandler().operation(); } }
}
测试
public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); }
}
输出:
h1deal!
h2deal!
h3deal!
链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18.命令模式(Command)
命令模式:使调用者和执行者解耦,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。
关系图如:
nvoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象。
代码
//命令接口
public interface Command { public void exe();
} //接口实现类
public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); }
}
public class Receiver { public void action(){ System.out.println("command received!"); }
} public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action(){ command.exe(); }
}
测试
public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); }
}
输出:command received!
命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术。
19.备忘录模式(Memento)
备忘录模式指保存一个对象的某个状态,以便在适当的时候恢复对象。通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。
关系图如:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例。
代码
public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); }
}
//备忘录类
public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; }
} //储存备忘录类
public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; }
}
测试
public class Test { public static void main(String[] args) { // 创建原始类 Original origi = new Original("egg"); // 创建备忘录 Storage storage = new Storage(origi.createMemento()); // 修改原始类的状态 System.out.println("初始化状态为:" + origi.getValue()); origi.setValue("niu"); System.out.println("修改后的状态为:" + origi.getValue()); // 回复原始类的状态 origi.restoreMemento(storage.getMemento()); System.out.println("恢复后的状态为:" + origi.getValue()); }
}
输出:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
20.状态模式(State)
当对象的状态改变时,同时改变其行为。举例QQ,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。
关系图如:
State类是个状态类,Context类可以实现切换。
代码
//状态类的核心类
public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); }
} //状态模式的切换类
public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); } }
}
测试
public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //设置第一种状态 state.setValue("state1"); context.method(); //设置第二种状态 state.setValue("state2"); context.method(); }
}
输出:
execute the first opt!
execute the second opt!
21.访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。
访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。
关系图如:
一个Visitor类,存放要访问的对象
代码
public interface Visitor { public void visit(Subject sub);
}public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); }
}
Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
public interface Subject { public void accept(Visitor visitor); public String getSubject();
} public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; }
}
测试
public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); }
}
输出:visit the subject:love
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦。
22.中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。
关系图如:
User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!
代码
public interface Mediator { public void createMediator(); public void workAll();
} public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } @Override public void createMediator() { user1 = new User1(this); user2 = new User2(this); } @Override public void workAll() { user1.work(); user2.work(); }
} public abstract class User { private Mediator mediator; public Mediator getMediator(){ return mediator; } public User(Mediator mediator) { this.mediator = mediator; } public abstract void work();
} //user实现类
public class User1 extends User { public User1(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user1 exe!"); }
} //user实现类
public class User2 extends User { public User2(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user2 exe!"); }
}
测试
public class Test { public static void main(String[] args) { Mediator mediator = new MyMediator(); mediator.createMediator(); mediator.workAll(); }
}
输出:
user1 exe!
user2 exe!
23.解释器模式(Interpreter)
一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
关系图如:
Context类是一个上下文环境类,Plus和Minus分别是用来计算的实现。
代码
public interface Expression { public int interpret(Context context);
} //加法类
public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); }
} //减法类
public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); }
} //环境类
public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } }
测试
public class Test { public static void main(String[] args) { // 计算9+2-8的值 int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); }
}
输出:3
解释器模式用来做各种各样的解释器,如正则表达式等的解释器等。
01-java设计模式知识点-上篇相关推荐
- 虚拟机的分类_「面试必备」Java虚拟机知识点复习手册(下)
关注我的微信公众号:后端技术漫谈 不定期推送关于后端开发.爬虫.算法题.数据结构方面的原创技术文章,以及生活中的逸闻趣事. 我目前是一名后端开发工程师.主要关注后端开发,数据安全,网络爬虫,物联网,边 ...
- Java虚拟机知识点快速复习手册(上)
前言 本文快速回顾了常考的的知识点,用作面试复习,事半功倍. 上篇主要内容为:虚拟机数据区域,垃圾回收 下篇主要内容为:类加载机制 面试知识点复习手册 全复习手册文章导航 Csdn全复习手册文章导航: ...
- Java设计模式系列之——模板方法模式
大事件 关注篮球或者喜欢逛社交网站的朋友们可能都知道,2019年10月5日,NBA休斯顿火箭队总经理莫雷在推特上发布了一张无知的涉港图片,引发全体中国人民的强烈抗议和不满,一时间舆论哗然,此后,NBA ...
- Java并发知识点快速复习手册(下)
前言 本文快速回顾了常考的的知识点,用作面试复习,事半功倍. 面试知识点复习手册 已发布知识点复习手册 Java基础知识点面试手册 快速梳理23种常用的设计模式 Redis基础知识点面试手册 Java ...
- 2019年秋招 Java 面试知识点梳理(高频问题)
Java 面试知识点梳理 基础一 JVM-内存区域分配 HotSpot 对象创建 JVM-类加载机制 JVM-内存分配(堆上的内存分配) JVM-GC回收机制 JVM-垃圾收集器 JVM指令重排序 重 ...
- Java集合知识点,看这篇就够了,还有月薪3万简历模板+BAT面试题,帮你进大厂!
Java集合知识点,猿人花了几天时间整理,还有Java超神之路脑图.月薪3万Java优秀简历模板.全网最全一线大厂Java笔试面试题.1000+本Java开发精华电子书送给大家,希望大家认真学习哦! ...
- Java企业实训 - 01 - Java前奏
前言: 虽然个人专攻.NET方向,不过由于个人是干教育行业的,方方面面的东西,不能说都必须精通,但肯定多少都会涉及到. 一个菜鸟学员,从啥都不会,经过一步步学习,最后到企业上手掌管一个模块甚至一个项目 ...
- JAVA设计模式--结构型模式
2019独角兽企业重金招聘Python工程师标准>>> 我们接着讨论设计模式,上篇文章我讲完了5种创建型模式,这章开始,我将讲下7种结构型模式:适配器模式.装饰模式.代理模式.外观模 ...
- 【java设计模式】-00目录
开篇 [java设计模式]-01设计模式简介 创建型模式: [java设计模式]-02工厂模式(Factory Pattern) [java设计模式]-03抽象工厂模式(Abstract Factor ...
最新文章
- 刚子扯谈:谢谢你 要学会尊重文字 即使它写的很狗屎
- 如何编辑Subversion中已提交的日志消息?
- InnoDB与MyISAM引擎区别
- Linux网络编程 五种I/O 模式及select、epoll方法的理解
- LeetCode:62. 不同路径
- boost::fusion::flatten用法的测试程序
- window10安装python2.7_Windows10-python2.7安
- ps法线贴图插件_法线与置换贴图原理讲解以及烘焙制作!
- ffmpeg rtp传输使用
- Servlet中forward和redirect的区别
- php自动生成网站地图
- PMP强化三错题记录
- 深入分析 RTPS协议
- linux系统怎么禁用键盘,Linux下禁用笔记本自带键盘和touchpad
- C语言中数值后面跟字母解析
- 高丽参的作用与功效及忌讳
- 腾讯云服务器配置jre、jdk、tomcat
- Decision Model and Notation (DMN)
- 如何巧妙的防止网站被劫持
- 婴幼儿蛋白质过敏怎么回事
热门文章
- 【历史上的今天】11 月 11 日:腾讯成立;信息论先驱出生;阿德曼提出 DNA 计算
- dPCA 二面角主成分分析
- 亚马逊IC-ID/ISED认证需要提供什么资料
- Vuepress-theme-reco 构建静态网页错误:在格式错误时超出了最大调用堆栈大小
- c语言编程入门教程+网易,人话讲编程·C语言入门:第一讲,Hello World
- CornerNet论文详解CornerNet: Detecting Objects as Paired Keypoints
- python图像分析_python数字图像处理(一)图像的常见操作
- java中map和表单字符串相互转换
- iOS_3DTouch使用
- 你是哪个级别?(工程师级别划分)