ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

开端-Java设计模式导读

开端-Java设计模式导读

开端-Java设计模式导读

和往常一样,本篇文章依旧采用“3W”顺序(即What,Why和Where)来回答软件工程中的设计模式是什么,为什么需要设计模式以及在什么地方使用设计模式这三个问题。

​ 本篇文章时Java设计模式系列技术文章的开篇,作为导读文章,将快速地对设计模式的基本概念、模式分类和适用范围进行解读。在后续的章节中,将对每一种类别的设计模式进行详细的讲解,讲解的内容包括每种设计模式的基本原理、适用范围和实战案例剖析三个部分。

1 模式的基本概念

​ 模式是指解决某个特定领域问题,实现既定目标的方法或思想。具体来说,模式是那些身处于某个行业的从业人员根据实际的工作经验总结出的,具有通用性的且被行业公认的解决问题的方法或流程。模式并非只在软件工程中被应用,其在日常的生产活动中被广泛地使用,如制造业,餐饮业,建筑设计、医疗卫生、教育培训以及软件工程等都有模式的身影。

2 什么是设计模式?

​ 首先,设计模式是一种模式。在软件工程中,设计模式是一种通用的、可重复使用的用于解决既定范围内普遍发生的重复性问题的软件设计方法。使用成熟可靠的设计模式,可以提高代码复用性,节省开发时间,从而实现功能更强大、高度可维护的代码。这有助于降低软件产品的总体拥有成本,即TCO(Total Cost of Ownership)。另一方面,由于采用了统一的标准设计方法(思想或理论知识),可以显著提升开发团队的生产效率和协作能力。

3 Java设计模式的分类

​ 在Java编程语言中,常用的设计模式可分为三种类型:

  • 建造类设计模式:主要用于定义和约束如何创建一个新的对象
  • 结构类设计模式:主要用于定义如何使用多个对象组合出一个或多个复合对象
  • 行为类设计模式:主要用于定义和描述对象之间的交互规则和限定对象的职责边界线

图3-1 设计模式分类

3.1 建造类设计模式

​ 建造类共包括五(5)种基本设计模式:单例模式,工厂模式,抽象工厂模式,建造器模式和原型模式,如图3-2所示:

图3-2 建造类设计模式

3.2 结构类设计模式

​ 结构类共包括八(8)种基本设计模式:适配器模式,组合模式,代理模式,享元模式,过滤器模式,桥接模式,修饰模式和外观模式,如图3-3所示:

图3-3 结构类设计模式

3.3 行为类设计模式

​ 行为类共包括十一(11)种基本设计模式:模板方法模式,解释器模式,责任链模式,观察者模式,战略模式,命令模式,状态模式,访客模式,转义模式,迭代器模式和备忘录模式,如图3-4所示:

图3-4 行为类设计模式

​ 设计模式不仅仅只有上述描述的这三大类,除此之外还有许多的设计模式。现已知的设计模式还有100多种,如DAO模式,依赖注入模式和MVC模式等。

4 快速理解设计模式

​ 在接下来的内容中,将快速对Java中常见的24中设计模式的基本概念进行梳理,以求对各种设计模式的原理和适用范围有一个大致的认识。

4.1 建造类

​ 建造类设计模式提供了对创建对象的基本定义和约束条件,以寻求最佳的实例化Java对象解决方案。

4.1.1 单例模式-Singleton

​ 单例模式限制类的实例化过程,以确保在Java虚拟机(JVM)中有且只有一个类的实例化对象。单例模式是Java中最常用,也是最简单的设计模式之一。单例模式通常需具备如下的几个特征:

  • 单例模式限制类的实例化,且Java虚拟机中只能存在一个该类的示例化对象
  • 单例模式必须提供一个全局可用的访问入口来获取该类的实例化对象
  • 单例模式常被用于日志记录,驱动程序对象设计,缓存以及线程池
  • 单例模式也会被用于其他的设计模式当中,如抽象工厂模式,建造者模式,原型模式等

单例模式的Java类的内部结构如图4-1所示:

图4-1 单例模式类图

下面是单例模式的一份示例代码清单:

4.1.2 工厂模式-Factory

​ 在Java程序设计过程中,当一个超类(super class)具有多个子类(sub class),且需要频繁的创建子类对象时,我们可以采用工厂模式。工厂模式的作用是将子类的实例化工作统一交由工厂类来完成,通过对输入参数的判断,工厂类自动实例化具体的子类。实现工厂模式需要满足三个条件:

  • 超类(super class):超类是一个抽象类
  • 子类(sub class): 子类需继承超类
  • 工厂类(factory class):工厂类根据输入参数实例化子类

图4-2为Java工厂模式的类图:

图4-2 工厂模式UML类图

下面是工厂模式的一份示例代码清单:

4.1.3 抽象工厂模式-Abstract Factory

​ 抽象工厂模式与工厂模式很类似,抽象工厂模式可以简单的理解为“工厂的工厂”。在工厂模式中,根据提供的输入参数返回产品类的实例化对象,这个过程需要通过if-else或者switch这样的逻辑判断语句来完成具体子类的判定。而在抽象工厂模式中,每种产品都有具体的工厂类与之对应,从而避免在编码过程中使用大量的逻辑判断代码。抽象工厂模式会根据输入的工厂类型以返回具体的工厂子类。抽象工厂类只负责实例化工厂子类,不参与商品子类的实例化工作。图4-3是抽象工厂模式的UML类图:

图4-3 抽象工厂模式

4.1.4 建造器模式-Builder

​ 建造者模式通常被用于需要多个步骤创建对象的场景中。建造者模式的主要意图是将类的构建逻辑转移到类的实例化之外,当一个类有许多的属性,当在实例化该类的对象时,并不一定拥有该实例化对象的全部属性信息,便可使用建造者模式通过逐步获取实例化对象的属性信息,来完成该类的实例化过程。而工厂模式和抽象工厂模式需要在实例化时获取该类实例化对象的全部属性信息。图4-4展示了建造器模式的基本逻辑关系:

图 4-4 建造器模式UML类图

4.1.5 原型模式-Prototype

​ 原型模式的主要作用是可以利用现有的类通过复制(克隆)的方式创建一个新的对象。当示例化一个类的对象需要耗费大量的时间和系统资源时,可是采用原型模式,将原始已存在的对象通过复制(克隆)机制创建新的对象,然后根据需要,对新对象进行修改。原型模式要求被复制的对象自身具备拷贝功能,此功能不能由外界完成。图4-5展示了原型模式的基本逻辑:

图4-5 原型模式UML类图

4.2 结构类

​ 结构类设计模式主要解决如何通过多个小对象组合出一个大对象的问题,如使用继承和接口实现将多个类组合在一起。

4.2.1 适配器模式-Adapter

​ 适配器模式的主要作用是使现有的多个可用接口能够在一起为客服端提供新的接口服务。在适配器模式中,负责连接不同接口的对象成为适配器。在现实生活中,我们也能够找到很多实际的案例来理解适配器的工作原理,例如常用的手机充电头,在手机和电源插座之间,手机充电头就扮演一个适配器的角色,它能够同时适配220V,200V,120V等不同的电压,最终将电转换成手机可用的5V电压为手机进行充电。图4-6展示了适配器的基本原理:

图 4-6 适配器模式UML类图

4.2.2 组合模式-Composite

​ 组合模式的主要作用是让整体与局部之前具有相同的行为。例如我们需要绘制一个图形(正方形,三角形,圆形或其他多边形),首先需要准备一张空白的纸,然后是选择一种绘制图案的颜色,再次是确定绘制图案的大小,最后是绘制图案。不管是绘制正方形还是三角形,都需要按照这个步骤进行。在软件设计过程中,组合模式的最大意义在于保证了客户端在调用单个对象与组合对象时,在其操作流程上是保持一致的。图4-7展示了组合模式的基本原理:

图 4-7 组合模式UML类图

4.2.3 代理模式-Proxy

​ 代理模式的主要作用是通过提供一个代理对象或者一个占位符来控制对实际对象的访问行为。代理模式通常用于需要频繁操作一些复杂对象的地方,通过使用代理模式,可以借由代理类来操作目标对象,简化操作流程。图4-8展示了代理模式的基本原理:

