技术标签: 面试 Java 状态模式 Java设计模式 行为型模式 设计模式
背景:
介绍状态模式前,我们先看这样一个实例:公司力排万难终于获得某个酒店的系统开发项目,并且最终落到了你的头上。下图是他们系统的主要工作:
当第一眼看到这个系统时你就看出这是一个状态图,每个框都代表了房间的状态,箭头表示房间状态的转换。分析如下:房间有三个状态:空闲、已预订、已入住,状态与状态之间可以根据客户的动作来进行转换,定义每个状态的值。
public static final int FREEMTIME_STATE = 0; //空闲状态
public static final int BOOKED_STATE = 1; //已预订状态
public static final int CHECKIN_STATE = 2; //入住状态
int state = FREEMTIME_STATE; //初始状态
通过客户的动作将每个状态整合起来,实现这个功能最简单的方式肯定是 if…else 啦!所以这里我们就通过动作将所有的状态全面整合起来。分析得这里有四个动作:预订、入住、退订、退房。如下:
/**
* @desc 预订
*/
public void bookRoom(){
if(state == FREEMTIME_STATE){ //空闲可预订
if(count > 0){
System.out.println("空闲房间,完成预订...");
state = BOOKED_STATE; //改变状态:已预订
count --;
//房间预订完了,提示客户没有房源了
if(count == 0){
System.out.println("不好意思,房间已经预订完,欢迎您下次光临...");
}
}
else{
System.out.println("不好意思,已经没有房间了....");
}
}
else if(state == BOOKED_STATE){
System.out.println("该房间已经被预订了...");
}
else if(state == CHECKIN_STATE){
System.out.println("该房间已经有人入住了...");
}
}
/**
* @desc 入住
*/
public void checkInRoom(){
if(state == FREEMTIME_STATE){
if(count > 0){
System.out.println("空闲房间,入住...");
state = CHECKIN_STATE; //改变状态:已预订
count --;
//房间预订完了,提示客户没有房源了
if(count == 0){
System.out.println("不好意思,房间已经预订完,欢迎您下次光临...");
}
}
else{
System.out.println("不好意思,已经没有房间了....");
}
}
else if(state == BOOKED_STATE){
if("如果该房间是您预订的"){
System.out.println("入住....");
state = CHECKIN_STATE;
}
else{
System.out.println("您没有预订该房间,请先预订...");
}
}
else if(state == CHECKIN_STATE){
System.out.println("该房间已经入住了...");
}
}
/**
* @desc 退订
*/
public void unsubscribeRoom(){
if(state == FREEMTIME_STATE){}
else if(state == CHECKIN_STATE){}
else if(state == BOOKED_STATE){
System.out.println("已退订房间...");
state = FREEMTIME_STATE;
count ++;
}
}
/**
* @desc 退房
*/
public void checkOutRoom(){
if(state == FREEMTIME_STATE){}
else if(state == BOOKED_STATE){}
else if(state == CHECKIN_STATE){
System.out.println("已退房..");
state = FREEMTIME_STATE;
count++;
}
}
正当你完成这个 “复杂” 的 if..else 时,客户增加需求说需要将某些房间保留下来以作为备用(standbyState),于是悲剧了,因为你发现要在所有的操作里都要判断该房间是否为备用房间。当你老大经过你身边的时候发现你正在纠结怎么改的时候,你老大就问你为什么不换一个角度思考以状态为原子来改变它的行为,而不是通过行为来改变状态呢?于是你就学到了状态模式。
状态模式,就是允许对象在内部状态发生改变时改变它的行为,对象看起来就好像修改了它的类,也就是说以状态为原子来改变它的行为,而不是通过行为来改变状态。
当对象的行为取决于它的属性时,我们称这些属性为状态,那该对象就称为状态对象。对于状态对象而言,它的行为依赖于它的状态,比如要预订房间,只有当该房间空闲时才能预订,想入住该房间也只有当你预订了该房间或者该房间为空闲时。对于这样的一个对象,当它的外部事件产生互动的时候,其内部状态就会发生变化,从而使得他的行为也随之发生变化。
从上面的UML结构图我们可以看出状态模式的优点在于:
(1)封装了转换规则,允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
(2)将所有与状态有关的行为放到一个类中,可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
但是状态模式的缺点在于:
(1)需要在枚举状态之前需要确定状态种类
(2)会导致增加系统类和对象的个数。
(3)对 “开闭原则” 的支持并不友好,新增状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
所以状态模式适用于:代码中包含大量与对象状态有关的条件语句,以及对象的行为依赖于它的状态,并且可以根据它的状态改变而改变它的相关行为。
策略模式和状态模式比较:策略模式和状态模式的结构几乎完全一致,但是它们的目的和本质完全不一样。策略模式是围绕可以互换的算法来创建业务的,而状态模式是通过改变对象内部的状态来帮助对象控制自己行为的。前者行为是彼此独立、可以相互替换的,后者行为是不可以相互替换的。
以前面的酒店的案例进行代码实现,对于该实例的UML图如下:
首先是状态接口:State
public interface State {
/**
* @desc 预订房间
*/
public void bookRoom();
/**
* @desc 退订房间
*/
public void unsubscribeRoom();
/**
* @desc 入住
*/
public void checkInRoom();
/**
* @desc 退房
*/
public void checkOutRoom();
}
然后是房间类:
public class Room {
/*
* 房间的三个状态
*/
State freeTimeState; //空闲状态
State checkInState; //入住状态
State bookedState; //预订状态
State state ;
public Room(){
freeTimeState = new FreeTimeState(this);
checkInState = new CheckInState(this);
bookedState = new BookedState(this);
state = freeTimeState ; //初始状态为空闲
}
/**
* @desc 预订房间
*/
public void bookRoom(){
state.bookRoom();
}
/**
* @desc 退订房间
*/
public void unsubscribeRoom(){
state.unsubscribeRoom();
}
/**
* @desc 入住
*/
public void checkInRoom(){
state.checkInRoom();
}
/**
* @desc 退房
*/
public void checkOutRoom(){
state.checkOutRoom();
}
public String toString(){
return "该房间的状态是:"+getState().getClass().getName();
}
/*
* getter和setter方法
*/
public State getFreeTimeState() {
return freeTimeState;
}
public void setFreeTimeState(State freeTimeState) {
this.freeTimeState = freeTimeState;
}
public State getCheckInState() {
return checkInState;
}
public void setCheckInState(State checkInState) {
this.checkInState = checkInState;
}
public State getBookedState() {
return bookedState;
}
public void setBookedState(State bookedState) {
this.bookedState = bookedState;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
然后是3个状态类,这个三个状态分别对于这:空闲、预订、入住。其中空闲可以完成预订和入住两个动作,预订可以完成入住和退订两个动作,入住可以退房。
/**
* @Description: 空闲状态只能预订和入住
*/
public class FreeTimeState implements State {
Room hotelManagement;
public FreeTimeState(Room hotelManagement){
this.hotelManagement = hotelManagement;
}
public void bookRoom() {
System.out.println("您已经成功预订了...");
hotelManagement.setState(hotelManagement.getBookedState()); //状态变成已经预订
}
public void checkInRoom() {
System.out.println("您已经成功入住了...");
hotelManagement.setState(hotelManagement.getCheckInState()); //状态变成已经入住
}
public void checkOutRoom() {
//不需要做操作
}
public void unsubscribeRoom() {
//不需要做操作
}
}
/**
* @Description: 入住状态房间只能退房
*/
public class BookedState implements State {
Room hotelManagement;
public BookedState(Room hotelManagement) {
this.hotelManagement = hotelManagement;
}
public void bookRoom() {
System.out.println("该房间已近给预定了...");
}
public void checkInRoom() {
System.out.println("入住成功...");
hotelManagement.setState(hotelManagement.getCheckInState()); //状态变成入住
}
public void checkOutRoom() {
//不需要做操作
}
public void unsubscribeRoom() {
System.out.println("退订成功,欢迎下次光临...");
hotelManagement.setState(hotelManagement.getFreeTimeState()); //变成空闲状态
}
}
/**
* @Description: 入住可以退房
*/
public class CheckInState implements State {
Room hotelManagement;
public CheckInState(Room hotelManagement) {
this.hotelManagement = hotelManagement;
}
public void bookRoom() {
System.out.println("该房间已经入住了...");
}
public void checkInRoom() {
System.out.println("该房间已经入住了...");
}
public void checkOutRoom() {
System.out.println("退房成功....");
hotelManagement.setState(hotelManagement.getFreeTimeState()); //状态变成空闲
}
public void unsubscribeRoom() {
//不需要做操作
}
}
最后是测试类:
public class Test {
public static void main(String[] args) {
//有3间房
Room[] rooms = new Room[2];
//初始化
for(int i = 0 ; i < rooms.length ; i++){
rooms[i] = new Room();
}
//第一间房
rooms[0].bookRoom(); //预订
rooms[0].checkInRoom(); //入住
rooms[0].bookRoom(); //预订
System.out.println(rooms[0]);
System.out.println("---------------------------");
//第二间房
rooms[1].checkInRoom();
rooms[1].bookRoom();
rooms[1].checkOutRoom();
rooms[1].bookRoom();
System.out.println(rooms[1]);
}
}
运行结果:
设计模式系列文章:
文章浏览阅读6.8k次。在搞银联POS机的东东,略感头疼,搞了N久,还是终端-Hulk 给的方法解决了3DES加密解密的难题。import java.security.Key;import java.security.spec.KeySpec;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.Secret_java 3des ecb nopadding
文章浏览阅读3.2k次。Webots初学者入门教程--3D视窗_如何在webots中给球体施加一个力
文章浏览阅读1.3w次,点赞1.5k次,收藏631次。js拿到接口数据 处理成三级或者四级结构再进行渲染_js 数组数据3级
文章浏览阅读7k次,点赞36次,收藏177次。vue3已经出了好长一段时间了,最近闲来无事简单学习了一下,新增的东西还是挺多的,写一篇文章来记录一下。谈到vue3,首先想到的就是组合式api,很大程度的解决了vue2选项式api的缺点,那有啥缺点?当文件中的业务代码非常多的时候,阅读修改代码的时候是非常痛苦的,data,method,watch还有计算属性之间来回跳转, 我已经准备拔刀了。下面这些图被疯转,很形象的展现了vue2和vue3的区别,可以看到。_vue3
文章浏览阅读1k次。在命令行中运行下面这行代码:(引号里面要改为自己的stanford-corenlp所在目录)java -mx4g -cp "F:\资源\stanford-corenlp-full-2018-10-05/*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -port 9000 -timeout 15000或java -Xmx4g -cp "F..._stanford corenlp 标注结果没有依存关系
文章浏览阅读7.8k次,点赞5次,收藏43次。目录基础概念快速入门进阶设置基础概念 Sequencer 编辑器使用户能够用专业的多轨迹编辑器(类似于Matinee )创建游戏内过场动画。通过创建 关卡序列(Level Sequences) 和添加 轨迹(Tracks),用户可以定义各个轨迹的组成,轨迹可以包含动画(Animation)(用于将角色动画化)、变形(Transformation)(在场景中移动各个东西)、音频(Audio)(用于包括音乐或音效)和数个其他轨迹(Track)类型等 Sequencer通过添加关键_ue sequencer
文章浏览阅读2.3w次,点赞5次,收藏23次。文件已上传,请在我的资源里找一下最近群晖nas上在用emby,感觉很不错但更新缩略图的时候,查看日志总会报错原来是装的二合一系统,没有核显文件上网找的方法都是需要重做系统,SSH也不靠谱,找不到挂载路径最后还是想到了最原始的方法,用pe系统里面的diskgunius直接替换,简单粗暴方法:1.准备好PE系统U盘2.准备好extra.lzma和extra2.lzma两个文件可自行搜索,或者下载我准备好的文件https://download.csdn.net/downlo_黑群晖3617xs核显硬解
文章浏览阅读5.4k次。使用JSSDK实现网站的QQ登录进入QQ互联官网:https://connect.qq.com/index.html 进行开发者注册并审核认证【实名认证】:首先使用QQ账号登录上述的QQ互联官网;接着填写开发者审核认证资料,需提交:开发者类型(个人的就选个人开发者)、名称(开发者的真实姓名)、联系地址、手机号码、电子邮箱(真实邮箱,方便接收“腾讯QQ互联”官方发来的邮件,例如:“开发者注_qq js sdk官网
文章浏览阅读1.1w次,点赞12次,收藏9次。本文基于我个人的一些学习和开发经验,以机器语言、汇编语言、C/C++、Java、C#为例,谈谈我的看法,如有不对的地方,欢迎指出。感谢我的老师和师兄师姐在我提出这个问题后,给出了他们的看法。本文尽可能用更容易理解的角度去编写,帮助理解编程语言。 对于编程语言的初学者,以及学过一些编程语言的科班学生、培训机构的学生等等而言,编程语言似乎很难理解。为什么会有这么多编..._编程语言c/c++
文章浏览阅读3.1k次。如果需要在图的边中加上边的权重可以通过在函数中设置参数来实现在拓扑图的每条边上加上权重。具体来说,需要先用函数获取边权重的字典形式,然后将该字典传递给函数来在图中显示权重标签。= 0:plt.show()这段代码中,函数用于计算节点在圆形拓扑上的位置,函数用于获取边权重的字典形式,函数用于在图中显示权重标签。nx.draw()函数用于画出节点和边的拓扑图,其中参数用于显示节点标签。最后,用plt.show()函数显示图形。运行后显示的图形为。_python绘制网络拓扑图
文章浏览阅读1.1w次,点赞4次,收藏29次。Apache+PHP环境搭建保姆级教程1.安装和配置Apache下载并安装Apache首先从apache官网下载[Download - The Apache HTTP Server Project]:可以看到有个Stable Release - Latest Version(稳定版 - 最新版本),进入链接选择Files for MIcrosoft Windos选择ApacheHaus选择你想要安装的Apache版本,VC是Visual Studio的一种对应开发环境,要注意这里选择的版_aparch + php
文章浏览阅读4.9k次,点赞7次,收藏23次。工具类的名称:EncryptionUtil工具类的功能:提供常用的加密解密方法,包括对称加密、非对称加密、哈希算法等。_java加密解密工具类