- 浏览: 369669 次
- 性别:
- 来自: 苏州
文章分类
- 全部博客 (335)
- C++ (190)
- 设计模式 (43)
- 数据库技术 (5)
- 网络编程 (11)
- 自动化测试 (6)
- Linux (13)
- OpenSSL (10)
- MS Crypt API (5)
- SCM (2)
- English (4)
- Android (10)
- EMV规范 (1)
- Saturn Platform (0)
- C (10)
- SQL (2)
- ASP.NET (3)
- 英语口语学习 (3)
- 调试工具 (21)
- 编译技术 (5)
- UML (1)
- 项目管理 (5)
- 敏捷开发 (2)
- Http Server (6)
- 代码审查、代码分析 (5)
- 面试基础 (10)
- 重点知识 (16)
- STL (6)
- Efficient C++资料 (8)
- 数据结构和算法 (7)
- 读书笔记 (0)
- 开源项目 (4)
- 多线程 (2)
- Console App (6)
- 个人开源项目 (4)
- IBM DevelopWorks (4)
- Java (16)
- 内存泄漏相关调试和检测 (13)
- 软件测试相关技术 (2)
- C# (11)
- Apple Related (1)
- 软件测试和管理 (2)
- EMV (1)
- Python (1)
- Node.js (6)
- JavaScript (5)
- VUE (1)
- Frontend (1)
- Backend (4)
- RESTful API (3)
- Firebase (3)
最新评论
-
u013189503:
来个密码吧
[C++][Logging] 项目中写日志模块的实现 -
wyf_vc:
来个密码啊!!
[C++][Logging] 项目中写日志模块的实现
转自
http://www.ibm.com/developerworks/cn/java/j-lo-serialNo/
本文介绍了一种基于自定义格式字符串, 应用 Command 模式的流水号管理功能,可以让用户灵活设定流水号的格式,具备非常好的灵活性和可扩展性。
流水号管理作为很多信息系统的一种基础功能,对于一些具有业务含义的流水号,一般由多个具有不同意义的组成部分组成,其格式会相对比较复杂,如何快速灵活的支持不同的流水号格式,十分有现实意义。本文介绍了一种基于自定义格式字符串,应用 Command 模式的流水号管理方案。在本方案中,对复杂的的流水号进行分析分解,拆分为更细小的子序列,再分别调用相应的生成接口生成,最终生成的流水号。使用本方案,流水号的生成规则定义在格式字符串中,如要生成不同格式的流水号,只需要修改格式字符串即可,生成和扩展都十分方便。
在信息系统中流水号作为一项必不可少的基础功能,流水号的生成、控制和管理十分重要。一般一个信息系统中,不同的流水号序列有不同的生成规则。如果我们对这些流水号进行进一步分析,这些不同的流水号序列又可细分为多个规则固定的子序列。基于这种分析,对于这种实际应用需求,如何来高效灵活的生成所需的流水号,就显得十分有意义。
首先看几个我们身边常见的流水号:身份证号、税务票、银行的业务流水号、排队号、国务院办公厅发文号(国办发〔2011〕48 号)等。如果对这些流水号进一步分析,我们可以得出,流水号一般由多个规则固定的子序列组成,常见的子序列规则如下:
1. 数字序列:1,2,3 … 或者 00001,00002,00003
2. 字母序列:如 ‘A-Z’
3. 固定字符串:如固定的字符前缀或后缀
4. 时间戳:如 yyyyMMdd 格式对应的为 20130201
5. 其他序列:如罗马字符 ‘I-X’
从另一个角度我们可以说,一个流水号就是上面子序列的排列组合。
基于这样的分析,我们可以创建一个流水号生成引擎,这个引擎可以读取和解析给定的流水号规则,将流水号规则拆分成多个子序列规则,并根据子序列规则生成子序列串,拼接返回最终的流水号。有了这个流水号生成引擎,在系统实际应用的时候,只需要对所需的流水号配置相应的生成规则即可。并且本方案还预留扩展接口,如果实际中遇到个性化的序列生成规则,开发者也可以根据实际需要进行扩展开发,这样也能有效覆盖个性化的需求。
说了这么多,不如图形展示来得直观,整个解决方案的流程见下图:
1. 流水号规则读取
2. 流水号规则解析、分割、获取子序列规则
3. 生成子序列任务的派发
4. 子序列汇总和合并
5. 返回最终的生成流水号实例
在上图中,这里需要说明如下几点:
1. 流水号规则用格式字符串表示为:Str@ 国办发〔# DateTime@yyyy # Str@〕# NumSeq@0C0 # Str@ 号,其中: “#” 为子序列间的分隔符,“@” 为子序列内部的分隔符。当然实际应用时,读者也可以按照自己的规则创建格式字符串。
2. 在每个子序列规则定义的内部,用 “@” 分为两部分,前面一部分(Str/DateTime/NumSeq)表明该子序列的类型,用于确定调用哪个类型的 Generator 来生成该子序列串,后面一部分是用于生成子序列串所需的信息。举例来讲,如 “Str@ 国办发〔”,“Str” 表示为固定字符串类型,固定字符串为 “国办发〔”;类似的 “DateTime@yyyy”,“DateTime” 表示时间日期类型,格式为四位的年代号,根据当前时间生成。
3. 在各个 Generator 生成完毕之后,自动拼接并返回最终生成的流水号。
我们可以认为,对于每一个流水号生成规则,似乎可以看做是一个命令队列;对于流水号生成引擎,一方面按照顺序接收生成子序列规则的命令,逐个读取并解析子序列规则,再根据类型分发给各个 Generator 来生成子序列传,最后合并返回最终需要的流水号;每个子序列的 Generator,即为最终的命令的执行者。
经过如上的分析并结合应用场景,我们很容易想到,可以使用 Command 模式来实现。Command 模式,一个典型的应用场景就是处理命令队列,减少行为请求者和动作执行者的耦合,提高灵活性和降低代码冗余。
命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或事务(Transaction)模式。命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
适用性:在软件系统中,行为请求者与行为实现者之间通常呈现一种紧耦合的关系。但在某些场合,比如要对行为进行记录撤销重做事务等处理,这种无法抵御变化的紧耦合是不合适的。这种情况下,使用 command 模式将行为请求者与行为实现者进行解耦。
说到这里,我们来看看 Command 模式 UML 类图。
这里对上图做一个简单的解释,命令模式涉及到五个角色,分别为:
客户(Client)角色:创建一个具体命令(ConcreteCommand)对象,并设置命令的接收者。
命令(Command)角色:定义一个给所有命令类的抽象接口,定义了统一的 execute() 接口方法。
具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现 Command 接口,并实现 execute() 方法,负责调用接收者的相应操作。
请求者(Invoker)角色:负责调用由 Client 下达的对象执行请求。
接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法为行动方法。
【注意】:在某些简单业务场景下,可在 ConcreteCommand 类的 execute 方法中直接编写实现代码,而省去接收者(Receiver)类以简化代码。如在本应用场景中省去 Receiver 类的创建。
在本应用场景中,正是利用了 Command 模式将行为请求者与行为实现者进行解耦,用于处理命令队列,实现了原子操作上的复用。Command 是对行为进行封装的典型模式,这样做有利于代码的健壮性,可维护性,还有复用性。
实现代码
对应 Command 模式,简单介绍一下本方案中使用到的核心的类和方法:
1. Client - SNGenerateApp.java:
具体的命令请求者,通过调用 addBuildInGenerator() 方法创建内置 generator,暴露 addGenerator() 方法供进一步扩展。
2. Invoker - SNGeneratorEngine.java:
为命令的直接接收者和分派者。
3. ICommand - IGenerator.java:
定义生成接口,子类通过实现 generate() 方法来生成具体的子序列串。
4. ConcreteCommand - XXXXGenerator.java:
各个具体的子序列生成类,实现 generate() 方法,负责最终的的子序列生成,为 Command 的最终执行者。这里简单实现了四个 generator。
a). 字符序列生成器:CharacterSequenceGenerator.java
b). 日期时间生成器:DateTimeGenerator.java
c). 数字序列生成器:NumberSequenceGenerator.java
d). 固定字符串生成器:StringGenerator.java
5. Receiver:
在本例中由于业务场景简单,为简化代码而直接将实现代码写在各 Generator 的 generate() 方法中,无独立的 Receiver 类。
另外还有两个辅助类简单提一下,MockDB.java 用于模拟数据库,存取自增序列;ParamConsts.java 为常量变量类,GeneratorTypeSet.java 定义子序列类型常量。
图 4. 包结构及类清单图
下面是具体的代码展示和介绍。
Generator 接口
定义 Generator,这里只有两个方法,一个是 getGeneratorType() 用于返回 Generator 类型,另一个是 generate() 方法用于生成子序列串。
清单 1. Generator 接口
数字序列 Generator
Generator 接口的实现类,用于处理数字序列的生成。下面的代码考虑到变长字符串和定长字符串两种不同情形。在实际中,序列号的生成还需要考虑按照一定的周期重置的情况(如每年都从 1 重新开始计数),可以在扩展实现的时候加以考虑。
清单 2. 数字序列 Generator
字符序列 Generator
Generator 接口的实现类,用于处理字符序列。这里只是一个简单的例子,比如车牌号的生成就可以使用到字符序列。
清单 3. 字符序列 Generator
日期序列 Generator
Generator 接口的实现类,用于生成时间日期戳。下面的代码基于 java.text.SimpleDateFormat 实现。
清单 4. 日期序列 Generator
固定字符 Generator
Generator 接口的实现类,用于生成固定字符。固定字符 Generator 比较简单,即直接返回配置的字符串。
清单 5. 固定字符 Generator
流水号生成引擎 Engine
流水号引擎需要负责接收流水号生成规则,根据类型调用对应的各个 Generator 生成子序列串,最后将子序列串并拼接并返回最终的流水号。
清单 6. 流水号生成引擎 Engine
流水号生成应用 App
流水号应用 SNGenerateApp.java 类,声明了 SNGeneratorEngine 对象的实例,并通过调用 addBuildInGenerator() 创建内置的 Generator 类并添加到 SNGeneratorEngine 中,通过 generateSN() 方法来发送生成流水号的请求。
清单 7. 流水号生成应用 App
应用举例
现在,我们就通过创建 SNGenerateApp 实例并执行相应方法来模拟流水号的生成。
这里仍然以前文提到的国务院办公厅发文号(国办发〔2011〕48 号)为例来说明,其格式字符串为:“Str@ 国办发〔#DateTime@yyyy#Str@〕#NumSeq@0C0#Str@ 号”,使用 MockDB.SEQ_ID_1 的 sequence 获取自增数值,示例代码如下:
清单 8. 应用示例代码 1
输出结果:
1. 生成 国务院办公厅发文号(国办发〔2014〕48 号)
如果我们要模拟生成常见的 ICP 备案号((沪 ICP 备 05172190 号))呢?很简单,只需要规则重新配置流水号的格式字符串,在这里对应的为“Str@ 沪 ICP 备 #NumSeq@8C0#Str@ 号”,无需修改任何代码。注意,一般来讲不同的流水号会用到不同的 sequence 数值,我们也做了模拟,这里传入 MockDB.SEQ_ID_2 的 sequence ID 来获取自增数值。
清单 9. 应用示例代码 2
输出结果:
2. 生成 ICP 备案号(沪 ICP 备 05172190 号)
想必有读者会问,如果我想生成的流水号还有一些其他特殊的生成规则怎么办?也很简单,只需要编写符合你业务要求的 Generator,然后将这个 Generator 通过 SNGenerateApp.addGenerator() 添加进去,最后再配置相应的流水号格式字符串即可。这样我们就会发现,只要新添加了一个 Generator,那么这个应用就具备相应的子序列生成功能,能够为以后生成含有该子序列的任意流水号提供支持。换句话说,我们可以生成出任意流水号,只要其生成规则是基于当前已有的 Generator 的排列组合,所需要做的只是配置一下流水号格式字符串,是不是非常方便和灵活呢?这也正是应用 Command 模式给我们带来的巨大好处。
总结
本文简单介绍了应用 Command 模式及其优点,并通过一个基于自定义的格式字符串的流水号生成的方案,进一步让读者了解应用场景。采用 Command 模式,我们将原本复杂的流水号生成过程化整为零,分解成各个可以重用的子序列,根据各子序列的类型派发给对应的 Generator,最终在拼接返回流水号。该实现方案适用于各种复杂规则流水号的生成,并且十分易于扩展和维护,在实际应用中也取得了不错的效果。通过本例,希望读者能够进一步了解 Command 模式,应用到适合的场景中。
参考资料
学习
阅读 GoF(“四人帮”)《设计模式》书籍,原名《Design Patterns: Elements of Reusable Object-Oriented Software》深入了解更多设计模式。
参考百度百科中关于 Command 模式的词条,可点击 command 模式。
访问 “developerWorks Java 设计模式与建模专题”,了解更多设计模式相关的专题讨论与实践。
developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。
http://www.ibm.com/developerworks/cn/java/j-lo-serialNo/
本文介绍了一种基于自定义格式字符串, 应用 Command 模式的流水号管理功能,可以让用户灵活设定流水号的格式,具备非常好的灵活性和可扩展性。
摘要
流水号管理作为很多信息系统的一种基础功能,对于一些具有业务含义的流水号,一般由多个具有不同意义的组成部分组成,其格式会相对比较复杂,如何快速灵活的支持不同的流水号格式,十分有现实意义。本文介绍了一种基于自定义格式字符串,应用 Command 模式的流水号管理方案。在本方案中,对复杂的的流水号进行分析分解,拆分为更细小的子序列,再分别调用相应的生成接口生成,最终生成的流水号。使用本方案,流水号的生成规则定义在格式字符串中,如要生成不同格式的流水号,只需要修改格式字符串即可,生成和扩展都十分方便。
背景
在信息系统中流水号作为一项必不可少的基础功能,流水号的生成、控制和管理十分重要。一般一个信息系统中,不同的流水号序列有不同的生成规则。如果我们对这些流水号进行进一步分析,这些不同的流水号序列又可细分为多个规则固定的子序列。基于这种分析,对于这种实际应用需求,如何来高效灵活的生成所需的流水号,就显得十分有意义。
场景分析
首先看几个我们身边常见的流水号:身份证号、税务票、银行的业务流水号、排队号、国务院办公厅发文号(国办发〔2011〕48 号)等。如果对这些流水号进一步分析,我们可以得出,流水号一般由多个规则固定的子序列组成,常见的子序列规则如下:
1. 数字序列:1,2,3 … 或者 00001,00002,00003
2. 字母序列:如 ‘A-Z’
3. 固定字符串:如固定的字符前缀或后缀
4. 时间戳:如 yyyyMMdd 格式对应的为 20130201
5. 其他序列:如罗马字符 ‘I-X’
从另一个角度我们可以说,一个流水号就是上面子序列的排列组合。
基于这样的分析,我们可以创建一个流水号生成引擎,这个引擎可以读取和解析给定的流水号规则,将流水号规则拆分成多个子序列规则,并根据子序列规则生成子序列串,拼接返回最终的流水号。有了这个流水号生成引擎,在系统实际应用的时候,只需要对所需的流水号配置相应的生成规则即可。并且本方案还预留扩展接口,如果实际中遇到个性化的序列生成规则,开发者也可以根据实际需要进行扩展开发,这样也能有效覆盖个性化的需求。
说了这么多,不如图形展示来得直观,整个解决方案的流程见下图:
图 1. 流水号生成流程图
1. 流水号规则读取
2. 流水号规则解析、分割、获取子序列规则
3. 生成子序列任务的派发
4. 子序列汇总和合并
5. 返回最终的生成流水号实例
图 2. 流水号生成过程实例
在上图中,这里需要说明如下几点:
1. 流水号规则用格式字符串表示为:Str@ 国办发〔# DateTime@yyyy # Str@〕# NumSeq@0C0 # Str@ 号,其中: “#” 为子序列间的分隔符,“@” 为子序列内部的分隔符。当然实际应用时,读者也可以按照自己的规则创建格式字符串。
2. 在每个子序列规则定义的内部,用 “@” 分为两部分,前面一部分(Str/DateTime/NumSeq)表明该子序列的类型,用于确定调用哪个类型的 Generator 来生成该子序列串,后面一部分是用于生成子序列串所需的信息。举例来讲,如 “Str@ 国办发〔”,“Str” 表示为固定字符串类型,固定字符串为 “国办发〔”;类似的 “DateTime@yyyy”,“DateTime” 表示时间日期类型,格式为四位的年代号,根据当前时间生成。
3. 在各个 Generator 生成完毕之后,自动拼接并返回最终生成的流水号。
我们可以认为,对于每一个流水号生成规则,似乎可以看做是一个命令队列;对于流水号生成引擎,一方面按照顺序接收生成子序列规则的命令,逐个读取并解析子序列规则,再根据类型分发给各个 Generator 来生成子序列传,最后合并返回最终需要的流水号;每个子序列的 Generator,即为最终的命令的执行者。
经过如上的分析并结合应用场景,我们很容易想到,可以使用 Command 模式来实现。Command 模式,一个典型的应用场景就是处理命令队列,减少行为请求者和动作执行者的耦合,提高灵活性和降低代码冗余。
Command 模式简介
命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或事务(Transaction)模式。命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
适用性:在软件系统中,行为请求者与行为实现者之间通常呈现一种紧耦合的关系。但在某些场合,比如要对行为进行记录撤销重做事务等处理,这种无法抵御变化的紧耦合是不合适的。这种情况下,使用 command 模式将行为请求者与行为实现者进行解耦。
说到这里,我们来看看 Command 模式 UML 类图。
图 3. Command 模式的 UML 类图
这里对上图做一个简单的解释,命令模式涉及到五个角色,分别为:
客户(Client)角色:创建一个具体命令(ConcreteCommand)对象,并设置命令的接收者。
命令(Command)角色:定义一个给所有命令类的抽象接口,定义了统一的 execute() 接口方法。
具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现 Command 接口,并实现 execute() 方法,负责调用接收者的相应操作。
请求者(Invoker)角色:负责调用由 Client 下达的对象执行请求。
接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法为行动方法。
【注意】:在某些简单业务场景下,可在 ConcreteCommand 类的 execute 方法中直接编写实现代码,而省去接收者(Receiver)类以简化代码。如在本应用场景中省去 Receiver 类的创建。
在本应用场景中,正是利用了 Command 模式将行为请求者与行为实现者进行解耦,用于处理命令队列,实现了原子操作上的复用。Command 是对行为进行封装的典型模式,这样做有利于代码的健壮性,可维护性,还有复用性。
实现代码
对应 Command 模式,简单介绍一下本方案中使用到的核心的类和方法:
1. Client - SNGenerateApp.java:
具体的命令请求者,通过调用 addBuildInGenerator() 方法创建内置 generator,暴露 addGenerator() 方法供进一步扩展。
2. Invoker - SNGeneratorEngine.java:
为命令的直接接收者和分派者。
3. ICommand - IGenerator.java:
定义生成接口,子类通过实现 generate() 方法来生成具体的子序列串。
4. ConcreteCommand - XXXXGenerator.java:
各个具体的子序列生成类,实现 generate() 方法,负责最终的的子序列生成,为 Command 的最终执行者。这里简单实现了四个 generator。
a). 字符序列生成器:CharacterSequenceGenerator.java
b). 日期时间生成器:DateTimeGenerator.java
c). 数字序列生成器:NumberSequenceGenerator.java
d). 固定字符串生成器:StringGenerator.java
5. Receiver:
在本例中由于业务场景简单,为简化代码而直接将实现代码写在各 Generator 的 generate() 方法中,无独立的 Receiver 类。
另外还有两个辅助类简单提一下,MockDB.java 用于模拟数据库,存取自增序列;ParamConsts.java 为常量变量类,GeneratorTypeSet.java 定义子序列类型常量。
图 4. 包结构及类清单图
下面是具体的代码展示和介绍。
Generator 接口
定义 Generator,这里只有两个方法,一个是 getGeneratorType() 用于返回 Generator 类型,另一个是 generate() 方法用于生成子序列串。
清单 1. Generator 接口
public interface IGenerator { /** * 获取子序列类型 * @return */ public String getGeneratorType(); /** * 生成子序列串 * @param formatStr * @return */ public String generate(String formatStr,Map paraMap); }
数字序列 Generator
Generator 接口的实现类,用于处理数字序列的生成。下面的代码考虑到变长字符串和定长字符串两种不同情形。在实际中,序列号的生成还需要考虑按照一定的周期重置的情况(如每年都从 1 重新开始计数),可以在扩展实现的时候加以考虑。
清单 2. 数字序列 Generator
public class NumberSequenceGenerator implements IGenerator{ private static final String type = GeneratorTypeSet.NUMBER_SEQUENCE; private String splitC = "C"; @Override public String getGeneratorType() { // TODO Auto-generated method stub return type; } @Override public String generate(String formatStr,Map paraMap) { String seqId = (String)paraMap.get(ParamConsts.PARAM_SEQ_ID); //从数据库中获取当前数值,并自动加 1 int seqNum = MockDB.getSeqNumAndIncrease(seqId); String[] charArray = formatStr.split(splitC); int seqNumLength = Integer.parseInt(charArray[0]); char prefixChar = charArray[1].charAt(0); if(seqNumLength == 0) return String.valueOf(seqNum); else return appendPrefixChar(seqNum,seqNumLength,prefixChar); } /** * 补足前缀以保证序列定长,如 0001, 保持 4 位,不足 4 位用'0'补齐 * @param seqNum 当前数值 * @param seqNumLength 需要返回的字符串长度 * @param prefixChar 用于补齐的前置字符串 * @return */ private String appendPrefixChar(int seqNum,int seqNumLength,char prefixChar) { String seqNumStr = String.valueOf(seqNum); for(int i = seqNumStr.length();i<seqNumLength;i++) { seqNumStr = prefixChar + seqNumStr; } return seqNumStr; } }
字符序列 Generator
Generator 接口的实现类,用于处理字符序列。这里只是一个简单的例子,比如车牌号的生成就可以使用到字符序列。
清单 3. 字符序列 Generator
public class CharacterSequenceGenerator implements IGenerator{ private static final String type = GeneratorTypeSet.CHARATER_SEQUENCE; //从’A’开始,自动增长 private char c = 'A'; @Override public String getGeneratorType() { // TODO Auto-generated method stub return type; } @Override public String generate(String formatStr,Map paraMap) { return String.valueOf((char)c++); } }
日期序列 Generator
Generator 接口的实现类,用于生成时间日期戳。下面的代码基于 java.text.SimpleDateFormat 实现。
清单 4. 日期序列 Generator
public class DateTimeGenerator implements IGenerator{ private static final String type = GeneratorTypeSet.DATE_TIME; @Override public String getGeneratorType() { // TODO Auto-generated method stub return type; } @Override public String generate(String formatStr,Map paraMap) { SimpleDateFormat sdf = new SimpleDateFormat(); sdf.applyPattern(formatStr); return sdf.format(new Date()); } }
固定字符 Generator
Generator 接口的实现类,用于生成固定字符。固定字符 Generator 比较简单,即直接返回配置的字符串。
清单 5. 固定字符 Generator
public class StringGenerator implements IGenerator{ private static final String type = GeneratorTypeSet.STRING; @Override public String getGeneratorType() { // TODO Auto-generated method stub return type; } @Override public String generate(String format,Map paraMap) { return format; } }
流水号生成引擎 Engine
流水号引擎需要负责接收流水号生成规则,根据类型调用对应的各个 Generator 生成子序列串,最后将子序列串并拼接并返回最终的流水号。
清单 6. 流水号生成引擎 Engine
public class SNGeneratorEngine { private static SNGeneratorEngine snGeneratorEngine = new SNGeneratorEngine(); private static Map<String,IGenerator> generatorMap = new HashMap<String,IGenerator>(); /**流水号生成规则*/ private String formatStr; /**子序列间分隔符*/ private String splitChar = "#"; /**子序列内部分隔符*/ private String innerChar = "@"; /**流水号子序列生成规则*/ private String[] subFormatStr; /** * 按照类型和 Generator 实例存放 * @param Generator */ public void addGenerator(IGenerator Generator) { generatorMap.put(Generator.getGeneratorType(),Generator); } private SNGeneratorEngine() { } public static SNGeneratorEngine getInstance() { return snGeneratorEngine; } /** * 接收流水号格式字符串,并分割成子序列 * @param formatStr */ public void setFormatStr(String formatStr) { this.formatStr = formatStr; subFormatStr = this.formatStr.split(splitChar); } /** * 生成流水号:分发给各个子序列 Generator 生成 * @param parameterMap * @return */ public String generate(Map parameterMap) { StringBuffer seriableNumber = new StringBuffer(); for(String format:subFormatStr) { seriableNumber.append(generateSubSN(format,parameterMap)); } return seriableNumber.toString(); } /** * 根据类型调用子序列 Generator 生成 * @param generateSubSN * @param parameterMap * @return */ private String generateSubSN(String subFormatStr,Map parameterMap) { String[] innerSubStr = subFormatStr.split(innerChar); IGenerator Generator = this.getGenerator(innerSubStr[0]); return Generator.generate(innerSubStr[1],parameterMap); } /**根据 GeneratorType 获取 Generator 实例*/ private IGenerator getGenerator(String generatorType) { return generatorMap.get(generatorType); } }
流水号生成应用 App
流水号应用 SNGenerateApp.java 类,声明了 SNGeneratorEngine 对象的实例,并通过调用 addBuildInGenerator() 创建内置的 Generator 类并添加到 SNGeneratorEngine 中,通过 generateSN() 方法来发送生成流水号的请求。
清单 7. 流水号生成应用 App
public class SNGenerateApp { private SNGeneratorEngine snGenEngine = SNGeneratorEngine.getInstance(); /** * 设置内置的生成器 */ private void addBuildInGenerator(){ snGenEngine.addGenerator(new CharacterSequenceGenerator()); snGenEngine.addGenerator(new DateTimeGenerator()); snGenEngine.addGenerator(new NumberSequenceGenerator()); snGenEngine.addGenerator(new StringGenerator()); } /** * 添加 Generator,提供扩展功能 * @param generator */ public void addGenerator(IGenerator generator) { snGenEngine.addGenerator(generator); } /** * 生成序列号 * @param snFormatStr 流水号格式字符串 * @param parameterMap 参数列表 * @return */ public String generateSN(String snFormatStr,Map parameterMap) { snGenEngine.setFormatStr(snFormatStr); return snGenEngine.generate(parameterMap); } }
应用举例
现在,我们就通过创建 SNGenerateApp 实例并执行相应方法来模拟流水号的生成。
这里仍然以前文提到的国务院办公厅发文号(国办发〔2011〕48 号)为例来说明,其格式字符串为:“Str@ 国办发〔#DateTime@yyyy#Str@〕#NumSeq@0C0#Str@ 号”,使用 MockDB.SEQ_ID_1 的 sequence 获取自增数值,示例代码如下:
清单 8. 应用示例代码 1
SNGenerateApp appication = new SNGenerateApp();//创建 App 实例 appication.addBuildInGenerator(); //设置内置 Generator //1. 设定流水号生成规则 国务院办公厅发文号(国办发〔2014〕48 号) System.out.println("1. 生成 国务院办公厅发文号(国办发〔2014〕48 号)"); //设定流水号生成规则 String snFormatStr = "Str@ 国办发〔#DateTime@yyyy#Str@〕#NumSeq@0C0#Str@ 号"; Map parameterMap1 = new HashMap(); //设定参数 parameterMap1.put(ParamConsts.PARAM_SEQ_ID, MockDB.SEQ_ID_1); //使用 sequence id 1 进行流水自增 for(int i=1;i<=5;i++)//生成 5 个流水号 { System.out.println("流水号"+i+":"+appication.generateSN(snFormatStr,parameterMap1)); }
输出结果:
1. 生成 国务院办公厅发文号(国办发〔2014〕48 号)
引用
流水号 1:国办发〔2014〕48 号
流水号 2:国办发〔2014〕49 号
流水号 3:国办发〔2014〕50 号
流水号 4:国办发〔2014〕51 号
流水号 5:国办发〔2014〕52 号
流水号 2:国办发〔2014〕49 号
流水号 3:国办发〔2014〕50 号
流水号 4:国办发〔2014〕51 号
流水号 5:国办发〔2014〕52 号
如果我们要模拟生成常见的 ICP 备案号((沪 ICP 备 05172190 号))呢?很简单,只需要规则重新配置流水号的格式字符串,在这里对应的为“Str@ 沪 ICP 备 #NumSeq@8C0#Str@ 号”,无需修改任何代码。注意,一般来讲不同的流水号会用到不同的 sequence 数值,我们也做了模拟,这里传入 MockDB.SEQ_ID_2 的 sequence ID 来获取自增数值。
清单 9. 应用示例代码 2
//2. 例如生成 :ICP 备案号(沪 ICP 备 05172190 号) System.out.println("2. 生成 ICP 备案号(沪 ICP 备 05172190 号)"); //设定规则 snFormatStr = "Str@ 沪 ICP 备 #NumSeq@8C0#Str@ 号"; Map parameterMap2 = new HashMap();//设定参数 parameterMap2.put(ParamConsts.PARAM_SEQ_ID, MockDB.SEQ_ID_2);//使用 sequence id 2 进行流水自增 for(int i=1;i<=5;i++)//生成 5 个流水号 { System.out.println("流水号"+i+":"+ appication.generateSN(snFormatStr,parameterMap2)); }
输出结果:
2. 生成 ICP 备案号(沪 ICP 备 05172190 号)
引用
流水号 1:沪 ICP 备 05172190 号
流水号 2:沪 ICP 备 05172191 号
流水号 3:沪 ICP 备 05172192 号
流水号 4:沪 ICP 备 05172193 号
流水号 5:沪 ICP 备 05172194 号
流水号 2:沪 ICP 备 05172191 号
流水号 3:沪 ICP 备 05172192 号
流水号 4:沪 ICP 备 05172193 号
流水号 5:沪 ICP 备 05172194 号
想必有读者会问,如果我想生成的流水号还有一些其他特殊的生成规则怎么办?也很简单,只需要编写符合你业务要求的 Generator,然后将这个 Generator 通过 SNGenerateApp.addGenerator() 添加进去,最后再配置相应的流水号格式字符串即可。这样我们就会发现,只要新添加了一个 Generator,那么这个应用就具备相应的子序列生成功能,能够为以后生成含有该子序列的任意流水号提供支持。换句话说,我们可以生成出任意流水号,只要其生成规则是基于当前已有的 Generator 的排列组合,所需要做的只是配置一下流水号格式字符串,是不是非常方便和灵活呢?这也正是应用 Command 模式给我们带来的巨大好处。
总结
本文简单介绍了应用 Command 模式及其优点,并通过一个基于自定义的格式字符串的流水号生成的方案,进一步让读者了解应用场景。采用 Command 模式,我们将原本复杂的流水号生成过程化整为零,分解成各个可以重用的子序列,根据各子序列的类型派发给对应的 Generator,最终在拼接返回流水号。该实现方案适用于各种复杂规则流水号的生成,并且十分易于扩展和维护,在实际应用中也取得了不错的效果。通过本例,希望读者能够进一步了解 Command 模式,应用到适合的场景中。
参考资料
学习
阅读 GoF(“四人帮”)《设计模式》书籍,原名《Design Patterns: Elements of Reusable Object-Oriented Software》深入了解更多设计模式。
参考百度百科中关于 Command 模式的词条,可点击 command 模式。
访问 “developerWorks Java 设计模式与建模专题”,了解更多设计模式相关的专题讨论与实践。
developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。
发表评论
-
[轉]java中String的“==”和equals判断相等性
2017-11-27 15:24 457http://blog.csdn.net/liucheng20 ... -
Android NDK搭建環境
2017-11-27 13:25 580https://www.cnblogs.com/ut2016- ... -
Java语言编码规范
2017-06-27 15:54 569http://morningspace.51.net/reso ... -
Java AWT应用示例 - 持续更新
2017-06-27 11:39 637/* * @(#)SampleAWT.java ... -
Java 事件模型基础:监听器、事件、事件源、事件注册
2017-06-27 08:55 823转自 http://www.cnblogs.com/mengd ... -
Java中基本数据类型和包装器类型的关系
2017-06-27 08:37 757转自 http://www.cnblogs.com/h ... -
深入剖析Java中的装箱和拆箱
2017-06-27 08:31 487转自 http://www.cnblogs.com/dolph ... -
Java JAR打包相关
2017-06-26 20:57 584JAVA jar打包时MANIFEST.MF格式约定 http ... -
Java反编译工具 - Java Decompiler
2017-06-21 20:14 474http://jd.benow.ca/ JD-GUI is ... -
javac,使用"-d ."与省略-d的区别
2017-06-20 20:27 810在当前工作目录下生成class文件,一般情况下有两种方法 ... -
JAVA环境变量配置
2017-06-19 13:56 355参考Link http://www.cnblogs.com/x ... -
JAVA高级应用之反射
2017-06-19 10:26 436参考链接 http://www.cnblogs.com/men ... -
Java高级应用之JNI
2017-06-19 09:00 599参考link http://www.cnblogs.com/l ... -
JDK工具appletviewer详解
2017-06-19 08:25 1131http://www.softown.cn/post/170. ... -
Java rt.jar 源码分析
2017-06-18 10:32 650jdk1.8.0_102 SRC +com +java ... -
http://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/
2016-07-15 16:30 512转自 http://www.ibm.com/developer ... -
一个跨平台的 C++ 内存泄漏检测器
2016-07-11 16:21 516转自 http://www.ibm.com/developer ... -
C++ 应用程序性能优化,第 6 章:内存池
2016-07-10 18:58 1580转自 http://www.ibm.com/devel ... -
[行为型模式] 访问者模式的理解
2016-07-04 14:25 0[img][/img] [img][/img] 头文件 ... -
[行为型模式] 策略模式的理解
2016-07-04 14:24 0[img][/img] [img][/img] 头文件 ...
相关推荐
### Observer与Command模式在VTK类库设计中的应用研究 #### 一、引言 VTK(Visualization Toolkit)是一套开源的三维可视化开发库,在国外得到了广泛应用,而在国内的研究相对较少,导致其应用受到一定限制。为了...
**命令模式(Command模式)详解** 命令模式是一种行为设计模式,它将请求封装为一个对象,使得我们可以使用不同的请求、队列或者记录请求日志,还可以支持可撤销的操作。在命令模式中,我们创建表示各种操作的类...
Command模式是一种行为设计模式,它将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。在Java编程中,这种模式被广泛应用于实现命令行操作、GUI事件...
**命令模式(Command Pattern)详解** 命令模式是一种行为设计模式,它将请求封装为一个对象,使得你可以使用不同的请求、队列或者日志请求,也可以支持可撤销的操作。在C++中实现命令模式,可以有效地解耦调用者和...
1. **事务管理**:Command模式可以用来创建一系列操作,这些操作可以作为一个整体进行回滚或提交,如数据库事务。 2. **宏命令**:允许用户将多个命令组合成一个宏命令,一次性执行多个操作。 3. **菜单系统**:在...
Command模式是一种行为设计模式,它的主要目的是将命令的发起者(Invoker)与命令的执行者(Receiver)解耦。在Command模式中,一个命令对象封装了特定的请求,调用者(Invoker)只需要知道如何发送命令,而无需知道...
Command模式是一种行为设计模式,它将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。在C++中实现Command模式,我们可以遵循以下几个关键步骤和...
这确保了即使在多应用实例之间,流水号也能保持全局唯一。 为了更好地适应实际项目,你可能还需要考虑以下几点: 1. **错误处理**:在与数据库交互时,应处理可能出现的异常,如连接失败、事务冲突等。 2. **并发...
Command模式是一种行为设计模式,它将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。在软件工程中,这种模式扮演着非常重要的角色,尤其是在需要...
用C++实现Command模式实现undo操作
- 使用Greenplum提供的工具进行自动化的监控和管理,例如Greenplum Command Center等。 综上所述,为了提高Greenplum数据库集群的成功率,涉及到的不仅是技术层面的知识,还包括运营和管理的策略。最佳实践的掌握...
Command设计模式是一种行为设计模式,它将请求封装为一个对象,使得你可以参数化不同请求,对请求排队或者记录请求日志,以及支持可撤销的操作。在.NET开发中,C#语言是实现这种模式的理想选择,因为它提供了丰富的...
实践Command模式的一个好处是它可以支持命令的撤销(undo)。这可以通过在Command接口中添加undo()方法来实现,具体命令类则需要保存足够的信息来执行undo操作。例如,OpenFileCommand可以保存上一次打开的文件名...
在“设计模式-command”中,我们看到这个模式的应用,通过将一个操作封装为一个对象(即命令对象),使得发送者可以无需知道接收者的具体实现,只需要知道如何发送命令即可。这种松耦合使得系统更易于维护和扩展。 ...
本资源“Java 设计模式最佳实践”旨在帮助程序员掌握并应用这些模式,提升软件设计能力。 1. **单例模式(Singleton)**:保证一个类只有一个实例,并提供全局访问点。在Java中,通常通过双重检查锁定、静态内部类...
其中,“Command”(命令)模式是一种行为型设计模式,它允许将请求封装成对象,从而使你能够用不同的请求对客户端进行参数化,对请求进行排队或记录请求日志,以及支持可撤销的操作。 #### 耦合与变化 耦合是指两...
命令模式是一种行为设计模式,它将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。在IT行业中,命令模式广泛应用于软件架构和系统设计,以提高代码...
singleton模式、Factory模式、Abstract Factory模式、Builder模式、Prototype模式、Adapter模式、Bridge模式、Composite模式、Decorator模式、Flyweight模式、Proxy模式、Chain of Responsibility模式、Command模式...