图 4-8 代理模式UML类图

4.2.4 享元模式-Flywight

​ 享元模式的主要作用是通过共享来有效地支持大量细粒度的对象。例如当需要创建一个类的很多对象时,可以使用享元模式,通过共享对象信息来减轻内存负载。如果在软件设计过程中采用享元模式,需要考虑以下三个问题:

  • 应用程序需要创建的对象数量是否很大?
  • 对象的创建对内存消耗和时间消耗是否有严格的要求?
  • 对象的属性是否可以分为内在属性和外在属性?对象的外在属性是否支持有客户端定义?

图4-9展示了享元模式的基本原理:

图 4-9 享元模式UML类图

4.2.5 外观模式-Facade

​ 外观模式的主要作用是为子系统中的一组接口提供一个统一的接口,以便客户端更容易去使用子系统中的接口。简单的理解是外观模式为众多复杂接口定义了一个更高级别的接口。外观模式的目的是让接口更容易被使用,图4-10展示了外观模式的基本原理:

图 4-10 外观模式UML类图

4.2.6 桥接模式-Bridge

​ 桥接模式的主要用途是将抽象类与抽象类的具体实现相分离,以实现结构上的解耦,使抽象和实现可以独立的进行变化。桥接模式的实现优先遵循组合而不是继承,当使用桥接模式时,在一定程度上可以在客户端中因此接口的内部实现。图4-11展示了桥接模式的基本原理:

图 4-11 桥接模式UML类图

4.2.7 修饰模式-Decorator

​ 修饰模式的主要作用是在运行时动态的组合类的行为。通常,你会添加一些新的类或者新的方法来扩展已有的代码库,然而,在某些情况下你需要在程序运行时为某个对象组合新的行为,此时你可以采用修饰模式。图4-12展示了修饰模式的基本原理:

图 4-12 修饰模式UML类图

4.2.8 过滤器模式-Filter

​ 过滤器模式是使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式将对象组合起来。图 4-13展示了过滤器模式的基本原理:

图 4-13 过滤器模式

4.3 行为类

​ 行为类设计模式主要用于定义和描述对象之间的交互规则和职责边界,为对象之间更好的交互提供解决方案。

4.3.1 模板方法模式-Template Method

​ 模板方法模式的主要作用是在一个方法里实现一个算法,可以将算法中的的一些步骤抽象为方法,并将这些方法的实现推迟到子类中去实现。例如建造一栋房子,我们需要设计图纸,打地基,构筑墙体,安装门窗和内部装修。我们可以设计不同的房屋样式(别墅,高楼,板房等),不同的门窗和不同的装修材料和风格,但是其顺序不能颠倒。在这种情况下,我们可以定义一个模板方法,规定方法的执行顺序,而将方法的实现推迟到子类中完成。图4-14展示了模板方法模式的基本原理:

图 4-14 模板方法模式UML类图

4.3.2 解释器模式-Mediator

​ 解释器(中介)模式的主要设计意图是定义一个中间对象,封装一组对象的交互,从而降低对象的耦合度,避免了对象间的显示引用,并可以独立地改变对象的行为。解释器(中介)模式可以在系统中的不同对象之间提供集中式的交互介质,降低系统中各组件的耦合度。图 4-15展示了解释器(中介)模式的基本原理:

图 4-15 解释器(中介)模式UML类图

4.3.3 责任链模式-Chain of Responsibility

​ 责任链模式主要作用是让多个对象具有对同一任务(请求)的处理机会,以解除请求发送者与接收者之间的耦合度。try-catch就是一个典型的责任链模式的应用案例。在try-catch语句中,可以同时存在多个catch语句块,每个catch语句块都是处理该特定异常的处理器。当try语句块中发生异常是,异常将被发送到第一个catch语句块进行处理,如果第一个语句块无法处理它,它将会被请求转发到链中的下一个catch语句块。如果最后一个catch语句块仍然不能处理该异常,则该异常将会被向上抛出。图4-16展示了责任链模式的基本原理:

图 4-16 责任链模式UML类图

4.3.4 观察者模式-Observer

​ 观察者模式的目的是在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,观察者会通知依赖它的对象,并根据新状态做出相应的反应。简单来说,如果你需要在对象状态发生改变时及时收到通知,你可以定义一个监听器,对该对象的状态进行监听,此时的监听器即为观察者(Observer),被监听对象称为主题(Subject)。Java消息服务(JMS)即采用了观察者设计模式(同时还使用了中介模式),允许应用程序订阅数据并将数据发布到其他应用程序中。图4-17展示了观察者模式的基本原理:

图 4-17 观察者模式UML类图

4.3.5 策略模式-Strategy

​ 策略模式的主要目的是将可互换的方法封装在各自独立的类中,并且让每个方法都实现一个公共的操作。策略模式定义了策略的输入与输出,实现则由各个独立的类完成。策略模式可以让一组策略共存,代码互不干扰,它不仅将选择策略的逻辑从策略本身中分离出来,还帮助我们组织和简化了代码。一个典型的例子是Collections.sort()方法,采用Comparator作为方法参数,根据Comparator接口实现类的不同,对象将以不同的方式进行排序。图 4-18 展示了策略模式的基本原理:

图 4-18 策略模式UML类图

4.3.6 命令模式-Command

​ 命令模式的设计意图是将请求封装在对象的内部。直接调用是执行方法的通常做法,然而,在有些时候我们无法控制方法被执行的时机和上下文信息。在这种情况下,可以将方法封装到对象的内部,通过在对象内部存储调用方所需要的信息,就可以让客户端或者服务自由决定何时调用方法。图 4-19 展示了命令模式的基本原理:

图 4-19 命令模式UML类图

4.37 状态模式-State

​ 状态模式的设计意图是更具对象的状态改变其行为。如果我们必须根据对象的状态改变对象的行为,可以在对象中定义一个状态变量,并使用逻辑判断语句块(如if-else)根据状态执行不同的操作。图4-20展示了状态模式的基本原理:

图 4-20 状态模式UML类图

4.3.8 访客模式-Visitor

​ 访客模式的设计意图是在不改变现有类层次结构的前提下,对该层次结构进行扩展。例如在购物网站中,我们将不同的商品添加进购物车,然后支付按钮时,它会计算出需要支付的总金额数。我们可以在购物车类中完成金额的计算,也可以使用访客模式,将购物应付金额逻辑转移到新的类中。图 4-21展示了访客模式的基本原理:

图 4-21 访客模式UML类图

4.3.9 转义(翻译)模式-Interpreter

​ 转义(翻译)模式的设计意图是让你根据事先定义好的一系列组合规则,组合可执行的对象。实现转义(翻译)模式的一个基本步骤如下:

  • 创建执行解释工作的上下文引擎
  • 根据不同的表达式实现类,实现上下文中的解释工作
  • 创建一个客户端,客户端从用户那里获取输入,并决定使用哪一种表达式来输出转义后的内容

图4-22展示了转义(翻译)模式的基本原理:

图 4-22 转义(翻译)模式UML类图

4.3.10 迭代器模式-Iterator

​ 迭代器模式为迭代一组对象提供了一个标准的方法。迭代器模式被广泛的应用于Java Collection框架中,Iterator接口提供了遍历集合元素的方法。迭代器模式不仅仅是遍历集合,我们还可以根据不同的要求提供不同类型的迭代器。迭代器模式通过集合隐藏内部的遍历细节,客户端只需要使用对应的迭代方法即可完成元素的遍历操作。图4-23 展示了迭代器的基本原理:

图 4-23 迭代器模式UML类图

4.3.11 备忘录模式-Memento

​ 备忘录模式的设计意图是为对象的状态提供存储和恢复功能。备忘录模式由两个对象来实现-Originator和Caretaker。Originator需要具有保存和恢复对象状态的能力,它使用内部类来保存对象的状态。内部内则称为备忘录,因为它是对象私有的,因此外部类不能直接访问它。图4-24展示了备忘录模式的基本原理:

