Java设计模式(24)-职责链模式

1. 引言 您有没有遇到各种需要走流程的事情?比如,请假申请,假设公司规定,3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。类似的场景还有很多,尤其在工作流中,比如物资审批、报账审批、资金审批等等…… 这里场景中,请求要发送给多个处理者,处理者要能够向后继续转发请求。存在着两种情况: 一方面,每一个处理者有自身能够处理的权限范围,超过权限范围的请求自身不处理,而是将请求转交给上级来处理,如果处理了则终止; 另一方面,每一个处理者可以部分处理请求,处理后可以再将请求发送给上级,也可以决定是否终止。 这样,可能多个处理者处理同一个请求,也有可能只有一个处理者能够处理请求,如下图所示: 多个处理者连成了一条链,请求沿着这条链可以向后传递,这里就会用到今天要讲的职责链模式。我么以请假流程为例,看看使用职责链模式前后的变化,感受职责链模式带来的好处。 2. 请假流程初步 根据前边的场景描述:3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。具备面向对象思维的我们不难写出如下的代码: 1、定义请假请求 请假请求类 class LeaveRequest { private final int days; (1) private final String name; (2) public LeaveRequest(int days, String name) { this.days = days; this.name = name; } // …… 省略getter和toString } 1 请假的天数,它来决定谁可以审批这个请假请求 2 请假人的名称 2、既然请假要求领导审批,我们来抽象一个领导的接口 抽象的领导接口,默认实现了请假审批逻辑 interface Leader { default void handle(LeaveRequest request) { (1) Random random = new Random(47); if (canHandle(request)) { (2) if (random.nextBoolean()) { System.out.println(this.getClass() + " [通过] 了请假请求: " + request); } else { System.out.println(this.getClass() + " [拒绝] 了请假请求: " + request); } return; } throw new RuntimeException(this.getClass() + " 不能处理请假请求: " + request); } boolean canHandle(LeaveRequest request); (3) } ...

2022-01-17 · 3 min · 613 words · Hank

Java设计模式(23)-备忘录模式

中国有句古话:"千金难买后悔药"。生活中很多时候,我们做过了的事情,虽然后悔,却无济于事。但是,在软件世界,我们却可以自己制作"后悔药"。比如,以前玩"仙剑",每每遇到Boss战,那必须要存档的,就算没打过也可以恢复存档再来一次,否则,玩过的人都知道,哭去吧……这里的游戏存档,就会用到今天说的——备忘录模式。 其实还有很多这种例子,比如Word等办公软件的撤销操作,撤销后需要恢复到前一状态;又比如,虚拟机或者今天的云服务器都支持创建快照,如果系统出问题了,可以直接恢复到快照版本…… 1. 概念 备忘录,顾名思义用来做备份的,后续可能按照备份数据进行恢复。DP 对备忘录模式的定义如下: 定义 备忘录模式(Memento Pattern):在 不破坏封装性 的前提下,捕获一个对象的内部状态,将在 该对象之外 保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 这个定义比较好理解,其基本思想就是要将对象的内部状态保存下来,便于之后恢复,但是保存的逻辑不由对象本身负责,而是单独抽出取来,减轻对象的职责,同时也便于扩展。这个思想与前边的 迭代器模式] 非常类似。 需要保存内部状态的这个对象,我们称为 原发器 (Originator),抽取出来的保存原发器状态的对象我们称为 备忘录 (Memento),我们来看看它们之间的关系。 2. 结构 备忘录模式的结构如下: 可以看到,备忘录模式中有三种角色: Originator: 即原发器,持有内部状态,提供创建备忘录的方法,以保存某个时刻内部的全部或部分状态,同时还提供还原的方法,从而支持后续可以还原到保存的状态 Memento: 备忘录对象,它存储 Originator 的状态,但要求它能够防止除了 Originator 之外的对象访问备忘录 Caretaker: 管理者,它负责管理备忘录,包括存储、删除操作,但是要求它本身不能访问和修改备忘录 备忘录的宽窄接口 备忘录对象它要求能够防止除了 Originator 之外的对象访问备忘录,而管理者又不能访问和修改备忘录,因此,要求备忘录能够具有 宽窄两种接口,Originator 能够通过 宽接口 创建、访问、修改备忘录对象,而管理者只能使用 窄接口 存储、删除和允许他人查询备忘录,本身不能修改和访问备忘录。 备忘录模式具有如下的优缺点: 抽取了备忘录对象,用来保存状态,减轻了原发器的职责 备忘录和原发器都可以再次抽象出单独的接口,便于扩展 备忘录会创建多个状态的副本,可能造成很大的开销 管理者管理多个备忘录,但它并不知道备忘录的内部状态,一个小的管理者可能存储和删除很大的备忘录,带来大的开销 备忘录模式适用于以下场景: 对象需要保存自身某一时刻的状态,以便后续进行恢复 对象保存自身状态时不暴露其实现细节,需要保持其封装性不被破坏 3. 实现 备忘录模式的实现大概有三种方式,每一种都有其适用场景。 3.1. 标准实现 标准实现方式,它抽取备忘录为单独的对象,由管理者来存储。我们以仙剑游戏为例,看看示例代码实现,如下: 1、定义备忘录对象 public class Memento { private State state; public Memento(State state) { (1) this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } } ...

2022-01-12 · 3 min · 614 words · Hank

Java设计模式(22)-迭代器模式

生活中,存在这样的场景:需要逐个遍历一堆对象,然后判断对象是否符合要求,做出相应的处理。例如,乘火车时,检票员站在门口挨个检票,没有买票的人不能乘车。类似的场景还有很多,比如乘地铁、乘公交、从书架上找书、食堂排队打饭等等…… 我们把需要逐个遍历的这一堆对象称为 对象集合,把挨个遍历的过程称为 迭代。迭代时,如果能将迭代过程从对象集合中抽取出来单独实现,让对象集合只负责管理自身状态,而不用负担迭代的任务,这就减轻了对象集合的职责,这就是今天要说的——迭代器模式。 也许你会想,对象集合不就是 List 吗,直接使用 "增强for循环" 遍历不就完成迭代过程了吗?说的没错!迭代器模式在很多面向对象的语言中都已经内置了,比如 Iterator,所以我们大多数情况下不会再自己实现它,但是学习模式本身的原理还是非常有必要。 1. 概念 DP对迭代器模式的定义如下:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 这个定义还是比较抽象,简单而言:迭代器模式,提倡将聚合对象(就是前边提的 "对象集合")的迭代逻辑抽取成一个 迭代器,聚合对象本身只需要提供一个返回迭代器的方法即可,而不需要关注迭代逻辑。客户端拿到对象集合的迭代器,就可以遍历了。 这种由客户端来决定如何遍历的方式,被称为 外部(主动)迭代器;还有一种迭代方式,客户端调用一个方法告诉聚合对象它的每一个元素该如何处理,然后由聚合对象内部负责迭代,这被称为 内部(被动)迭代器。Java 两种都支持,比如外部用增强for循环遍历 List,就是外部迭代器,而调用 List 的 forEach(Consumer<? super T> action) 方法,或者调用 Iterator 的 forEachRemaining(Consumer<? super E> action) 方法就属于内部迭代器。 聚合对象中的元素并不一定是有序存储的,比如 Set。此外,迭代器除了从头开始遍历聚合对象,还可以实现从尾部开始遍历,这取决于具体实现。比如,Java 提供了 ListIterator 支持双向迭代,它继承自 Iterator。 2. 结构 迭代器模式的结构如下图所示: 这里我使用的Java的泛型方式来定义的类,标准的迭代器模式显然没有泛型。从图可知,迭代器模式一共分为四个角色: Aggregate<T>: 抽象聚合对象,定义了通用的接口方法,通常是一个对象集合,支持存储多个对象,并支持创建一个迭代器。如 Java的 List、Set、Coleection 等接口 Iterator<T>: 抽象迭代器,定义了迭代的接口方法,如 next 迭代下一个对象,hasNext() 返回是否还有下一个对象等等 ConcreteIterator<T>: 具体迭代器实现,一个 Aggregate 可能存在多个迭代器实现 ConcreteAggregate<T>: 具体聚合对象,如 Java中的 ArrayList,HashSet 等等 ...

2022-01-11 · 3 min · 571 words · Hank

Java设计模式(21)-中介者模式