图 4-24 备忘录模式UML类图

小节

​ 在本篇文章中,说明了模式是指解决某个特定领域问题,实现既定目标的方法或思想;设计模式是一种通用的、可重复使用的用于解决既定范围内普遍发生的重复性问题的软件设计方法。同时,对Java中常见的设计模式进行了分类,设计模式分为建造、结构和行为三种类型,并对每种类型的设计模式的基本概念和原理进行了介绍,在后续的章节中,将详细的介绍每种设计模式的原理、使用方式和适用范围,并给出相应的实战源码。

附件

​ 在文章最后,为了便于大家阅读,提供了本文的电子版文件

​ 百度网盘下载地址:https://pan.baidu.com/s/1OREFj5QmLsHcKNbaJGLTmw

​ 文件提取码:jqgf

​ 文件提取二维码:

技术精讲:Java 8 Stream API

技术精讲:Java 8 Stream API

技术精讲:Java 8 Stream API

Stream(流)是在Java 8中新增的新特性,首先需要为Java 8 Stream正名:Java 8中的Stream跟Java I/O Stream(例如:InputStream,OutputStream等)没有任何的关系。Stream是Java中数据源的包装器,通过Stream我们可以快速的对数据源进行操作(例如:过滤,排序、求和等等),且Stream不对任何数据进行存储,所以Stream也不是数据结构。

​ 在Java 8中,Stream增强了Array,List等对象操作数据的能力,Stream提供了一些列的方法使的这些原有的集合类可以轻松创建Stream对象实例。通过Stream API提供的静态方法,可以快速的生成有限/无限的数据流。特别指出,Stream只是增强了原有集合类的能力,并未对底层的源码做任何的修改。

1.Stream的工作流程

​ 接下来,将介绍使用Java Stream的基本步骤。在Java 8中,Stream的生命周期一共有三个阶段:

  • 1.获取数据源并创建Stream实例。数据源可以是数组、列表、对象或者I/O流
  • 2.执行中间操作(Intermediate Operations)。中间操作可以是过滤、排序、类型转换等操作
  • 3.执行终端操作(Terminal Operation)。终端操作主要是对最终结果进行计数、求和、创建新集合等操作

图1-1展示了Java 8 Stream的工作工作流程。

Stream工作流程

图 1-1 Stream工作流程

2.Stream 操作管道是什么?

​ 所谓的Stream操作管道是指“中间操作”和“终端操作”的组合。在Java 8 Stream API中,许多的操作方法都将当前的Stream作为最终结果返回,这就允许开发人员以连式编程的方式,组合出更大的Stream操作管道。Java 8 Stream API位于java.util.stream包下,其组织结构如图2-1所示:

java.util.stream包

图2-1 java.util.stream包

接下来,将详细介绍“中间操作”和“终端操作”各自的方法和特性。

3.Stream中间操作(Intermediate Operations)是什么?

​ Stream的中间操作将返回一个Stream,它允许开发者以查询的方式调用多个其他的操作。特别注意的是,在“终端操作”方法未被调用之前,“中间操作”的方法不会被执行。Stream的“中间操作”一共包括七个:Stream.filter,Stream.map,Stream.flatmap,Stream.peek,Stream.sorted和Stream.limit。

3.1.Stream.filter

​ Stream.filter将返回一个包含与之谓词相匹配的元素的新的Stream。下面将通过一个示例,演示Stream.filter的使用方法。

Stream.filter

执行结果:

此示例使用“中间操作”Stream.filter过滤以“j”开头的字符串,并使用“终端操作”Stream.count统计以“j”开头的字符串数量。

3.2.Stream.map

​ Stream.map将使用java.util.function.Function提供的方法转换Stream中的元素。Stream.map操作通常用于转换集合对象。下面是使用Stream.map的示例代码:

Stream.map

执行结果:

3.3.Stream.flatmap

​ Stream.flatmap通常将Stream中的每一个元素转换成0个或多个元素。下面将通过flatmap来统计一个文本中单词的出现次数(不重复)。示例代码如下:

Stream.flatmap

执行结果:

source.txt内容:

java 8 stream example by ramostear

3.4.Stream.peek

​ Stream.peek在调试期间非常有用,它允许您在操作Stream之前查看Stream内部的数据。下面是示例代码:

执行结果:

Java
C#
C++
GO
result size = 4

3.5.Stream.distinct

​ Stream.distinct是根据其内部的equals方法在Stream中对元素进行去重操作。下面是使用示例:

执行结果:

6
1
8
11

3.6.Stream.sorted

​ Stream.sorted方法用于将Stream中的数据元素进行排序。下面是使用示例:

执行结果:

1
6
8
11

3.7.Stream.limit

​ Stream.limit方法用于限定Stream中元素的个数。下面是使用示例:

执行结果:

0
1
2
3
4
5
11
618

4.Stream 终端操作(Terminal Operations)是什么?

​ 终端操作用于生产最终的数据结果,如对象,数组或者列表。在终端操作方法被执行前,中间操作方法将不会被执行。终端操作方法一共有十二个,它们是:forEach,toArray,reduce,collect,min,max,count,anymatch,allMatch,noneMatch,findFirst和findAny。表4-1列举了这十二个终端操作的使用方法:

Stream终端操作方法

图 4-1 Stream终端操作方法

5.如何创建Stream实例?

​ Stream支持多种数据源创建Stream实例,如Array,List,Object和I/O流。接下来,通过简单的示例来演示如何创建Stream。

5.1 使用数组创建Stream

​ 首先,我们定义一个静态的数组users,并初始几条用户数据,代码如下:

private static User[] users = {new User("user1"),new User("user2"),new User("user3")};

接下来,将利用users来创建一个Stream对象实例,示例代码如下:

public static void fromArray() {
    Stream<User> uStream = Stream.of(users);
    System.out.println("create stream from array.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.2 使用对象创建Stream

​ 沿用上面定义的users数组,通过数组下标获得用户对象,并利用这些对象创建一个Stream实例,代码如下:

public static void fromObjects() {
    Stream<User> uStream = Stream.of(users[0],users[1],users[2]);
    System.out.println("create stream from objects.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.3 使用List创建Stream

​ 接下来,我们将users数组转换成List,并使用此List来创建Stream实例。代码如下:

public static void fromList() {
    List<User> list = Arrays.asList(users);
    Stream<User> uStream = list.stream();
    System.out.println("create stream from list.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.4 使用builder方法创建Stream

​ 最后,我们将演示使用Stream内置的builder()静态方法创建Stream实例。代码如下:

public static void byBuilder() {
    Stream.Builder<User> userStream = Stream.builder();
    userStream.accept(users[0]);
    userStream.accept(users[1]);
    userStream.accept(users[2]);

    System.out.println("create stream by builder.");
    userStream.build().forEach(u->System.out.println(u.getUsername()));

}

​ 以上就是通过数据源创建Stream实例的几种不同方式。

6.为什么要使用Stream?

​ 在命令式编程中,我们必须逐行编写代码,才能完成相关的计算。例如,计算1~10000的数的总和,我们需要使用for(int i=1;i<=10000;i++){….}循环语句来迭代求值。在这个语句中,我们需要花费额外的精力来维护loop变量的值。而在声明式编程中,我们只需要关注想要实现的目标,而不是其内部重复的循环逻辑。Stream API通过使用内部迭代器和lambda表达式,帮助我们完成了那些重复性的逻辑。Stream API的作用不仅仅用于数据迭代,它还能通过中间操作和终端操作实现数据的排序、类型转换、求和、计数、求最大值、最小值以及模式匹配等操作。

​ 接下来,表6-1展示了命令式编程和声明式编程的优劣:

命令式编程与声明式编程

总结

​ 在本文中,介绍了Java Stream的基本工作流程,并详细列举了中间操作和终端操作的细节。通过使用Stream,我们可以将传统的命令式编程代码进行简化,以提高编程效率。

设计模式-适配器模式

设计模式-适配器模式

设计模式-适配器模式(Adapter)

1.设计意图

适配器模式是将一个类的接口转换成另一个类想要的接口,适配器让原本两个不兼容的类能够兼容。
适配器模式类图

2. 演示案例

现实生活中我们到处都可以看到适配器

  • 1.如果你需要将SD卡中的数据传输到电脑上,为了将数据传输到电脑上,你可能需要一个与计算机USB接口兼容的读卡器,以便将SD卡连接到计算机。此时的读卡器就是一个适配器。
  • 2.如果你需要给手机充电,Type-c接口和普通充电接口无法对接,此时你需要一个充电转换器或者电源适配器,以便能够对接充电,这里的转换器就是一个适配器。
  • 3.同声传译人员将英文话翻译成中文话,翻译人员就是一个“适配器”

简而言之

适配器模式允许你在适配器中包装一个原本不兼容的对象,使其与另外一个类兼容

维基百科:

在软件工程中,适配器模式是一种软件设计模式,它允许现有类的接口作用于另外一个接口。它通常用于在不修改源代码的前提下使现有的类能够与其他类一起工作。

3. 程序示例

我们就以SD卡通过读卡器将数据传输到电脑中为案例来讲解适配器模式。
首先我们创建一个存储卡类和一个数据传输接口。存储卡类中有一个现实内存大小的方法:memory();数据传输接口中定义两个方法:read()和write();
SDCard.java

package com.ramostear.pattern.adapter;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-19:41
 * @modify by :
 * @info:[存储卡类]
 * @since:
 */

public class SDCard {

   public void memory(){
       System.out.println("8GB");
   }

}

DataTransfer.java

package com.ramostear.pattern.adapter;

/**
 * @author ramostear
 * @create-time 2019/1/4 0004-19:54
 * @modify by :
 * @info:[数据传输接口]
 * @since:
 */
public interface DataTransfer {

    void read();

    void write();
}

接下来我们将定义一个读卡器类,并将SD卡 “插入” 读卡器中,且实现数据读写接口
CardReader.java

package com.ramostear.pattern.adapter;

/**
 * @author ramostear
 * @create-time 2019/1/4 0004-19:56
 * @modify by :
 * @info:[读卡器]
 * @since:
 */
public class CardReader implements  DataTransfer{

    private SDCard sdCard;


    public CardReader() {
        this.sdCard = new SDCard();
    }

    @Override
    public void read() {
        sdCard.memory();
        System.out.println("read data from sdCard...");
    }

    @Override
    public void write() {
        sdCard.memory();
        System.out.println("write data to sdCard...");
    }
}

然后我们定义一个计算机类,预留“USB”的接口,并设置数据读写入口
Computer.java

package com.ramostear.pattern.adapter;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-20:01
 * @modify by :
 * @info:[计算机类]
 * @since:
 */
public class Computer {

    private DataTransfer dataTransfer;

    public Computer(){}

    public Computer(DataTransfer dataTransfer){
        this.dataTransfer = dataTransfer;
    }

    public DataTransfer getDataTransfer() {
        return dataTransfer;
    }

    public void setDataTransfer(DataTransfer dataTransfer) {
        this.dataTransfer = dataTransfer;
    }

    public void read(){
        dataTransfer.read();
    }

    public void write(){
        dataTransfer.write();
    }
}

最后,让我们将装入SD卡的读卡器插入到计算机的USB口中,启动计算机并对SD卡中的数据进行读写
App.java

package com.ramostear.pattern.adapter;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-20:04
 * @modify by :
 * @info:[测试类]
 * @since:
 */
public class App {

    public static void main(String[] args){
        /**
         * 1. 将SD卡插入读卡器
         * 2. 将读卡器插入电脑USB接口
         * 3. 电脑开机
         * 4. 从SD卡中读入数据到电脑,或者将输入从电脑中写入SD卡
         */
        Computer computer = new Computer(new CardReader());
        computer.read();
        computer.write();
    }

}

控制台输出:
控制台输出
至此,适配器模式设计实现完成。

4. 适用场景

当满足(不限于)以下应用场景时:

  • 你想复用现有的类,但它所提供的接口与你期望的接口不匹配时
  • 你希望创建一个可重用的类,并让该类与其他不可预见的类(不一定具有兼容接口的类)协作时
  • 大多数使用第三方库的应用程序使用适配器作为应用程序和第三方库之间的中间层,以将应用程序与库分离。如果必须使用另一个库,则只需要新库的适配器,而不必更改应用程序代码
  • 你需要使用几个现有的子类,但通过对每个子类进行子类化来调整它们的接口是不切实际的

5. 使用建议

5.1 类适配器

由于类适配器是适配者类的子类,因此可以在适配器类中调整适配者类中的方法,使得适配器的灵活性更强。但是如果在Java这样的单继承语言中,一次最多只能适配一个适配者类,且目标抽象类只能为接口,不能为类。

5.2 对象适配器

把多个不同的适配者适配到同一个目标类,同一个适配器可以把适配者和它的子类都适配到一个目标接口。

6. 实际案例

设计模式-抽象工厂模式

设计模式-抽象工厂模式

设计模式三十六计之抽象工厂模式(Abstract Factory)

1.设计意图

提供一个接口,用于创建相关或者从属对象的族,而不是指定他们的具体类。以下以生产计算机为例给出UML类图:
abstract-factory-pattern-uml

2.演示案例

假设我们要生产一台计算机(广义的),一台计算机有一些共同的物件。小型计算机(以手机为例)需要有触控屏、微处理器和小型化的内存条。大型计算机(以PC机为例)需要有显示屏、多核处理器和内存条。计算机的各个部件存在着相互依赖关系。

简而言之

抽象工厂即工厂的工厂,它将单独但相关/依赖的工厂分组在一起而不是指定具体类别的工厂。

维基百科:

抽象工厂模式提供了一种方法来封装一组具有共同主题的单个工厂,而不指定它们的具体类。

3.代码示例

以上述的生产计算机为案例,首选我们需要定义一些部件接口并实现这些部件接口

Memory.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:23
 * @modify by :
 * @info:[内存接口类]
 * @since:
 */
public interface Memory {

    String getDescription();
}

Screen.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:22
 * @modify by :
 * @info:[屏幕接口类]
 * @since:
 */
public interface Screen {

    String getDescription();
}

Processor.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:24
 * @modify by :
 * @info:[处理器接口类]
 * @since:
 */
public interface Processor {

    String getDescription();
}

PhoneMemory.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:28
 * @modify by :
 * @info:[手机内存类]
 * @since:
 */
public class PhoneMemory implements Memory{

    static final String DESCRIPTION = "This is phone memory";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

PhoneScreen.java

package com.ramostear.pattern.abstractfactory;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:26
 * @modify by :
 * @info:[手机屏幕类]
 * @since:
 */
public class PhoneScreen implements Screen{

    static final String DESCRIPTION = "This is phone screen";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

PhoneProcessor.java

package com.ramostear.pattern.abstractfactory;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:29
 * @modify by :
 * @info:[手机处理器类]
 * @since:
 */
public class PhoneProcessor implements Processor{

    static final String DESCRIPTION = "This is phone processor";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

ComputerMomory.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:38
 * @modify by :
 * @info:[电脑内存条]
 * @since:
 */
public class ComputerMemory implements Memory{

    static final String DESCRIPTION = "This is computer memory";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

ComputerScreen.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:36
 * @modify by :
 * @info:[电脑屏幕]
 * @since:
 */
public class ComputerScreen implements Screen{

    static final String DESCRIPTION = "This is computer screen";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

ComputerProcessor.java

package com.ramostear.pattern.abstractfactory;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:39
 * @modify by :
 * @info:[电脑处理器]
 * @since:
 */
public class ComputerProcessor implements Processor{

    static final String DESCRIPTION = "This is computer processor";

    @Override
    public String getDescription() {
        return DESCRIPTION;
    }
}

然后,我们定义一个抽象的电子产品生产工厂类并创建两个它的实现类:
ElectronicFactory.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:32
 * @modify by :
 * @info:[电子设备生产工厂接口类]
 * @since:
 */
public interface ElectronicFactory {
    /**
     * 生产屏幕
     * @return
     */
    Screen produceScreen();

    /**
     * 生产内存条
     * @return
     */
    Memory produceMemory();

    /**
     * 生产处理器
     * @return
     */
    Processor produceProcessor();

}

ComputerFactory.java

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:40
 * @modify by :
 * @info:[电脑生产工厂]
 * @since:
 */
public class ComputerFactory implements ElectronicFactory{

    @Override
    public Screen produceScreen() {
        return new ComputerScreen();
    }

    @Override
    public Memory produceMemory() {
        return new ComputerMemory();
    }

    @Override
    public Processor produceProcessor() {
        return new ComputerProcessor();
    }
}

PhoneFactory.java

package com.ramostear.pattern.abstractfactory;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-2:35
 * @modify by :
 * @info:[手机生产工厂]
 * @since:
 */
public class PhoneFactory implements ElectronicFactory{


    @Override
    public Screen produceScreen() {
        return new PhoneScreen();
    }

    @Override
    public Memory produceMemory() {
        return new PhoneMemory();
    }

    @Override
    public Processor produceProcessor() {
        return new PhoneProcessor();
    }
}

现在我们已经拥有了一个抽象的工厂,它可以让我们生产相关的电子产品部件,即手机工厂可以生产手机屏幕、手机处理器和手机内存条,同样电脑工厂可以生产电脑显示器、电脑内存条和电脑处理器等。我们来简单的测试一下:

public class SimpleTest {

    public static void main(String[] args){
        ElectronicFactory factory = new PhoneFactory();
        Screen screen = factory.produceScreen();
        Memory memory = factory.produceMemory();
        Processor processor = factory.produceProcessor();

        System.out.println(screen.getDescription()+"\n"+memory.getDescription()+"\n"+processor.getDescription());

    }
}

控制台输出:

现在,我们可以为不同的电子产品生产工厂设计一个工厂,即工厂的工厂。本例子中,我们创建一个FacotryMaker类,负责返回PhoneFactory或者ComputerFactory,客户端可以通过FactoryMacker工厂来创建所需的工厂,进而生产不同的电子产品部件(屏幕、处理器、内存条)。
首先定义一个枚举类型的类FactoryType,用于给FactoryMacker提供选择参考:

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-4:16
 * @modify by :
 * @info:[工厂类型]
 * @since:
 */
public enum FactoryType {
    PHONE,COMPUTER;
}

然后定义一个生产工厂的工厂类FactoryMacker:

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-4:16
 * @modify by :
 * @info:[工厂创建器:工厂的工厂]
 * @since:
 */
public  class FactoryMacker {

    /**
     * 此工厂方法负责创建具体的工厂类
     * @param type
     * @return
     */
    public static ElectronicFactory makeFactory(FactoryType type){
        switch (type){
            case PHONE:
                return new PhoneFactory();
            case COMPUTER:
                return new ComputerFactory();
            default:
                throw new IllegalArgumentException("FactoryType not supported.");
        }
    }
}

最后,我们定义一个AbstractFactory类来封装上述的单个工厂类:

package com.ramostear.pattern.abstractfactory;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-4:21
 * @modify by :
 * @since:
 */
public class AbstractFactory {

    private Screen screen;
    private Memory memory;
    private Processor processor;

    public void createFactory(final ElectronicFactory factory){
        setScreen(factory.produceScreen());
        setMemory(factory.produceMemory());
        setProcessor(factory.produceProcessor());
    }

    public Screen getScreen() {
        return screen;
    }

    private void setScreen(Screen screen) {
        this.screen = screen;
    }

    public Memory getMemory() {
        return memory;
    }

    private void setMemory(Memory memory) {
        this.memory = memory;
    }

    public Processor getProcessor() {
        return processor;
    }

    private void setProcessor(Processor processor) {
        this.processor = processor;
    }
}

现在,整个抽象工厂模式案例已经全部实现。最后测试一下我们创建的抽象工厂模式案例:

package com.ramostear.pattern.abstractfactory;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-4:27
 * @modify by :
 * @info:[对抽象工厂进行测试]
 * @since:
 */
public class TestAbstractFactory {

    public static void main(String[] args){
        AbstractFactory factory = new AbstractFactory();

        System.out.println("produce phone...");

        factory.createFactory(FactoryMacker.makeFactory(FactoryType.PHONE));
        System.out.println(factory.getScreen().getDescription());
        System.out.println(factory.getMemory().getDescription());
        System.out.println(factory.getProcessor().getDescription());


        System.out.println("produce computer...");

        factory.createFactory(FactoryMacker.makeFactory(FactoryType.COMPUTER));
        System.out.println(factory.getScreen().getDescription());
        System.out.println(factory.getMemory().getDescription());
        System.out.println(factory.getProcessor().getDescription());
    }
}

控制台输出:

4.适用性

当满足以下场景时适合适用抽象工厂模式

  • 系统应该独立于其产品的创建、组成和表示方式
  • 一个系统应该配置多个产品系列中的一个
  • 相关产品对象的系列设计为一起使用,您需要强制执行此约束
  • 您希望提供产品的类库,并且只显示它们的接口,而不显示它们的实现
  • 您需要一个运行时值来构造一个特定的依赖项
  • 您需要提供一个或多个仅在运行时已知的参数,然后才能解析依赖项
设计模式-单例模式

设计模式-单例模式

设计模式三十六计之单例模式(Singleton)

解释:单例模式是为了确保在整个应用生命周期内一个类只有一个实例化对象,并且提供该实例化对象的全局访问入口

1.结构和原理

一个最基本的单例模式类包含一个私有的静态变量、一个私有的构造函数和和一个共有的静态函数。其中私有构造函数保证了该类不能通过构造函数来实例化,只能通过共有的静态函数返回一个唯一的私有静态变量。

2.实现单例模式

  • 懒汉模式———线程不安全

    public class Singleton{
      private static Singleton instance;
      private Singleton(){}
    
      public static Singleton getInstance(){
        if(instance == null){
          instance = new Singleton();
        }
        return instance;
      }   
    }
    

    在上述实现中,私有静态变量instance被延迟实例化,这样做的好处是在实际项目中,如果Singleton类没有被使用到,它就不会被实例化,从而减小系统开销。

    注意:懒汉模式在多线程环境中是不安全的,例如当前有n个线程同时执行 getInstance() 方法时,此时的instance都为null,那么Singleton类就会被实例化n次。这与单例模式的设计初衷相悖。

  • 饿汉模式———线程安全

    public class Singleton{
      private static Singleton instance = new Singleton();
      private Singleton(){}
      public static Singleton getInstance(){
        return instance;
      }
    }
    

    备注:在懒汉模式中,线程不安全是由于instance被多次实例化所造成的,在饿汉模式中直接实例化Singleton就解决了线程不安全问题。但是这种方式就失去了延迟实例化的好处。

  • 双重校验锁模式———线程安全

    public class Singleton{
      private volatile static Singleton instance;
      private Singleton(){}
      public static Singleton getInstance(){
        if(instance == null){
          synchronized(Singleton.class){
            if(instance == null){
              instance = new Singleton();
            }
          }
        }
        return instance;
      }  
    }
    

    在双重校验锁模式下,双重锁先判断instance是否被实例化,如果instance没有被实例化,则将实例化instance的语句进行加锁操作,只有当instance真正为null时,才去实例化Singleton。instance只会被实例化一次,之后将不会被再次实例化。

    说明:volatile关键字的作用是在多线程环境下,禁止JVM的指令重排列

  • 静态内部类模式

    public class Singleton{
      private Singleton(){}
    
      public static Singleton getInstance(){
        return SingletonProvider.INSTANCE;
      }
    
      public static class SingletonProvider{
        private static final Singleton INSTANCE = new Singleton();
      }
    }
    

    静态内部类模式与饿汉模式有异曲同工之处

  • 枚举模式

    public enum Singleton{
      INSTANCE;
      private String name;
      //getter()...
      // setter()...
      // otherMethod()...
    }
    

    使用方式:

    public class UserEnumSingleton{
      Singleton instance = Singleton.INSTANCE;
      instance.setName("example");
      System.out.println(instance.getName());
      Singleton instance2 = Singleton.INSTANCE;
      instance2.setName("example2");
      System.out.println(instance2.getName());
      instance.otherMethod();
      //other options...
      //使用java反射原理操作
      try{
        Singleton[] enums = Singleton.class.getEnumConstants();
        for(Singleton instance : enums){
          System.out.println(instance.getName());
        }
      }catch(Exception e){
        e.printStackTrace();
      }
    }
    

    控制台输出结果:

    example
    example2
    example2
    

    借助JDK的枚举来实现单例模式,不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

3.总结

优点

单例模式使得应用系统中一个类只被实例化一次,节省系统资源开销,对于系统中需要频繁创建和销毁的对象,使用单例模式可以在一定程度上提高系统的性能。

缺点

由于采用单例模式对类进行设计,就必须要记住获取对象的入口,即共有的静态函数名,而不是采用new关键字进行类的实例化,这在多人协同开发的项目中会给开发人员带来一些困扰(看不到源码),因此需要统一编码规范。

适用的范围

  • 需要频繁的进行创建和销毁的类
  • 实例化过程中耗费时间过长、占用资源过多且被频繁调用的类
  • 频繁操作I/O流或者访问数据库的类
  • 应用中定义的工具类
设计模式-观察者模式

设计模式-观察者模式

设计模式三十六计之观察者模式(Observer)

1. 设计意图

定义对象之间的一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖项。

观察者模式

简而言之

你别来找我,给我你的联系方式,有事我会主动联系你

2.案例演示

以当前最火热的吃鸡游戏作为一个简单的案例来演示观察者模式,当玩家进入游戏时,会收到游戏服务器推送的提示消息,随着游戏的进行,如果某个玩家被Kill掉了,游戏服务器会把此消息推送给房间里的其他玩家。在本案例中,“游戏” 是一个抽象的被观察者,“吃鸡游戏” 是具体的被观察者;“游戏玩家” 是一个抽象的观察者(接口),而玩家A、玩家B等是具体的观察者。案例的UML关系如下图:

UML类图-案例对象关系

3. 示例代码

3.1 抽象的被观察者类(Subject)

AbstractGame.java

package com.ramostear.pattern.observer;
import java.util.ArrayList;
/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:27
 * @modify by :
 * @info:[抽象的被观测者类]
 * @since:
 */
public abstract class AbstractGame {
    /**
     * 定义一个存放观察者的容器
     */
    public final ArrayList<Observer> obsList = new ArrayList<>();
    /**
     * 注册观察者
     * @param obs   观察者
     * @param <T>
     */
    public <T> void attach(Observer obs){
        if (obs == null){
            throw new NullPointerException("Observer is null.");
        }else{
            this.attachObs(obs);
        }
    }
    /**
     * 注册观察者
     * @param obs
     */
    private void attachObs(Observer obs){
        if (obs == null){
            throw new NullPointerException("class is null");
        }else {
            synchronized (obsList){
                if(!obsList.contains(obs)){
                    obsList.add(obs);
                }
            }
        }
    }
    /**
     * 注销观察者
     * @param obs   观察者
     * @param <T>
     */
    public <T> void detach(Observer obs){
        if(obs == null){
            throw new NullPointerException("Observer is null");
        }else {
            this.detachObs(obs);
        }
    }
    /**
     * 注销观察者
     * @param obs
     */
    private void detachObs(Observer obs){
        if(obs == null){
            throw new NullPointerException("Class is null");
        }else{
            synchronized (obsList){
               obsList.remove(obs);
            }
        }
    }
    /**
     * 通知所有的观察者
     * @param messages
     */
    public abstract void notifyAllObs(String...messages);
    /**
     * 通知某个观察者
     * @param obs
     * @param messages
     */
    public abstract void notifyObs(Observer obs,String...messages);
}

AbstractGame类中定义了添加、删除和通知观察者的方法,同时有一个List类型的容器,用于保存已注册的观察者,当需要通知观察者时,从容器中取出观察者信息。

说明:抽象的被观察者可以定义成一个抽象类或者接口,本案例中采用的是抽象类

3.2 抽象的观察者接口(Observer)

Observer.java

package com.ramostear.pattern.observer;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:26
 * @modify by :
 * @info:[观察者接口]
 * @since:
 */
public interface Observer {
    /**
     * 更新状态
     * @param messages
     */
    void update(String... messages);
}

在该接口中定义了一个update() 方法,当被观察者发出通知时,此方法会被调用。

3.3 具体被观察者(ConcreteSubject)

ChikenGame继承了AbstractGame类,并对通知方法进行了具体的实现。
ChikenGame.java

package com.ramostear.pattern.observer;

/**
 * @author ramostear
 * @create-time 2019/1/5 0005-23:55
 * @modify by :
 * @info:[吃鸡游戏类]
 * @since:
 */
public class ChickenGame extends AbstractGame {

    private String roomName;

    public ChickenGame(String roomName) {
        this.roomName = roomName;
    }


    public String getRoomName() {
        return roomName;
    }

    public void setRoomName(String roomName) {
        this.roomName = roomName;
    }

    @Override
    public void notifyAllObs(String... messages) {
        obsList.forEach(obs->{
            this.notifyObs(obs,messages);
        });
    }

    @Override
    public void notifyObs(Observer obs, String... messages) {
       if (obs == null){
           throw new NullPointerException("Observer is null");
       }else{
          obs.update(messages);
       }
    }
}

3.4 具体观察者(ConcreteObserver)

Gamer类实现了Observer接口,并对Observer的update方法进行了具体的实现;这里为了演示,只是简单的对消息进行输出。
Gamer.java

package com.ramostear.pattern.observer;
/**
 * @author ramostear
 * @create-time 2019/1/6 0006-0:06
 * @modify by :
 * @info:[游戏玩家]
 * @since:
 */
public class Gamer implements Observer{

    private String name;

    public Gamer(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(String... messages) {
        System.out.println("玩家:"+name);
      for (String message:messages){
          System.out.println("消息->"+message+"\n");
      }
    }
}

3.5 测试本次案例

创建一个吃鸡游戏叫“三国吃鸡演义” ,将刘、关、张三个玩家注册到吃鸡游戏中。游戏发布消息给三个玩家,刘、关、张同时收到游戏发出的消息,当关羽挂掉后,只有刘、张两个玩家收到消息;当张飞再挂掉后,只有刘备收到消息。为了演示观察者模式,最后我们让关羽满血复活,此时刘、关二人收到游戏发出的消息。
App.java

package com.ramostear.pattern.observer;
/**
 * @author ramostear
 * @create-time 2019/1/6 0006-0:08
 * @modify by :
 * @info:[测试类]
 * @since:
 */
public class App {
    public static void main(String[] args){
        ChickenGame game = new ChickenGame("三国吃鸡演义");
        Gamer gamerLiu = new Gamer("刘备");
        Gamer gamerZhang = new Gamer("张飞");
        Gamer gamerGuan = new Gamer("关羽");

        game.attach(gamerLiu);
        game.attach(gamerGuan);
        game.attach(gamerZhang);
        game.notifyAllObs("欢迎进入"+game.getRoomName());
        game.notifyAllObs(new String[]{"刘关张桃园三结义,开始三国吃鸡演义..."});

        game.detach(gamerGuan);
        game.notifyAllObs("#关羽:\"我去!被98K爆了,快来扶我一下!\"");
        game.notifyAllObs("#刘备:\"我去,这货肥得一批!\"");

        game.detach(gamerZhang);
        game.notifyAllObs("#张飞:\"我去,这比是挂!\"");
        game.notifyAllObs("#刘备:\"我去!咋这么多人,我凉了!\"");

        game.attach(gamerGuan);
        game.notifyAllObs("关羽满血复活");
        game.notifyAllObs("#刘备:\"苟住,苟住就能赢!\"");

    }
}

测试结果:
观察者模式案例测试结果

4. 适用性

当满足以下情况中的一种时使用观察者模式

  • 当抽象有两个Aspect时,一个依赖于另一个。 将这些Aspact封装在单独的对象中可让您独立地改变和重用它们.
  • 当一个对象的更改需要更改其他对象时,你不知道到底需要更改多少个关联的对象
  • 当不希望多个对象之前发生紧耦合时

5. 真实案例

设计模式-建造者模式

设计模式-建造者模式

设计模式三十六计之建造者模式(Builder)

1. 设计意图

将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示。
建造者模式类图

2. 演示案例

假设我们需要创建一个用户对象,用户对象的属性有身份证号码、姓名、年龄、性别、族别、地址。最简单的方式是定义一个包含这六个属性的构造函数来完成对象的创建,但是,你想在构建的过程中只想包含其中的一个或者几个属性的时候,问题来了,没有与之对应的构造函数存在,也不可能提前定义多构造函数来覆盖这种动态传参的构造需求。这种类型的需求场景就需要建造者模式了(Builder)。

简而言之

建造者模式的要领是允许你创建不同风格的对象,同时避免构造函数被污染。当一个对象可能有N中风格存在或者对象的创建要涉及到多个步骤的时候适用于建造者模式

维基百科

构建器模式是一种对象创建软件设计模式,旨在寻找伸缩构造器反模式的解决方案。

在开始给出具体的代码前,我们先对比一下常规的一种设计方式。就上述的案例场景,我们可能会给出如下的一个构造函数:

  public User(String id,String name,int age,int gender,String nation,String address){
    //setter...
  }

在这种设计方式下,当用户属性改变时,构造函数参数的数量可能会很快失去控制,并且很难理解构造函数的参数排列,另外,如果用户属性继续增加,构造函数的参数列表将持续增长。这被称为伸缩构造反模式。

3. 程序示例

针对上述提到的问题,最合适的解决方案时使用建造者模式,首先我们需要创建一个User类:


public class User {

  private String id;    //身份证号码
  private String name;  //姓名
  private int age = 0;  //年龄
  private int gender = 0;//0:保密,1:男性,2:女性
  private String nation;  //族别
  private String address; //地址

  private User(Builder builder){
    this.id = builder.id;
    this.name = builder.name;
    this.age = builder.age;
    this.gender = builder.gender;
    this.nation = builder.nation;
    this.address = builder.address;
  }

  //getter...
  //setter...
  //toString...
}

然后我们需要创建一个Bulder类:

public static class Builder{

  private String id;    //身份证号码
  private String name;  //姓名
  private int age = 0;  //年龄
  private int gender = 0;//0:保密,1:男性,2:女性
  private String nation = "none";  //族别
  private String address = "none"; //地址

  /**
   * 假定建造时必须提供身份证号码和姓名
  **/
  public Builder(String id,String name){
    if(id == null || name == null){
      throw new IllegalArgumentException("id and name can not be null");
    }
    this.id = id;
    this.name = name;
  }

  public Builder withAge(int age){
    this.age = age;
    return this;
  }

  public Builder withGender(int gender){
    this.gender = gender;
    return this;
  }

  public Builder withNation(String nation){
    this.nation = nation;
    return this;
  }

  public Builder withAddress(String address){
    this.address = address;
    return this;
  }

  public User build(){
    return new User(this);
  }


}

说明:这里我们使用了一个静态内部类 Builder 来实现一个建造器

最后我们可以这样来创建一个用户对象:


public class TestUserBuilder{

  public static void main(String[] args){

    User zhangSan = new User.Builder("13579","张三").withAge(22).withGender(1).build();

    User wangWu = new User.Builder("24680","王五").withAge(30).withGender(1).withNation("汉族").build();

    User liLei = new User.Builder("123456","李蕾").withAge(18).withGender(2).withNation("苗族")
                         .withAddress("贵州省黔西南布依族苗族自治州").build();

    System.out.println(zhangSan.toString());
    System.out.println(wangWu.toString());
    System.out.println(liLei.toString());
  }
}

输入结果

id:13579,name:张三,age:22,gender:1,nation:none,address:none
id:24680,name:王五,age:30,gender:1,nation:汉族,address:none
id:123456,name:李蕾,age:18,gender:2,name:苗族,address:贵州省黔西南布依族苗族自治州

4.使用场景

满足一下需求的时候推荐使用建造者模式:

  1. 创建复杂对象的算法应该独立于组成对象的各个部分以及它们的组装方式。
  2. 构造过程必须允许对构造的对象进行不同的表示

5.建造者模式应用例子

6.总结

建造者模式使得对象内部的属性可以独立变化,使用者不必知道对象内部的组成细节,每个建造器相对独立,与其他的建造器无关。建造者模式的应用让对象创建过程更加灵活和易于控制。建造者模式也有相应的弊端,它使得对象的创建过程暴露给外界,让整个对象的 “加工工艺” 变得不透明。

参考

设计模式_工厂方法

设计模式_工厂方法

设计模式三十六计之工厂方法(Factory Method)

解释:工厂方法模式是简单工厂模式的衍生,解决了简单工厂模式的破坏高内聚责任分配原则问题,完全实现了“开-闭”原则和可扩展,其核心创建一个产品对象的工厂接口,将具体的实例化操作推迟到产品子工厂中。工厂类不再负责实例化产品类,它仅仅负责具体子工厂类必须实现的接口。

1. 结构和原理

工厂方法模式是在简单工厂模式的基础上进行了抽象,它包含了一个抽象的Factory类,该类不再负责具体产品类的实例化,而只是负责定义产品生实例化的规范,具体的实例化工作交给子工厂类去完成。

2. 实现工厂方法模式

Product.java

public interface Product {
    public String start();
}

Apple.java

public class Apple implements Product{
    @Override
    public String start() {
        return "iPhone X start...";
    }
}

Huawei.java

public class Huawei implements Product{
    @Override
    public String start() {
        return "P20 Pro start...";
    }
}

Factory.java

  public abstract class Factory{
    abstract public Product createMehod();
    public Product doing(){
      Product product = createMehod();
    }
  }

AppleFactory.java

  public class AppleFactory extends Factory{
    public Product createMehod(){
      return new Apple();
    }
  }

HuaweiFactory.java

  public class HuaweiFactory extends Factory{
    public Product createMehod(){
      return new HuaWei();
    }
  }

Customer.java

public class Customer {

    public static void main(String[] args) {

        AppleFactory appleFactory = new AppleFactory();

        HuaweiFactory huaweiFactory = new HuaweiFactory();

        Apple iPhoneX = appleFactory.createMehod();
        Huawei P20Pro = huaweiFactory.createMehod();

        System.out.println(iPhoneX.start());

        System.out.println(P20Pro.start());
    }
}

控制台输出:

iPhone X start...
P20 Pro start...

3. 总结

优点

  • 符合开闭原则:新添加一种新产品时,只需要增加具体的产品类和相应的工厂子类
  • 符合单一职责原则:每个工厂子类之负责创建对应的产品,工厂类只负责定义生成产品的规范
  • 没有静态工厂方法:可实现扩展,体现了多态性。

缺点

  • 一个工厂子类只能生产一种具体的产品
  • 更换产品时,需要更换生产产品的具体工厂
  • 实现的复杂度增加,系统总类的数量成对增加

适用场景

  • 当一个类希望通过其子类来指定创建对象时
  • 当一个类不需要知道它所需的对象的类时
  • 将创建对象的任务委派给多个工厂子类中的一个来完成时
设计模式_简单工厂模式

设计模式_简单工厂模式

设计模式三十六计之简单工厂模式(Simple Factory)

解释:工厂模式的目的在于当实例化某个对象时,用户不需要知道它的内部细节,只需要知道创建对象的入口(接口)

1. 结构和原理

简单工厂模式的原理是把某些类的实例化工作交由一个独立的类来完成,这个独立的类就称为简单工厂类,这种设计模式就是简单的工厂模式。
在简单工厂模式中,让简单工厂类来决定使用哪一个具体的子类来实例化当前的对象,这样做的好处客户类(获取对象的类)和子类(需要实例化的类)之间实现解耦合,客户类不需要知道子类中的细节以及选择哪一个类来实例化;当子类发生改变时,客户类也不需要进行任何的修改,所有的繁杂工作都将由工厂类负责。

2. 实现简单的工厂模式

  public interface Product{
      public String doing();
  }
  public class Apple implements Product{
    @Override
    public String doing(){
      return "iPhone X";
    }
  }
  public class Huawei implements Product{
    @Override
    public String doing(){
      return "P20 Pro"
    }
  }
  public class OPPO implements Product{
    @Override
    public String doing(){
      return "R17 Pro"
    }
  }
  public class SimpleFactory{

    public static final int APPLE = 1;
    public static final int HUAWEI = 2;
    public static final int OPPO = 3;

    public Product createProduct(int type){
        if(type == APPLE){
          return new Apple();
        }else if(type = HUAWEI){
          return new Huawei();
        }else{
          return new OPPO();
        }
    }
  }
  public class Customer{
    public static void main(String[] args){
      SimpleFactory factory = new SimpleFactory();
      Product phone1 = factory.createdProduct(SimpleFactory.APPLE);
      Product phone2 = factory.createdProduct(SimpleFactory.HUAWEI);
      Product phone3 = factory.createdProduct(SimpleFactory.OPPO);

      System.out.println(phone1.doing());
      System.out.println(phone2.doing());
      System.out.println(phone3.doing());
    }
  }

控制台输出:

IPhone X
P20 Pro
R17 Pro

3. 总结

优点

在简单工厂模式中,工厂类(SimpleFactory)是整个模式的关键,它包含了必要的逻辑判断,通过获取外界给定的信息来决定应该创建哪一个具体类的对象。通过使用工厂模式,客户端类可以创建具体的产品子类中解放出来,客户端类只需要知道如何消费对象就可以了,而不必要去知道究竟是哪一个类实例化了以及该类的内部细节是什么。

缺点

简单工厂模式也存在它的缺陷,由于工程类(SimpleFactory)集中了所有类实例化对象的逻辑,违反了 高内聚 的责任分配原则,将全部创建逻辑集中到一个类中;它所能创建的类只能提前设置好,如果后续有新的子类加入,则需要重新修改工厂类。

适用场景

  • 工厂类负责实例化的对象较少的情况下
  • 客户端类只传入工厂类的参数,而不需要干预产品子类实例化过程的情况下
  • 由于简单工厂模式破坏了高内聚责任分配原则,建议在小范围内使用
设计模式-原型模式(Prototype)

设计模式-原型模式(Prototype)

设计模式三十六计之原型模式(Prototype)

1. 设计思想

将一个对象作为指定的原型实例,并通过克隆此原型来创建新的对象。
原型模式类图

2. 演示案例

关于克隆,大家都知道克隆羊‘克隆羊多利’ ,本章节我们就以‘克隆羊多利’ 来演示原型模式

简而言之

原型模式就是克隆现有的对象来创建新的对象

百度百科:

原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

原型模式允许你创建现有对象的副本并更具实际需要进行修改,而不是从头开始创建新的对象并进行设置。

3. 代码示例

在Java中,一个对象只需要实现Cloneable接口,覆写clone方法,此clone方法名可以自定义,就可以完成原型模式的设计。
首先我们创建一个Sheep类并实现Cloneable接口:

 public class Sheep implements Cloneable{
   private String name;

   public Sheep(String name){
     this.name = name;
   }

   public void setName(String name){
     this.name = name;
   }

   public String getName(){
     return this.name;
   }

   /**
    * 覆写clone方法
   **/
   @Override
   public Sheep clone() throws CloneNotSupportedException{
     return new Sheep(name); //此处是关键
   }

 }

然后我们可以通过如下的方式来克隆一个对象并进行修改:

  public class TestPrototype{
    public static void main(String[] args){
      Sheep oldSheep = new Sheep("oldSheep");
      System.out.println(oldSheep.getName());

      //根据需要对oldSheep进行克隆和修改
      Sheep cloneSheep = oldSheep.clone();
      cloneSheep.setName("cloneSheep");

      System.out.println(cloneSheep.getName());
    }
  }

输入内容:

  oldSheep
  cloneSheep

4. 适用范围

  • 当系统独立于其他产品的创建、组合和表示方式的时候
  • 当要在运行时指定实例化的类时,例如动态加载类
  • 当需要避免重复创建结构相似的工厂类的时候
  • 当一个类的实例可以有几个不同的状态组合时,创建相应数量的原型对象并进行克隆的方式比手动实例化类要简单得多。
  • 当需要降低系统开销时。克隆对象的成本要比创建对象低很多

5. 浅克隆与深克隆

原型模式涉及到 浅克隆深克隆 的概念 。首先我们通过代码来演示来说明什么是浅克隆。定义一个Sheep类实现Cloneable接口,并覆写clone方法,且在原来的代码基础上增加一个Color属性:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:44
 * @modify by :ramostear
 * @info:[原型类:Sheep]
 * @since:
 */

public class Sheep implements Cloneable{

    private String name;

    private  Color color = new Color();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sheep(String name) {
        this.name = name;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        return sheep;
    }

}

说明:代码中增加了Color类的引用,Color类中什么都没有定义,只是一个空的类。

测试代码:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:50
 * @modify by :ramostear
 * @info:[浅克隆测试代码]
 * @since:
 */

public class TestPrototype {

    public static  void main(String[] args) throws CloneNotSupportedException{
        Sheep sheep1 = new Sheep("sheep1");
        Sheep sheep2 = (Sheep) sheep1.clone();
        System.out.println("print hashcode ...");
        System.out.println("sheep1`s hashcode:"+sheep1.hashCode());
        System.out.println("sheep2`s hashcode:"+sheep2.hashCode());

        System.out.println("print color ...");
        System.out.println("sheep1`s color:"+sheep1.getColor().hashCode());
        System.out.println("sheep2`s color:"+sheep2.getColor().hashCode());
    }
}

控制台输出结果:
浅克隆-控制台输入结果

从输出结果可以看到,我们对sheep1进行了克隆,得到sheep2,此时的sheep2是一个全新的引用,但由于Sheep类中加入了另外的一个引用 Color,所以sheep1和sheep2对Color的引用还是同一个,输出的hashcode都为‘356573597’,证明Color的引用没有连带克隆,这就是浅克隆。

如何做到连同Color对象一起克隆而不是指向同一个引用?这就是接下来要讲的深克隆

首先我们对Color类进行一些改造,让Color类同样实现Cloneable接口,并覆写clone方法。

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-15:08
 * @modify by : ramostear
 * @info:[Color类,实现Cloneable接口]
 * @since:
 */
public class Color implements  Cloneable{
    private String color;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Color color = (Color)super.clone();
        return color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Color(String color) {
        this.color = color;
    }
    public Color(){}
}

然后对Sheep的clone方法进行一些调整

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:44
 * @modify by :ramostear
 * @info:[原型类:Sheep]
 * @since:
 */

public class Sheep implements Cloneable{

    private String name;

    private  Color color = new Color();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sheep(String name) {
        this.name = name;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        //增加了对color的设置
        sheep.color = (Color) color.clone();
        return sheep;
    }
}

测试代码保持不变:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:50
 * @modify by :ramostear
 * @info:[深克隆测试代码]
 * @since:
 */

public class TestPrototype {


    public static  void main(String[] args) throws CloneNotSupportedException{
        Sheep sheep1 = new Sheep("sheep1");
        Sheep sheep2 = (Sheep) sheep1.clone();
        System.out.println("print hashcode ...");
        System.out.println("sheep1`s hashcode:"+sheep1.hashCode());
        System.out.println("sheep2`s hashcode:"+sheep2.hashCode());

        System.out.println("print color ...");
        System.out.println("sheep1`s color:"+sheep1.getColor().hashCode());
        System.out.println("sheep2`s color:"+sheep2.getColor().hashCode());
    }
}

输出结果:
深克隆-控制台输入结果

从输出结果可以看到,我们对sheep1进行了克隆,得到sheep2,此时的sheep2是一个全新的引用,且sheep2的color也是一个全新的引用,至此,我们就实现了原型模式的深克隆。

6. Java中的实际案例