如果您有租房的经历,那么您对中介并不陌生。租房时,我们先去房屋租赁中介登记,告诉它您的租房需求,然后中介会按照您的要求为您筛选适合的房子。房子确定后,您会与中介签订租赁合同,缴纳费用,然后拿到钥匙……整个过程中,所有事项都由中介一手包办,入住以后房屋有任何问题,您都直接去找中介,您甚至可能并不知道房东是谁。这就是我们今天要说的模式——中介者模式。 中介者模式来源于生活中的各个中介机构,不如前边提到的房产中介、贷款中介、票务中介等等。 1. 概念 DP 对中介者模式的定义是这样的:用一个 中介对象 来封装一系列的对象交互,各个对象 不显式地相互引用,从而使其 松散耦合,而且可以独立的改变它们之间的交互。这完全符合 迪米特法则。 简而言之,中介者模式将多个相互引用的对象解耦,使得它们不直接交互,而是"有事找中介"。 除了生活中的中介机构外,中介者模式在软件中应用也很多,比如,服务网关,各个服务不直接通信,而是都通过网关来互相调用;又比如,MVC 模式中,service 层就是使用典型的中介者模式,因为 service 层依赖了多个 dao 层的类,而 dao 层的类之间并不直接交互…… 2. 结构 中介者模式的类图如下: 存在4个角色: Mediator: 抽象中介者,定义了通用接口让同事类之间进行通信 ConcreteMediator: 具体中介者,实现 Mediator 的具体业务来支持同事类之间的通信 Colleague: 抽象同事类,定义同事类通用接口,内部聚合 Mediator ,通过它与其他同事类交互 ConcreteColleague: 具体同事类,调用 Mediator 实现与其他具体同事类的通信 在中介者模式中,同事类之间互不认识,但是他们都认识中介者,就好比租房时您不认识房东,但是房东和您都认识中介,通过中介来完成通信,因此,中介者是作为一个中心化的角色存在。 优点: 同事类之间完全结构,符合 迪米特法则] 同事类可以扩展和复用,并且减少了它们的子类数量 简化了对象模型,由多对多关系变成了一对多关系 中介者决定同事类之间如何交互,而抽象中介者对这种交互进行了抽象,便于扩展 缺点: 同事类之间的交互完全由中介者来控制,中介者复杂度增加 中心化的中介者出现问题,则同事类之间无法交互,即存在单点问题 3. 适用场景 中介者模式适用于以下场景: 多个对象间存在复杂的依赖关系,结构混乱且难以理解 一个对象引用其他多个对象,并直接与它们通信,导致难以复用该对象 一个对象被多个对象引用,难以扩展,又不想为其生成子类 4. 与其他模式的区别 与观察者模式 上一篇 讲了观察者模式,与中介者模式非常类似。 结构类似,都定义了类似的4个角色,两个抽象角色和两个具体角色 两者实现的功能不同,观察者模式适用于一个对象改变而其他对象也需要做出改变的场景,而中介者模式重点在于解耦对象间多对多的复杂通信关系 中介者模式可以与观察者模式组合使用,如:中介者也作为通知发送者 与外观模式 中介者模式与 外观模式 也类似: ...

2022-01-08 · 2 min · 303 words · Hank

Java设计模式(20)-观察者模式

很快又到年底了,一年一度的春节除了可以享受愉快地享受假期之外,每年的"春节联欢晚会"也是让人倍感期待。然而,大多数人都没能坐在电视机跟前等着它的开始,有的人在厨房忙着准备丰富的食物,有的在一起打麻将、玩牌,还有的可能在玩电脑游戏……大家都想看晚会,于是派出一个小朋友,告诉他:"如果晚会开始了,你要立刻来通知我们哦!"虽然,大家就可以继续做自己的事情,小朋友就坐在电视跟前,当晚会开始的时候,他就立即跑去挨个通知大家…… 观察者模式示意(图片来源网络) 模式源于生活,上边的这个场景,就是一个典型的观察者模式的例子。 1. 概念 先来看看观察者模式的定义。 DP 对观察者模式的定义 观察者模式(Observer),又叫发布-订阅(publish-subscribe)模式,定义了对象间的一种一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新。 生活中,有很多这样的一个对象依赖其他多个对象(一对多关系)的例子,该对象状态发生变化了,其他关联的对象都需要知道并且做出行动。如上边的小孩儿看到晚会开始了,要立即通知其他人,其他人可以选择去看春晚,也可以选择继续做自己的事情。又比如,路口红绿灯变绿时,意思是告诉大家可以通行了;微信群有人发消息了,群成员都可以看到这条消息,等等…… 在软件领域,观察者模式的运用非常多,如:消息队列(MQ) 可以视为观察者模式实现;又比如,响应式编程 的核心就是观察者模式。 2. 结构 观察者模式类图如下: 可以看到,观察者模式有4个角色: Subject: 抽象主题对象,又叫做被观察者(Observable),或者目标对象,定义了添加、删除和通知观察者的方法,知道注册的观察者列表 ConcreteSubject: 具体主题对象,实现 Subject,有自身的状态,状态变化后向各个 Observer 发出通知 Observer: 抽象观察者,定义观察者的通用接口,包含一个用于更新的 update 方法 ConcreteObserver: 具体观察者,实现 Observer,依赖 ConcreteSubject ,当 ConcreteSubject 变化时得到通知,自身也可以更改 ConcreteSubject 的状态,此时可以通知其他观察者更新 上边的几个角色,将抽象和实现相分离,主题对象和观察者都可以各自进行扩展。另外,并不总是主题对象调用 notify 方法去通知观察者们,观察者也可以调用它从而通知其他观察者。 优点: 观察者模式降低了对象间的耦合性,提高了复用性,符合依赖倒置原则 主题对象广播通知给所有观察者,并不关心具体的观察者是谁,易于添加和删除观察者,而具体的更新与否以及如何更新由观察者自身实现 缺点: 观察者自身并不知道其他观察者的存在,它对更改主题状态的代价一无所知,因此需要定义和维护依赖准则,否则可能引起错误更新 主题和观察者之间仍然存在耦合性(没有完全解耦),存在相互依赖关系甚至可能造成循环依赖 3. 适用场景 观察者模式的使用场景: 对象间存在一对多的依赖关系,双方都需要独立的扩展和复用 一个对象的改变会同时改变其他依赖对象 一个对象必须通知其他对象,但是并不知道其他对象具体是谁 4. 示例 接下来看看观察者模式的示例代码(注意,下边的代码没有考虑并发安全性)。 1、定义观察者 public interface Observer { void update(); (1) } ...

2022-01-06 · 3 min · 489 words · Hank

Java设计模式(19)-状态模式

(图片来源网络) 生活中,存在很多与"状态"相关的案例,比如:昨天晚上你熬夜看球赛,上午状态很差,迷迷糊糊的,熬到中午睡了一觉,下午又变得精神百倍了。在不同的时间,所处的状态可能不一样,而且还会按照一定条件流转,比如从犯困的状态变成了精神百倍的状态。这种在不同时间、不同条件下状态产生变化的对象,我们称之为"有状态"对象,状态作为其属性会产生变化。 在软件开发过程中,这种状态转换的场景非常多。比如,系统订单随着时间的推移,状态会产生转换,可能从下单后的待支付转换到支付后的待发货,也可能在发货后从待收货变成已收货,等等。 1. 活动状态案例 假设有一个活动需求,管理员可以在系统中添加活动,让用户来参与,同时可以对活动进行管理,比如禁用启用活动、终止活动等。假设活动的状态有:正常、已开始、已结束、已禁用、已终止等,它们之间的流转过程如下图所示: Figure 1. 活动的状态变化 那么,如何实现活动的状态变化呢?传统的方式是将活动的状态定义为枚举类,然后在代码中进行if..else..或者switch的条件判断,通过编码切换到其他的状态,示例代码如下: 活动枚举类 enum ActivityStateEnum { NORMAL, STARTED, FINISHED, DISABLED, TERMINATED (1) } 1 通过枚举来定义活动的不同状态 通过条件判断来实现状态转换 class NormalActivityStateChange { public ActivityStateEnum change(ActivityStateEnum state) { ActivityStateEnum retState = null; switch (state) { case NORMAL: // 省略具体业务逻辑... retState = ActivityStateEnum.STARTED; (1) break; case STARTED: // 省略具体业务逻辑... retState = ActivityStateEnum.FINISHED; (2) break; case DISABLED: // 省略具体业务逻辑... retState = ActivityStateEnum.NORMAL; (3) case TERMINATED: // 省略具体业务逻辑... case FINISHED: // 省略具体业务逻辑... default: // 其他为最终状态,什么都不做 } return retState; } } ...

2021-06-22 · 3 min · 506 words · Hank

Java设计模式(18)-命令模式

(图片来源于网络) 试想一个去餐厅吃饭的场景,假设餐厅还没有智能化的系统,流程基本上是这样:客人坐下后,服务员会拿出菜单让客人点菜,每点一个菜品就在小本上记录下来,等客人点餐完毕后,服务员将所记录的客人已点菜品清单交给厨房,由厨师来负责烹饪。等待客人用菜完成后,服务员可以拿出记录的点菜清单,然后交给餐厅前台收银员收款结账。这整个过程中,有这么几个角色:客人、服务员、厨师、收银员,现在我们用代码来描述这个场景,怎么来设计呢? 1. 传统的点菜系统设计 传统的方式来设计,我们可能会很容易想到如下的类图关系: Figure 1. 传统方式设计的点菜系统类图 客人直接依赖服务员,使用其点菜服务来点菜,而服务员直接依赖厨师类,通知其完成做菜功能。客人用餐完成后,可以去前台结账。服务员所记录的点菜清单很好的为厨师和收银人员提供了依据:厨师可以借助点菜清单知道自己需要做什么菜,收银人员可以借助点菜清单来计算收款数目。 整个设计初看没有问题,各个功能都实现并且运行的很好。但是,如果某些客人点完菜过后发现,自己点的菜品太多了,可能吃不完而造成粮食的浪费。我们需要系统支持客人取消点菜的功能,一旦客人发现菜品过剩,就可以申请取消,以发扬勤俭节约的美德。 好吧,我们只需要在服务员类上添加"取消点菜"的功能,当服务员收到取消点菜的请求时,他可以在点菜清单上将取消的菜品划掉,然后再交给厨师一个新的点菜清单。此时的类图看起来像这样: Figure 2. 添加了取消点菜功能的类图设计 虽然功能实现了,但是客人、服务员和厨师三者的关系是紧耦合的,随着客人需求的更改,服务员和厨师都需要更改。如果厨师有多个人,每个人负责不同的菜品类别,如炒菜厨师只负责炒菜,而蒸菜、汤品、面食都由不同的厨师负责。那么,客人所点的菜品可能会由多个厨师来完成制作,此时服务员可能会手忙脚乱,因为服务员与厨师紧耦合,客人新增点菜、取消点菜等等请求都需要服务员去与厨师交涉,一旦弄错了对应关系就会造成严重的后果。 其实,服务员与厨师的关系可能是通知和被通知的关系,如果能将这种关系抽象出来,那么服务员和厨师的关系不就解耦了吗?可以将服务员通知厨师看成是给厨师下达一个命令,这个命令有多种支持的操作,如:执行命令、撤销命令、重做命令等等,执行命令时,可以记录日志,以便查阅,就如同服务员手上的"点菜清单",随时都能查看客户点了什么菜、取消了什么菜。 服务员发出命令,我们称之他为请求者,而厨师我们称之为接收者,这种将命令请求者和命令接收者之间添加一层命令抽象的设计模式就是本文要讨论的命令模式。 2. 什么是命令模式 命令模式:将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化,并且可以对请求排队或记录日志,还支持可以撤销的操作。 Figure 3. 命令模式类图 从图上可以看出,命令模式有几个角色: 请求者(Invoker): 请求的发起者,内部聚合了很多命令对象,可以调用这些命令对象执行相关请求,但是不直接依赖接收者(Receiver); 抽象命令(Abstract Command): 定义了命令通用方法的接口或者抽象类(通常包含execute方法表示执行命令),如执行、撤销、重做等; 具体命令(Concrete Command): 实现或者继承抽象命令,但是自己不直接实现业务逻辑,而是内部聚合多个Receiver,调用其相关方法来完成命令的具体业务实现; 接收者(Receiver): 真正实现了命令的相关业务功能,被具体命令对象依赖; 命令模式常见的场景 遥控器,可以控制家用电器的开关、模式切换、频道切换等,可以使用命令模式来设计,遥控器作为请求者(Invoker),电器作为接收者(Receiver); 前文所述的到餐厅点菜,就可以使用命令模式设计,服务员作为请求者(Invoker),而厨师作为命令接收者(Receiver),抽象命令定义点菜、取消点菜等方法; GUI软件系统界面功能的设计,界面包含多个功能按钮,点击后分别实现不同的功能,此时可以使用命令模式,按钮作为请求者(Invoker),系统后端实现按钮点击后真正功能的类作为接收者(Receiver),而抽象命令可以定义按钮的执行方法,如下图所示: Figure 4. GUI对象将命令委派给命令对象 命令模式的优点 完全符合单一职责原则,每一个具体命令(Concrete Command)负责单独的命令逻辑; 完全符合开闭原则,可以在不修改已有客户端代码的情况下在程序中扩展新的命令; 通过引入中间层(命令层)降低了系统的耦合度,便于命令的扩展; 可以实现命令的撤销和恢复功能;可以实现命令的排队和延迟执行; 可以实现将多个命令组合成复杂的命令。 命令模式的缺点 增加了命令层,系统复杂度增加,带来系统理解上的困难; 具体命令类的数量增加导致系统的类数量增加,系统的开发和维护成本增高。 3. 命令模式代码 接下来看看命名模式的基本代码实现: 抽象命名接口(Abstract Command): interface Command { void execute(); } ...

2021-06-14 · 2 min · 356 words · Hank

Java设计模式(17)-策略模式

策略(Strategy),表示实现某种目标的方案集合。这里的方案,在计算机软件中,就是算法。比如,要实现商场打折的目标,那么可能的方案有多种:商品折扣、商品满减、商品积分等等,这些在软件系统里边都代表了不同的算法实现。再举一个简单的例子:假如从公司回家,你可能有几种方式,如乘坐地铁、乘坐公交,又或者是打网约车、自己开车,等等,这些方案都是为了达到回家这一目标,但是他们都是可以相互替换的,你可以今天坐地铁,明天坐公交,后天懒了不想走路,那么直接打网约车也可以。这样的系统怎么来设计呢? 1. 不使用策略模式 如果我们要实现前边"回家方式"的例子,最普通的方式就是通过条件判断来实现。代码如下: class BackhomeService { public static final int RAILWAY = 1; public static final int BUS = 2; public static final int ONLINE_CAR = 3; public static final int DRIVE = 4; void backHome(int type) { if (type == RAILWAY) { System.out.println("乘坐地铁回家"); } else if (type == BUS) { System.out.println("乘坐公交回家"); } else if (type == ONLINE_CAR) { System.out.println("乘坐网约车回家"); } else if (type == DRIVE) { System.out.println("开车回家"); } } } ...

2021-06-07 · 3 min · 444 words · Hank

Java设计模式(16)-模板方法模式

软件设计过程中,有时候我们发现某一业务场景的基本流程是固定的,但是可能不同业务的某些步骤存在一些差异,但是整体上存在很大的共性。比如,现在全国正大力推动新冠疫苗的接种工作,不论是现在的新冠疫苗,还是以前接种的乙肝、流感等等疫苗,接种的流程其实都差不多,比如可能都需要经过预约、前往、现场审核、接种和最后的观察等步骤。现在我们来设计疫苗接种的类图,怎么设计呢?我们可以找出流程中的共性,设计出几个基本的流程骨架,然后将不同的步骤抽象化,由具体子类去实现,这就是"模板方法模式"。 前边的系列文章,我们已经学习了创建型设计模式和结构型设计模式,从本篇开始开始学习行为型设计模式。模板方法模式属于行为型设计模式的一种,比较简单,我们直接切入正题。 1. 模板方法模式 模板方法模式(Template Method Pattern),也称模板模式,其思想是:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 模板方法模式的类图如下: 从图上可以看到,模板方法模式分为2个部分: 抽象模板类(Abstract Template):给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法:模板方法定义了算法的骨架,内部会顺序调用其他基本方法,实现某一算法流程。通常,模板方法会定义为final的以禁止子类重写; 基本方法:基本方法定义了算法流程中的某一个步骤,它可以定义为抽象方法,具体实现交给子类;也可以定义为普通方法,即提供了默认实现,子类可以选择是否重写。还有一种比较特殊的方法,称为钩子方法,这种方法默认是空方法,子类可以选择是否重写。 具体实现类(Concrete Class):实现抽象模板类定义的抽象方法,并依据情况覆盖抽象模板类提供的基本方法,完成自身特定的业务逻辑处理。 优点 模板方法模式的优点: 可扩展性: 模板方法模式封装了程序不变的部分,将可变的部分延迟到子类实现,可以很容的扩展多个具体子类实现 代码重用: 公共代码抽象到父类中,便于代码的复用 缺点 基于继承体系实现,因此具有继承体系的缺点,如在父类添加抽象方法,所有子类都需要实现 存在多个子类,导致类的数量过多,系统变的庞大 2. 示例 模板方法模式较为简单,我们来看一个demo。 案例:以本文开头的疫苗接种为例,疫苗接种需要经过预约、前往、现场审核、接种和观察等步骤,使用模板方法模式设计程序。 程序代码如下: 1、首先,抽象模板类: 代码清单:抽象模板类 abstract class AbstractVaccinate { protected final String name; public AbstractVaccinate(String name) { this.name = name; } // 疫苗接种 public final void vaccinate() { (1) order(); tripMode(); check(); vaccinating(); if (mustObservate()) (5) observate(); afterVaccinated(); } // 预约 public abstract void order(); (2) // 去往接种目的地方式 public void tripMode() { (3) System.out.println(this.name + " 接种前自行前往接种地..."); } // 审核 public abstract void check(); (2) // 接种 public void vaccinating() { (3) System.out.println(this.name + " 接种中..."); } // 观察 public void observate() { (3) System.out.println(this.name + " 接种后观察30分钟..."); } // 钩子方法,是否必须观察 public boolean mustObservate() { (4) return false; } // 接种完成后 public void afterVaccinated() { (3) System.out.println(this.name + " 接种成功!"); } } ...

2021-05-27 · 2 min · 287 words · Hank

Java设计模式(15)-代理模式

虽然马上就是2021年了,但是大家回想起去年(2020年初)的时候,是不是高兴不起来呢?人类还没有彻底战胜新冠病毒之前,我们还是要遵从国家号召:多居家、少聚集、勤洗手、戴口罩!疫情期间,我们足不出户免不了要点外卖。如果没有外卖配送服务,那么你可能得自己去餐厅取餐了。还好,我们可以享受外卖配送服务,只需要点好餐,然后外卖小哥回到餐厅取餐然后直接送到我们手上,这样我们就不需要跟餐厅直接接触了,也减少了感染的风险。其实,外卖小哥就是一个代理,他作为一个中间角色(其实就是中介),帮助用户去餐厅取餐然后送餐给用户,这样用户就不需要直接面对餐厅。 1. 什么是代理 什么是代理?代理就是中介,它提供与服务提供方相同或者相似的功能,客户不再直接面对服务提供方,而是先找代理,再由代理去与服务提供方交互完成功能。现实生活中,有很多代理的例子,比如前边提到的外卖订餐,外卖小哥就是一个代理角色;又如,购买火车票,火车票代售点就是一个代理角色,用户直接可以去代售点购买车票;再如,各种中介所,如婚姻中介所、房产中介等等,都是代理。 在软件中,代理主要指的就是本文要探讨的代理模式,或者模式中的代理类,我们 稍后 再细说。 在网络中,代理服务器(Proxy Server),专门用来代理网络用户去取得网络信息。代理服务器又有正向代理和反向代理的区分。 1.1. 正向代理和反向代理 正向代理和反向代理,其实是按照代理服务所在位置、客户端对代理服务器是否有感知来考虑的。 1、正向代理 正向代理,其服务器位于客户端,客户端需要访问目标服务器,但是不会直接访问,而是先访问代理服务器,然后再由代理服务器转发请求到目标服务器。也就是说,正向代理其代理的是客户端的请求,客户端明确知道代理服务器的地址,然后在连接到代理服务器使其转发请求到目标服务器。其原理如下: Figure 1. 正向代理示意图 如图所示,客户端链接到代理服务器(Proxy),客户端和代理服务器位于同一个网络中。 正向代理的主要用途是,客户端由于某些原因不能直接访问目标服务器,但是代理服务器可以,因此通过代理服务器作为跳板从而达到访问目标服务器的目的。例如,我们需要访问Google网站,但是国内无法访问,我们需要使用一些代理服务或软件(它们可以访问Google),这些服务和软件就是正向代理服务器。各大操作系统都有代理服务设置,例如macOS的: Figure 2. MacOS中网络代理设置 2、反向代理 反向代理,其实是相当于正向代理而言的。在反向代理中,代理服务器与目标服务器位于同一网络中,代理服务器代理并隐藏目标服务器,客户端无需知道代理服务器的存在。原理如下: Figure 3. 反向代理示意图 代理服务器就像服务端的一道防火墙,既可以过滤掉非法的请求,又可以隐藏目标服务其的真实地址等信息,对外提供统一的访问入口,提到服务端的安全性。另外,代理服务器能够实现网络负载均衡,将请求分发到不同的目标服务器上,以缓解请求压力,提高访问速度和性能。 反向代理在网络应用中使用非常普遍,常见的软件如Nginx、Apache等都可以作为反向代理服务器。 1.2. 为什么要使用代理 前边说过了正向代理和反向代理的作用,总结一下,代理有如下的作用: 访问客户端不能直接访问的服务器,比如Google、Facebook等 提高网络访问速度,例如访问某些网站速度很慢,但是代理服务器访问之很快,这样客户端通过访问代理服务器可以提高网络速度,比如常见的网络加速器 实现安全保护,比如反向代理服务器可以设置规则过滤非法请求,也可以隐藏服务器IP地址和端口以保护后端服务器,还可以做权限认证等 实施负载均衡,反向代理服务器可以按照既定策略将不同的请求分发到不同的后端服务器上,缓解其访问压力,如常见的轮询、按IP Hash、按请求URL hash、随机选择、最小访问、按地域选择等负载均衡策略 实现动静分离,反向代理服务器支持直接访问静态资源,不需要后端服务器支持,这样可以加速静态页面的访问,如Nginx对静态资源的访问支持非常好,性能强大 了解了什么是代理,接下来我们看看在计算机软件中,代理模式的概念。 2. 代理模式简介 技术来源于生活,代理模式是一种生活中代理运用的总结。DP对代理模式(Proxy Pattern)的定义如下:给对象提供一个代理以控制对该对象的访问。 当客户端不能直接依赖目标对象,又或是想要增强目标对象的功能,可以为其创建一个代理对象(中介),然后客户端访问代理对象而不直接依赖目标对象,再由中介对象来访问目标对象。这种设计模式就是代理模式,其结构如下图所示: 可以看到,代理模式有几种角色: 抽象主题角色(Subject: 抽象类或接口,用来定义业务功能的公共方法 真实主题角色(RealSubject): 实现了抽象主题(Subject)接口,完成具体业务功能实现。它就是目标对象,客户端需要依赖它提供的功能 代理对象(Proxy): RealSubject的代理实现,保证与其具有相同的功能,因此也会实现Subject接口。代理对象内部会聚合RealSubject,具体功能会调用RealSubject来完成,同时内部也可以增强RealSubject的功能 从结构上看,代理模式与 适配器模式 类似,两者都存在一个中间对象,其都会聚合具体对象并实现一个抽象接口,但是它们的目的并不相同:代理模式更强调对象的访问控制和功能增强,而适配器模式更强调功能的转换。 代理模式的主要优点: 代理模式在客户端与目标对象之间增加了中间对象,可以控制目标对象访问 代理对象可以扩展目标对象的功能 其主要缺点: 代理模式会造成系统设计中类的数量增加 在客户端和目标对象之间增加一个代理对象,中转过程会影响请求速度 增加了系统的复杂度 3. Java中代理实现的三种方式 代理可以分为静态代理和动态代理,动态代理技术有效的减少了上边的缺点。 ...

2020-12-31 · 3 min · 588 words · Hank

Java设计模式(14)-享元模式

享元模式,"享"即共享,"元"即元素,软件中则为对象,享元就是共享对象之意。这种设计模式用来共享对象而不是大量创建对象,以节约系统资源。现实中,很多东西都可以使用享元模式来解决,比如围棋、五子棋,棋子的颜色就黑白两种,只是他们在棋盘的位置不同;又如,展示类网站,多个用户公用一套系统,只是内容和展示形式存在差异;再如,教室的课桌和凳子…​上述示例都有一个共同点:相关的东西存在很大的相似,但是也不完全相同,此时如果要开发软件,那么都可以用享元模式来设计。 1. 从围棋游戏开始 假设要你开发一款围棋游戏,我们知道,围棋棋子有黑色和白色两种,棋子在棋盘上的位置随着下棋的进行而不同,那么你怎么设计类关系? 常规的思路是使用工厂模式,棋手每落下一枚棋子,那么就创建一个棋子实例,并设置它放置的位置: Figure 1. 使用工厂模式 但是,围棋有 19 × 19 = 361 个交叉点,假设每个点上全部都有棋子,那么足足需要创建361个对象!这无疑是对系统资源的浪费。 那么,有好的解决办法吗?答案是使用享元模式来设计。 2. 什么是享元模式 享元模式(flyweight),也叫蝇量模式,它强调对象的细粒度控制和共享,主张运用共享技术有效地支持对象的复用。"享"即共享,"元"即元素,软件中则为对象,说白了就是最大程度的让对象可以共享并复用。享元模式的类结构如图所示: Figure 2. 享元模式类结构 如图所示,可以看出,享元模式有如下几种角色: 抽象享元角色(Flyweight):具体享元对象的超类或接口,可以接受作用于外部状态(见后文); 具体享元角色(ConcreteFlyweight):继承或实现抽象享元角色,为内部状态增加存储空间; 非共享享元角色(UnsharedConcreteFlyweight):同样继承或实现抽象享元角色,但是这些类并不需要共享出来; 享元工厂角色:管理Flyweight对象,确保合理的共享他们。它往往提供获取Flyweight对象的方法,当对象存在时直接返回,否则创建一个。 提示 有的书上也将UnsharedConcreteFlyweight对象单独提出来,将其看做外部状态的抽象,它并不实现Flyweight,而是作为Flyweight接口方法的参数进行传递,以说明它不共享。 享元模式的结构也好理解:抽象一个享元接口,提供通用的方法,然具体享元对象实现该接口,但是并不是所有逇具体享元对象都需要共享,因此按需共享并拆分,最后由享元工厂统一管理他们。上边还提到两个概念:外部状态和内部状态,它们是什么? 2.1. 外部状态和内部状态 在享元模式中,对象状态按是否共享分为两种:共享和不共享,随着环境变化而改变的、不可以共享的状态称为外部状态;相反,不会随着环境变化而改变的状态称为内部状态。 使用享元模式,重点是要分析出哪些对象是外部状态,哪些是内部状态,并将外部状态单独抽象出来,作为一个变化的部分。 在前边围棋的例子中,棋子的颜色只有黑、白两种,它们不会随着下棋的进行而增加或者减少,因此棋子的颜色是内部状态。而棋子在棋盘上放置的位置,随着下棋的进行,棋子的位置都会不同,因此,棋子的位置是外部状态。 又比如,多个用户共用一个网站,网站可以作为Flyweight对象而共享,而网站的代码、模板、数据库都是不会变化(增加或减少)的,它们可作为内部状态来共享。但是,不同的用户账号是不同的,每个用户都有自己额账号,因此,账号可以作为外部状态。此时就可以将账号单独提取出来作为一个变化的实体: Figure 3. 共享网站的设计类图 3. 围棋游戏改进 我们再来看看如何使用享元模式来解决围棋中重复创建对象、浪费资源的问题。 首先,前边已经分析了,围棋棋子的颜色是内部状态,而其在棋盘的位置是外部状态。那么,我们可以设计如下的类图: Figure 4. 围棋使用享元模式设计的类图 内部对象定义为Color类,外部对象抽象为Position类,Piece为抽象的Flyweight对象,WeiqiPiece作为具体的享元对象实现了Piece接口。最后,这些对象通过WeiqiPieceFactory统一管理。示例代码如下: 1、定义外部状态类: // 外部状态: 棋子位置 class Position { private final int x; private final int y; public Position(int x, int y) { this.x = x; this.y = y; } public String position() { (1) return "(" + x + "," + y + ")"; } } ...

2020-12-21 · 2 min · 326 words · Hank

Java设计模式(13)-外观模式

在现实生活中有很多这样的例子,比如,炒股,散户股民大多对股票金融知识缺乏,所以他们想要炒股赚钱往往看运气,但是散户想要保证低风险的投资和拿到稳定回报,怎么办呢?大多数人会选择购买基金,相对于股票,散户们不需要专业知识,只需要将自己交给基金会,由专业人士替他们购买股票,赚到钱后再按资金比例分给他们,这样就大大降低了投资风险。 Figure 1. 股票与基金示意图 基金作为一个中间对象,它解决了股民专业知识匮乏却也能投资股票赚钱的需求。其实,这就是外观模式。 1. 外观模式简介 外观模式(Facade Pattern),也叫门面模式,是一种经常被使用的结构性设计模式。DP对其定义如下:通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。 还记得 迪米特法则] 吗? 迪米特法则(Law of Demeter,LoD),又叫作最少知识原则(Least Knowledge Principle,LKP),描述了如何设计对象之间的依赖关系,它的定义如下:一个对象对自己所依赖的对象知道的越少越好。更进一步的意思,即:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。 外观模式严格体现了迪米特法则,它降低了系统之间的耦合性和复杂度,提高了系统的可维护性。 外观模式有三种角色: 外观(Facade)角色:整合多个子系统,为其提供统一的接口实以现业务需求; 子系统(SubSystem)角色: 实现某方面的业务功能,供外观角色调用,但是并不知道外观角色的存在; 客户端(Client)角色:调用外观角色访问各个子系统实现不同的业务功能。 其类结构如下图所示: Figure 2. 外观模式类图 我们在软件开发中,经常会有意无意的使用外观模式。比如,MVC模式业务开发中,Service层会屏蔽Dao层的实现细节,对外只暴露一个方法,该方法内部可能会需要使用多个Dao来完成数据库操作。又比如现在流行的微服务架构,API网关就承担着外观角色,客户端直接访问网关,而不会直接去请求各个具体的服务。 Figure 3. API网关示意图 再举一个现在比较专注的例子:智能家居。当你装上了智能家居系统,那么你就可以一键控制家用电器的开关,是不是很舒服?比如,你早上起床,控制器会一键为你打开电动窗帘、播放闹钟、关闭空调等等,再也不用一个一个去操控了。我们看看怎么使用外观模式解决这个问题。 2. 应用案例:智能家居 假设我家安装了一些智能设备:智能空调、智能音响、自动窗帘、智能电灯等,现在我可不想一个个去控制它们。是时候请出超级管家了,它来负责一键操作这些设备。设计类图如下: Figure 4. 智能家居简易设计类图 智能管家作为指挥中心,它具有问好、叫醒起床、上班前告别、下班回家欢迎等功能,当然,假设我们可以通过语音和App操控它。这些功能,智能管家自己不会实现,而是通过调用下边的各个智能设备共同完成。我们看看代码如何实现。 1、智能设备,多个子系统,提供了自身的功能方法。 // 窗帘 class SmartCurtain { public void open() { System.out.println("打开窗帘"); } public void close() { System.out.println("关闭窗帘"); } } // 灯光 class SmartLight { public void open() { System.out.println("打开灯光"); } public void close() { System.out.println("关闭灯光"); } } // 空调 class AirConditioner { public void open() { System.out.println("打开空调"); } public void close() { System.out.println("关闭空调"); } } // 智能音响 class SmartSoundBox { public void play() { System.out.println("播放音乐"); } public void stop() { System.out.println("停止播放音乐"); } } ...

2020-12-08 · 2 min · 271 words · Hank

Java设计模式(12)-组合模式

1. 整体与部分的关系 很多场景下,需要考虑整体和部分之间的关系,在对其进行管理功能设计时,需要考虑到灵活性和扩展性,而继承关系往往不能很好地解决这些问题。一个最典型的例子就是:组织机构。比如,就公司而言,总公司可能下设有分公司,分公司下又有办事处,而办事处、分公司、总公司可能都会存在一些职能部门。很明显,组织机构是一个整体和部分的关系,并且它是一颗树状结构。 Figure 1. 复杂的组织机构树状结构 设计这样的结构,你是否会使用继承关系?如让分公司继承总公司、办事处继承分公司,这样往往是从机构的大小维度来划分的,子类除了具备父类的功能外,还能够独立扩展功能。但是,在组织机构树中,其实每个节点(机构)所承担只能都是差不多的,换言之,总公司、分公司、办事处三者其实并没有父子关系,在管理职能上他们的功能是相同的,比如都能添加、删除、查询子的机构节点。所以采用继承关系来设计其实是不合适的。 还有很多整体-部分关系的例子,比如学校、学院和系的关系,电脑组装商家可以出售整机也可以出售配件,文字处理软件中单个文字和整段甚至整篇文字的处理方式差不多,又如Java AWT、Swing中的容器组件和简单控件的关系。其实,这一类问题,最终解决的都是整体和部分被一致对待的问题,组合模式很好的解决了这个问题。 2. 组合模式简介 在Design Patterns一书中对组合模式的定义如下: 组合模式(Composite Pattern),将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 组合模式是一种结构型设计模式,其"对象组合成树形结构"定义代表了对整体部分关系的抽象,将树形结构抽象为三个部分:根节点、树枝节点和叶子节点,其中,根节点、树枝节点可以添加子节点,而叶子节点不能添加子节点。因此,组合模式有两种方式:透明方式和安全方式。 在说明这两种方式之前,先看看组合模式的角色: 抽象构件(Component):对象组合的高度抽象构件,声明了对象操作的公共接口。透明方式和安全方式对节点管理方法的声明存在差异:透明方式会声明管理节点的接口,如添加、删除,但是安全方式不会声明管理接口,而是将其移交到树枝节点声明和实现,具体后边再说; 树枝构件(Composite):树形结构的分支节点,实现抽象构件,同时承担管理子节点职能,如实现添加、删除节点的方法; 树叶构件(Leaf):树形结构的叶子几点,没有子节点,所以不承担管理职能,它实现添加、删除等管理职能方法是没有意义的; 透明方式和安全方式主要区别在于:客户端是否需要区分树枝构件(Composite)和树叶构件(Leaf),透明方式则不区分,而安全方式则需要区分。 2.1. 透明方式 透明方式,不区分树叶构件和树枝构件,两者都实现构建构件的api,因此,树形结构管理职能的API(添加、修改、删除等)声明在抽象构件中,客户端使用时不需要区分树枝和树叶,因为他们具有相同的方法。类图如下: Figure 2. 透明方式类图 这种方式的好处在于,客户端不需要判断树叶构件和树枝构件,因此对客户端是统一的或者说透明的,方便管理;但是前边我们说过,树叶构件是没有子节点的,它不承担管理职能,实现管理职能的方法没有意思,因此我们只能进行方法空实现或者抛出异常,这一点不太友好。 2.2. 安全方式 相对于透明方式,安全方式显得比较保守。它只在抽象构件中声明公共方法,管理职能的方法放到树枝构件中进行声明和实现,这样树叶构件就不会存在管理职能的方法了。类图如下: Figure 3. 安全方式类图 这种方式的优点在于,树叶构件不需要对管理职能方法做无意义的空实现或抛异常,代码更严谨,但是其缺点也很明显:客户端需要明确知道调用的是树叶构件还是树枝构件,相对而言比较麻烦。 3. 应用示例 看一个例子。大学中分为多个学院,每个学院下又存在多个系(专业),我们看看如果运用组合模式来实现。 分析:学校、学院、系属于整体和部分关系,我们将其看做同一个整体并进行抽象,得到一个组织机构接口,让他们分别实现该接口。同时,学校、学院下都包含子节点,因此他们可以看做树枝构件,内部持有一个List来保存子节点数据,而系下不再有子节点,它属于树叶构件。 Figure 4. 学校学院系类图 类图中,Organization为抽象构件,声明了add、remove两个管理职能方法,而业务处理方法只有一个print方法,用来打印自身和其下的所有子节点。 从类图可以看出,我们使用的是组合模式的透明方式。 具体代码如下: 1、抽象构件: interface Organization { void add(Organization org); void remove(Organization org); void print(); } 2、树枝构件: ...

2020-12-07 · 2 min · 240 words · Hank

Java设计模式(11)-装饰模式

我同事大头,喜欢吃面。这天大头去一家面馆吃面,由于大头饭量大,面没吃完觉得不够,有点了两个卤蛋,一会儿再叫了一份青菜,最后时刻又加了一碗豆浆。面我们称为主食,必须要点,其他称为小吃,可以随意组合点单也可不点,假如面的价格是8元,卤蛋2元一个,青菜5元一份,豆浆3元一碗,现在要计算总共价格,如何设计?要求具备良好的扩展性和维护性。 1. 大头吃面第一版 既然面馆提供这么多好吃的东西,最容易的想到的就是在类中增加方法,初版设计类图如下: Figure 1. 大头吃面第一版设计类图 这种方式,将点主食、小吃分别加到管理类中,然后计算总价,实现代码如下: class NoodleRestaurant { private int totalPrice; void orderNoodles(int count) { System.out.println("点了" + count + "份面"); totalPrice += 8 * count; } void addEggs(int count) { System.out.println("点了" + count + "份鸡蛋"); totalPrice += 2 * count; } void addVegetables(int count) { System.out.println("点了" + count + "份青菜"); totalPrice += 5 * count; } void addSoySauce(int count) { System.out.println("点了" + count + "份豆浆"); totalPrice += 3 * count; } public int getTotalPrice() { return totalPrice; } } ...

2020-11-05 · 4 min · 744 words · Hank

Java设计模式(10)-桥接模式

有这样一个需求:手机都有发短信、打电话等功能,而且手机有各种各样的样式,比如翻盖手机、直板手机、滑盖手机、折叠手机等等,同时,手机还有各种各样的品牌,苹果、洛基亚、华为、小米、VIVO等等,我们应该如何来设计类之间的关系,并保持良好的可扩展性呢? 1. 糟糕的继承设计方式 通常,我们会使用简单的继承的方式来设计类,因为继承的方式我们最容易想到,继承设计的类图如下: Figure 1. 继承的方式设计类图 上边的类设计,从手机样式的维度出发,将手机划分为翻盖手机、直板手机,他们继承自"手机"父类,下边的各个品牌在分别按照手机样式分类,继承自样式父类。这样的设计,表面上能够解决问题,但是带来的非常大的缺点: 类非常多,每一种手机样式下,所有的品牌都需要建立这种样式的类,我们将这种现象称为类爆炸; 难以扩展和维护,增加一种手机样式,所有的品牌都需要在这种样式下添加新的类,如果增加一种品牌,那么原有的所有样式下都需要增加新品牌的类,这违反了单一职责原则 [1] 和开闭原则 [1] 前边 面向对象设计模式遵循的原则 一文时我们说过,继承最主要的缺点在于其破坏了类的封装性,父类的修改也会导致子类的变化,子类和父类的依赖关系非常紧密。因此,在考虑使用继承关系时,首先要明确类之间是否是明确的is a的关系,而不是like a,并遵循里式替换原则,另外,合成复用原则也指出:设计类时优先考虑使用合聚合和组用而不是继承。 桥接模式能够很好的解决像这样存在多个维度变化对象(比如这里的手机样式和品牌)的设计问题。 2. 桥接模式介绍 2.1. 桥接模式简介 桥接模式(Bridge Pattern)的定义如下:将抽象部分与它的实现部分分离,使他们都可以独立变化。 桥接模式包括如下的几种角色: 抽象化(Abstraction)角色:抽象类,并包含一个对实现化对象的引用 扩展抽象化(Refined Abstraction)角色:抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用 具体实现化(Concrete Implementor)角色:实现化角色接口的具体实现 几种觉色之间的关系如下图所示: Figure 2. 桥接模式的结构类图 抽象化角色、实现化角色将桥接模式结构分为两个部分:抽象部分由抽象化角色作为父类,实现部分由实现化角色为父类。然后抽象化角色依赖了实现化角色,中间的依赖线类似一座桥梁,很形象的说明了桥接模式的命名由来。从上边的类图可以看出,桥接模式是合成复用原则的体现,通过抽象化角色聚合实现化角色,将双方联系起来,这样既便于扩展,又能减少类的数量。 桥接模式的优点: 多个变化维度拆分为抽象和实现两个部分,都可以独立变化,程序易扩展 遵循开闭原则,添加新类时,仅需要修改客户端调用代码,而其他代码不需要变化,对扩展开放对修改关闭 遵循合成复用原则 注意,桥接模式定义中说的抽象与实现分离,并不是说将抽象类和其派生子类分离,这样做并无意义。这只是告诉我们在设计类时,首先需要明确可能变化的维度,将多个变化的维度都单独抽取出来,使得他们都可以独立的扩展。 比如前边的设计样式和手机品牌,这两个维度都会变化,可以将其抽象出来,手机的基本功能如打电话、发短信可以放到品牌中来实现,它就是作为实现部分,而手机样式作为抽象部分实现手机样式的不同功能。具体类图如下; Figure 3. 桥接模式解决手机设计问题 现在,我们编码来实现上述手机问题。 2.2. 桥接模式示例代码 1、首先,抽象手机品牌接口PhoneBrand,它作为实现化角色(Implementor): 实现化角色 // 手机品牌接口 interface PhoneBrand { // 获取品牌名称 String getName(); // 打电话 default String call() { return "用" + this.getName() + "手机打电话"; } } ...

2020-11-04 · 2 min · 323 words · Hank

Java设计模式(9)-适配器模式

模式来源于生活,适配器模式最能直观的体现这一点。适配器,就是将多个原本不能共同工作的产品经过一定的处理,使得他们能够彼此协同工作的一种装置。现实生活中,有很多适配器的例子,如常见的电源适配器,可以将220V的交流电压转换为手机、电脑等电器所需的低电压;又比如,苹果手机的type-C耳机插孔,不能使用3.5mm的耳机,怎么办呢?可以增加一个耳机转换器,它就是一个适配器,能够将3.5mm耳机成功用于Type-C接口的iPhone上;还有各种转换器,如HDMI转VGA、Type-C转USB等等,都是适配器。 1. 适配器模式简介 适配器模式(Adapter pattern)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 对于这个定义,需要明确几点: 首先,有一个已经存在的类A,但是不满足我们的需求,我们需要的是类B; 其次,A、B完成的功能相似,或者说,类B的功能类A其实都已经实现了,我们不想再实现一次; 第三,A、B不能共同完成工作,我们需要经过适配将A、B变得可以协同工作。 所以,什么时候可以考虑使用适配器模式? 当两个类所做的事情相同或相似,但是他们不能协同工作,此时可以通过适配器模式,但是有一个前提,就是两个类都不可能修改,如果他们频繁变动,那么首先要考虑的是重构代码来使他们统一。比如,遗留的系统、老的系统组件,他们中已经实现的功能,几乎不会修改,可以使用适配器模式。 适配器模式的主要优点如下: 客户端通过适配器可以透明地调用目标接口,更简单、直接; 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类; 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。 适配器模式有三个主要角色: 目标(Target)接口:当前业务系统所期待的类,可以是抽象类或接口 被适配者(Adaptee):即现有系统或组件中已经存在的类,实现了我们所需的功能 适配器(Adapter):一个转换器,将被适配者转换为目标,让客户可以按目标接口透明的使用被适配者的功能 在java语言中,不能多继承,所以适配器模式中,我们一般会将目标申明为接口,然后通过继承或者依赖被适配者的方式来实现适配器。 适配器模式有三种形式:类适配器、对象适配器、接口适配器。 2. 类适配器模式 顾名思义,类适配器适配的是类。要想使用类的功能,java中我们只能用继承的方式,但是这违背了合成复用原则原则。尽量不要使用继承,而是使用合成的方式。 因此,类适配器最典型的缺点是,如果要替换适配器,将会变得很复杂,这种方式应该尽量不用。 这种形式的类结构如下图所示: Figure 1. 类适配器模式 Aapater类继承被适配者Adaptee,并实现目标接口Target,因此客户端可以透明的使用Target接口的api,而Adapter内部则使用Adaptee来实现Target定义的相关功能。 举个例子:用手机充电器给手机充电,假设手机需要使用5V的直流电才可以充电,但是家用电都是220V交流电,我们看看如何使用适配器模式解决这个问题。 1、首先,我们有220v的交流电,这就是被适配者Adaptee: 被适配者 class AlternatingCurrent { int outputVoltage() { (1) return 220; } } 1 被适配者拥有一个输出220V的交流电方法。 2、然后,我们新定义一个输出5V直流电的接口,这就是目标接口: 目标接口 interface DirectCurrent { int outputVoltage(); } 3、最后,定义适配器,进行适配: 适配器 class PhoneCharger extends AlternatingCurrent implements DirectCurrent { @Override public int outputVoltage() { int voltage = super.outputVoltage(); // 电压转换处理 return voltage / 44; (1) } } ...

2020-08-24 · 2 min · 299 words · Hank

Java设计模式(8)-建造者模式

建造者模式(Builder Pattern),旨在解决复杂对象的创建过程,将对象与其创建过程分离,通过相同的创建过程创建多种不同的对象。 生活中有很多复杂对象构建的例子。例如,汽车生产线的组装,都要经过车架、车身、变速箱、发动机、车轮等零部件的组装过程,如果将这些组装过程单独抽取出来,将组装结果——汽车独立出来,那么这个组装过程就可以重用,比如组装不同颜色、不同品牌的各种汽车。再比如,建房子,需要经过打地基、搭框架、砌墙、封顶等一系列复杂过程,如果对这个过程进行抽象,将房子和其建造过程分离,则更灵活,如通过这个建造过程可以建造高层建筑、低层建筑、普通民房等房屋。 1. 什么是建造者模式 建造者模式(Builder Pattern),又叫生成器模式,将复杂对象的创建过程和对象本身进行抽象和分离,使得创建过程可以重用并创建多个不同表现的对象。 优点: 各个具体的建造者相互独立,有利于系统的扩展; 客户端不必知道产品内部组成的细节,便于控制细节风险。 缺点: 产品的创建过程相同才能重用,有一定的局限性; 产品的内部创建过程变化。 2. 建造者模式结构 建造者模式有4个角色: 产品角色(Product):复杂对象,复杂的创建过程中包含多个创建步骤,由具体建造者来实现各个创建步骤的业务逻辑; 抽象建造者(Abstract Builder):抽象产品角色的多个创建步骤,可以是接口和抽象类,通常会定义一个方法来创建复杂产品,一般命名为build; 具体建造者(Concrete Builder):实现抽象建造者中定义的创建步骤逻辑,完成复杂产品的各个部件的具体创建方法; 指挥者(Director):调用抽象建造者对象中的部件构造与组装方法,然后调用build方法完成复杂对象的创建; 他们之间的关系如下图所示: Figure 1. 建造者模式类结构图 Director内部组合了抽象的Builder,用来构建产品;Client依赖Director创建产品,但是需要告诉它使用什么具体的Builder来创建,也就是会依赖具体的构建器. 抽象的Builder有两种形式:抽象类和接口。图中抽象的Builder为抽象类,其内部组合依赖了Product,并在build方法直接返回它;如果抽象Builder为接口,那么内部不会依赖Product,类结构上也会有一些变化,如下图所示: Figure 2. Builder为接口时的类结构图 最显著的区别是,具体构建器需要实现build方法,返回具产品信息。标准设计模式提供的是一种思路,在具体实现的时候有很多形式,但是其核心思想是不变的。 3. 示例 下面我们看一个简单的示例:我们建设生产一部普通汽车,都需要经过组装底盘、组装变速箱、发动机、车架等步骤,如果我们把汽车和它的生产过程分离开,使用建造者模式来实现,该怎么做呢? 1、首先,汽车就是我们需要建造的产品,其定义如下: 产品 class Car { // 发动机 private String motor; // 变速箱 private String gearbox; // 底盘 private String chassis; // 车架 private String frame; } 为了简单,这里省略了getter和setter。这里的产品已经是具体的产品了,实际上,这个产品一般是抽象的产品,下边可能会有多个具体的产品信息。 2、然后,抽象汽车生产过程,定义抽象的建造者: 抽象建造者 abstract class CarBuilder { protected Car car = new Car(); // 构建发动机 abstract void buildMotor(); // 构建变速箱 abstract void buildGearbox(); // 构建底盘 abstract void buildChassis(); // 构建车架 abstract void buildFrame(); public Car build() { return car; } } ...

2020-08-23 · 2 min · 423 words · Hank

Java设计模式(7)-原型模式

某些情况下,我们需要重复的创建多个对象,但是这些对象仅仅只有某几个属性不一致,大部分的信息都是相同的,如果使用传统的构造函数来创建对象,我们需要不断的实例化对象,并且反复调用属性的set方法来设置值,而这些值大多都是相同的。有没有一种模式,能够快速而高效的创建这些差别不大的对象呢?这就需要使用到原型模式。 1. 什么是原型模式 原型模式(Prototype Pattern),它的基本思想是:创建一个对象实例作为原型,然后不断的复制(或者叫克隆)这个原型对象来创建该对象的新实例,而不是反复的使用构造函数来实例化对象。 原型模式创建对象,调用者无需关心对象创建细节,只需要调用复制方法,即可得到与原型对象属性相同的新实例,方便而且高效。 举一个最常见的例子,猴王孙悟空本领大,拔下猴毛一吹,就可以得到很多个与自己一模一样的猴子猴孙。这里就可以使用到原型模式,来复制孙悟空。另外,再举个生活中的例子,刚毕业找工作的同学们,都需要填写病打印纸质的简历,但是这些简历信息只有你想要投递的公司信息不一样,其他的信息如个人基本信息、教育经历、工作经验等都是相同的,我们就可以使用原型模式复制简历,然后修改公司信息即可,而无需重复创建多个简历,在一遍遍填写。 2. 原型模式结构 原型模式的结构如下图所示: Figure 1. 原型模式结构图 ` 结构分为三个部分: Prototype: 原型抽象接口,提供复制(clone)方法,以便实现类实现该方法来复制自己 ConcretePrototype: 具体原型对象,实现 Prototype 接口的复制方法来复制自己,从而创建新实例。 Client: 负责调用原型对象的复制方法获得原型对象新实例,并按需修改新实例 3. Java中的Cloneable接口 Java语言提供了一个 Cloneable 接口,这是一个标记型接口,用来表示实现了该接口的对象可以进行克隆,其定义如下: public interface Cloneable { } 真正的实现克隆的逻辑其实是在 Object 类上: public class Object { protected native Object clone() throws CloneNotSupportedException; // …… } 因此,Java实现原型模式比较方便,只需要实现 Cloneable 接口即可,克隆时调用对象自己的 clone 方法即可。但是,clone 方法是一个native实现,其实它仅仅实现了浅拷贝,稍后再细说。 4. 基础示例 接下来,我们编码实现前边所举的猴王孙悟空分身的例子,首先看看常规方式是如何实现的。 4.1. 常规的实现方式 先来编写一个 MonkeyKing 类,它有名称、居住地、技能强度、寿命等属性: class MonkeyKing { // 高强度 static int HIGH_SKILL_STRENGTH = 10; // 普通强度 static int NORMAL_SKILL_STRENGTH = 5; // 姓名 private String name; // 地址 private String address; // 能力强度 private int skillStrength; // 寿命 private int lifetime; // 省略getter、setter } ...

2020-07-15 · 4 min · 653 words · Hank

Java设计模式(6)-抽象工厂模式

如果工厂模式中需要生产多种类型的产品,那么工厂方法模式就适合了,需要用到抽象工厂模式。 1. 简介 抽象工厂模式,定义抽象工厂,并将产品生产延迟到具体工厂实现中,而它生产的产品都是同种类型的。比如,电视机工厂生产的都是电视机,电视机是一个抽象产品,而它的具体实现有黑白电视、彩色电视、液晶电视等等,但是都属于同一种产品类型-电视机。 如果现在电视机工厂不仅生产电视,还能生产空调等其他电器了,那么,我们称电视机、空调等不同类型的产品为产品簇,代表不同类型的多种产品。 Figure 1. 产品簇示意图 现在,工厂方法模式不适用了,我们需要使用抽象工厂模式。 抽象工厂模式(Abstract Factory Pattern)的定义如下:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类,访问类无须指定具体的类就可以得到同组的不同类型的产品。 抽象工厂模式升级了工厂方法模式,将原本仅能创建同种类型的产品升级为可以创建多种类型的产品. 优点 可以使用工厂模式生产多个产品簇,新增同类型产品时只需要新增一个具体工厂实现,不需要修改客户端代码,遵循开闭原则。 缺点 当产品簇中需要新增产品时,会修改原产品簇的所有工厂类,不符合开闭原则。 因此,抽象工厂模式在使用时应该酌情考虑其倾斜性,新增同类产品时,遵循开闭原则;但是,产品簇添加新的产品,所有的工厂类都需要添加生产新产品的方法。 2. 适用场景 抽象工厂模式通常适用于以下场景: 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、电风扇、洗衣机、空调等 系统中有多个产品族,但每次只使用其中的某一族产品,比如有人只喜欢穿某一个品牌的衣服和鞋 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构 3. 结构 抽象工厂模式的结构与工厂方法模式一样,同样具有抽象工厂、抽象产品、具体工厂、具体产品四大角色: 抽象工厂: 定义产品创建的接口,具有多个产品创建方法,这些方法负责创建不同的产品; 具体工厂: 实现抽象工厂的产品创建方法,用于具体实现如何创建这些产品簇中不同的产品; 抽象产品: 定义产品的规范,如产品规格、属性、功能等;就有多个抽象产品,组成产品簇; 具体产品: 抽象产品的具体实现,具体产品与具体工厂是多对一的关系,即一个具体工厂可以生产多个具体产品。 Figure 2. 抽象工厂模式结构 可以看到,AbstractFactory 是顶层抽象工厂接口,定义了创建不同产品簇的方法。不同的产品簇抽象出不同的产品接口,如图中的 Product1、Product2。 如果要添加一个产品的具体实现,那么客户端和工厂不需要改动,只需要新增一个具体工厂即可,那么结构变成了这样: Figure 3. 新增具体产品时的类结构变化 图中紫色部分是需要新加的部分,原工厂和客户端不需要做改动,遵循开闭原则。 但是,如果现在要增加新的产品线,即是说产品簇中要新加一个新产品,那么原工厂代码需要添加生产这个新产品的方法,而且客户能也需要为调用这个新方法而做出修改,此时的类结构如下图: Figure 4. 产品簇中添加新产品时的类结构变化 图中紫色部分为新加的产品,而原来的工厂添加新的方法(红色部分)来生产这个新的 Product3 产品,客户端也需要修改代码来调用工厂的新方法。 4. 与工厂方法模式的比较 抽象工厂模式与工厂方法模式在结构上其实是相同的,但是他们创建的产品类型不同: 工厂方法模式仅能创建某一类产品,而抽象工厂模式可以创建多种类型的产品; 工厂方法模式如果能够支持创建多种类型的产品,那么它会演变为抽象工厂模式;同理,如果抽象工厂模式仅创建同一类型的产品,它就变成了工厂方法模式 抽象工厂模式具有工厂方法模式的优点和缺点 5. 示例 接下来看一个实例。现在有一个电器工厂,可以生产电视机、空调,假设电视机有黑白、彩色两种,空调有挂式和柜式空调两种。工厂既能生产电视机,也能生产空调。我们用抽象工厂来设计,创建两个具体的工厂,工厂A来生产黑白电视机和挂式空调,而工厂B来生产彩电和柜式空调。类的结构如下: Figure 5. 电器工厂设计类图...

2020-06-09 · 3 min · 612 words · Hank

Java设计模式(5)-工厂方法模式

工厂方法模式,是工厂模式的一种,它对简单工厂模式进行进一步抽象化。它将工厂和产品进行抽象,抽象出来的工厂负责定义创建产品的规范,而抽象的产品负责定义产品规范,然后通过具体的工厂实现类来创建具体的产品。 1. 简介 工厂方法模式(Factory Method Pattern),它的定义如下:定义一个创建产品对象的工厂接口,它负责定义创建产品的方法,而具体的产品创建工作推迟到具体工厂类当中来完成,具体工厂类来角色如何创建产品。 优点 客户端(产品使用者)和产品的创建过程相分离,客户端无需关心产品的创建过程 系统增加新的产品时,只需要添加具体产品类和创建它的具体工厂实现即可,无需修改原有代码,满足开闭原则 缺点 每个具体的产品都对应了具体的工厂,系统复杂度增加。 2. 结构 简单工厂模式在新加产品的时候会违背开闭原则,而工厂方法模式对其进行了改进,增加了抽象工厂和抽象产品。 工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品构成,其类图如下: 抽象工厂(AbstractFactory):抽象了创建产品的方法,例如 createProduct(),但是不负责具体实现产品创建过程;这里的抽象工厂一般是接口,也可以是抽象类 具体工厂(ConcreteFactory):抽象工厂的具体实现类,实现了抽象工厂定义的产品创建方法 抽象产品(AbstractProduct):就是图中的Product,抽象产品对工厂生产的产品进行了抽象,定义了产品的规范,如产品的功能、参数、属性等;同样的,抽象产品也可以是抽象类或接口 具体产品(ConcreteProduct):抽象产品的具体实现,由具体工厂进行创建,通常,具体产品与具体工厂成一一对应关系 3. 示例 继续使用 上一篇 生产牛奶的例子。商品售卖各种牛奶,如纯牛奶、酸奶、高钙奶等等,前边使用的是简单工厂模式。现在,我们将其改为工厂方法模式。 类的设计如下: 这里,抽象的产品是 Milk,抽象的工厂是 MilkFactory,具体的工厂是 PureMilkFactory 等,他们负责生产某一种具体的产品,如 PureMilkFactory 负责生产 PureMilk。商店 Store 不用关心具体的产品是什么,它只需要依赖抽象的工厂和产品即可。 部分关键代码如下: 抽象工厂 interface MilkFactory { Milk createMilk(); } 抽象产品 interface Milk { String getName(); } 具体产品 class PureMilk implements Milk { @Override public String getName() { return "伊利纯牛奶"; } } ...

2020-06-06 · 1 min · 181 words · Hank

Java设计模式(4)-简单工厂模式

工厂模式,一般分为三种,包括:简单工厂模式(也叫静态工厂模式)、工厂方法模式、抽象工厂模式。在GOF提及的23中设计模式中,没有简单工厂模式,但对于简单的软件开发中,简单工厂模式使用的还是很普遍的。 1. 简介 传统的设计,客户端创建各种产品,来实现功能。这样,客户端与各种产品进行了耦合,违背了OCP原则。 Figure 1. 传统设计 我们能不能把产品的创建过程抽取出来,形成一个产品创建工厂呢?这样,就可以使创建与使用相分离,客户端就直接依赖工厂,而不是与直接的产品进行耦合了。这就是工厂模式的思路。 简单工厂模式是工厂模式的一种,它的工厂负责创建具体的产品,客户端不需要关注产品的创建过程,而是直接依赖工厂即可。简单工厂模式的类图如下: Figure 2. 简单工厂模式 简单工厂又称为静态工厂,因为它创建产品的方法一般都是静态方法,客户端不需要实例化工厂就可以直接调用它的产品创建方法。 优点 产品的创建过程抽取出来,客户端与具体产品创建过程解耦; 客户端仅负责自身业务,不关注产品创建过程,职责更单一 缺点 工厂和产品耦合,违背了OCP原则,添加新产品需要修改工厂类代码 工厂类的方法是静态的,并不利于扩展 因此,简单工厂模式适用于创建的产品比较固定,种类少、变动不频繁的场景,往往在小型系统适用。 2. 示例 举个例子,现在有一个牛奶店售卖各种牛奶,假设出售的过程包括生产、收款、拿货几个步骤,而且此店售卖的牛奶种类不是很多。如果没有工厂,店铺不但需要处理售卖的逻辑,而且还需要创建各种牛奶的实例。这会导致客户端职责过多,并不利于其管理和维护。 现在的设计这样的: class Store { public Milk sale(String name) { System.out.println("顾客购买牛奶"); System.out.println(" > 生产牛奶"); Milk milk = createMilk(name); System.out.println(" > 收款"); System.out.println(" > 拿货"); return milk; } public Milk createMilk(String name) { if ("pure".equals(name)) { return new PureMilk(); } else if ("yogurt".equals(name)) { return new Yogurt(); } else if ("highCalcium".equals(name)) { return new HighCalciumMilk(); } else { throw new RuntimeException("无法生产牛奶:" + name); } } } ...

2020-06-05 · 1 min · 170 words · Hank

Java设计模式(3)-单例模式

单例模式(Singleton Pattern),Java中最简单的设计模式之一,它定义了如何在整个系统范围内仅创建只有单个实例的类。单例模式是一种创建型模式,系统中的类只有一个实例,该类负责自己创建自身的唯一单个实例,并提供一个静态方法来获取自身实例。 1. 单例模式解决的问题 单例模式(Singleton Pattern)的目的是要保证系统中一个类仅有一个实例,并且该类给外部提供一个访问它实例的方法。单例模式旨在解决系统中的类被频繁创建和销毁而占用较多资源的问题。 单例模式不允许外部创建其实例(构造器私有化),而是自身提供给外部一个静态方法来获取其单实例对象。 优点 单例类减小了资源占用,一个类仅有一个实例,内存开销小。 缺点 单例类没有接口,不能继承,与单一职责原则冲突,需要自己关注自身实例创建逻辑。 2. Java中单例模式的8种写法分析 在Java中,单例模式有8八种写法,但是可用的只有几种,我们来分析一下他们的写法和优缺点。 2.1. 饿汉式-静态常量 这种方式利用了类初始化机制,在类初始化时就创建单例实例。 public class AvailableEagerSingleton1 { private static final AvailableEagerSingleton1 INSTANCE = new AvailableEagerSingleton1(); (1) private AvailableEagerSingleton1() { (2) } public static AvailableEagerSingleton1 getInstance() { (3) return INSTANCE; } } 1 静态常量,类初始化时就创建实例 2 构造器私有化,不允许外部直接创建实例 3 提供静态方法给外部调用,以获取其实例 这种方式的优点就是实现起来简单,而且没有线程安全问题,在初始化静态属性时直接创建实例;缺点是,没有实现懒加载,如果类不会被使用,则会存在资源浪费。 如果确定类会被使用,这种方式也是推荐使用的。 2.2. 饿汉式-静态代码块 另一种懒汉式的变体是,使用静态代码块来代替静态属性创建实例,两者其实没有什么根本区别: public class AvailableEagerSingleton2 { private static AvailableEagerSingleton2 INSTANCE; { INSTANCE = new AvailableEagerSingleton2(); (1) } private AvailableEagerSingleton2() { } public static AvailableEagerSingleton2 getInstance() { return INSTANCE; } } ...

2020-05-29 · 2 min · 408 words · Hank

Java设计模式(2)-软件设计遵循的七大原则

设计模式为面向对象软件设计的前进道路照亮了明灯,而设计模式遵循面向对象软件设计的七大原则,这些原则也是设计模式的基础。 1. 单一职责原则 单一职责原则(Single Responsibility),它的定义是:对类来说的,一个类应该只负责一项职责。即是说,一个类只负责一种职责,让其职责单一化,如果有其他的职责则应该设计另外的类来进行协助。比如:订单处理类只负责处理订单,而不该去处理支付的逻辑。单一职责原则也可以引申到方法、类、模块甚至服务和子系统中。 类的职责单一,并不是说一个类只有一个方法,而是说一个类只承担实现一中业务职责,如:OrderService只负责处理订单相关的逻辑,而不会涉及支付相关的逻辑。 下面来看看具体的例子。假设我们设计一个Animal类,来管理动物的飞行、陆行、游水等动作。 1.1. 基础示例 这个版本的设计如下图所示: Figure 1. 初版设计 初版的代码如下: Animal类 // 一个类负责动物的飞行方法 abstract class Animal { public void fly() { System.out.println(getName() + "能够飞行"); } public void runOnLand() { System.out.println(getName() + "能在陆地上跑"); } public void swimInWater() { System.out.println(getName() + "能在水中游"); } protected abstract String getName(); } 这里设计了一个抽象类,里边有各种方法来打印某种动物(具备名称)的这些行为。 然后,各种动物都可以继承 Animal : Animal下的具体动物 // 小鸟 class Bird extends Animal { @Override public String getName() { return "小鸟"; } } // 大雁 class WildGoose extends Animal { @Override public String getName() { return "大雁"; } } // 狗 class Dog extends Animal { @Override public String getName() { return "狗"; } } // ……其他动物 ...

2020-05-27 · 6 min · 1094 words · Hank

Java设计模式(1)-什么是设计模式?

什么是设计模式?顾名思义,设计模式是一种软件设计所使用的方法,适用于面向对象软件设计。设计模式是一种方法,或者说是一种方法论,它是软件前辈们经历了无数的软件工程设计中的痛点和教训之后,总结出来的经验的结晶。设计模式是一种软件设计方法,不是代码,也不是规范。 1. 设计模式的提出 如果设计出优秀的软件一直是软件从业者探索的目标。优秀的软件,必须具备可复用、可扩展、可维护、高性能等特点,既要保证系统的高内聚、低耦合的特点,又要保证系统随时应对各种变化的灵活性。然很,这往往依赖于软件设计者的个人经验积累,而新手往往对此无从下手。 设计模式的提出,为软件设计指明了方向。 设计模式概念的提出,要追溯到1994年。在1994年,由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides 四为软件大师合著了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用面向对象软件的基础)的书,该书首次提到了软件开发中设计模式的概念。 Figure 1. 设计模式-可复用面向对象软件设计的基础 Ps: 公众号后台回复关键字"设计模式"下载此书 该书总结了面向对象设计中最有价值的经验,并总结出了23中最常见和最有价值的设计模式,为软件设计之路开启了一盏明灯,此书的四名作者又被称为GOF(Gang Of Four,四人帮)。这23种设计模式主要基于面向对象设计的两种基本原则: 对接口编程而不是对实现编程 优先使用对象组合而不是继承 2. 设计模式的概念 设计模式来源于城市和建筑模式: 每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题解决方案的核心。这样,你就能一次又一次的使用该方案而不必做重复劳动。 — Christopher Alexander 这个思想同样适用于面向对象的设计模式。一般而言,设计模式包含四大要素: 名称(pattern name): 描述模式解决的问题、方案和效果 问题(problem): 描述何时使用模式、设计的问题以及问题存在的背景 解决方案(solution): 描述了设计的组成部分、他们之间的相互关系和协作的方式 效果(consequence): 描述了使用模式应该达到的效果和应该权衡的问题 3. 设计模式的类型 书中对设计模式进行了详细阐述,并将设计模式分为三大类型: 3.1. 创建型模式(Creational) 对象怎么创建?Java中,最简单的是使用关键 new 来创建一个对象实例,然而,我们需要思考:我们需要这么多对象实例吗?这种方式是我们需要的吗?这种方式是最好的吗?创建型模式抽象了对象实例化过程,它描述了如何去创建对象,而无需关注实例化细节。创建型模式包括: 简单工厂模式(Factory Pattern)、 抽象工厂模式(Abstract Factory Pattern)、 工厂方法模式(Factory Method Pattern) 单例模式(Singleton Pattern) 原型模式(Prototype Pattern) 建造者模式(Builder Pattern) 3.2. 结构型模式(Stuctural) 结构型模式,描述了如何组合类和对象以获得最大的结构,通常这些模式使用继承的方式来进行组合,包括: ...

2020-05-26 · 1 min · 148 words · Hank