重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」
作者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章
沉淀、分享、成长,让自己和他人都能有所收获!
一、前言
场地和场景的重要性
射击需要去靶场学习、滑雪需要去雪场体验、开车需要能上路实践,而编程开发除了能完成产品的功能流程,还需要保证系统的可靠性能。就像你能听到的一些系统监控指标;QPS
、TPS
、TP99
、TP999
、可用率
、响应时长
等等,而这些指标的总和评估就是一个系统的健康度。但如果你几乎没有听到这样的技术术语,也没接触过类似高并发场景,那么就很像驾驶证的科目1考了100分,但不能上路。没有这样的技术场景给你训练,让你不断的体会系统的脾气秉性,即便你有再多的想法都没法实现。所以,如果真的想学习一定要去一个有实操的场景,下水试试才能学会狗刨。
你的视觉盲区有多大
同样一本书、同样一条路、同样一座城,你真的以为生活有选择吗?有时候很多选项都是摆设,给你多少次机会你都选的一模一样。这不是你选不选而是你的认知范围决定了你下一秒做的事情,另外的一个下一秒又决定了再下一个下一秒。就像管中窥豹一样,20%的面积在你视觉里都是黑色的,甚至就总是忽略看不到,而这看不到的20%就是生命中的时运!但,人可以学习,可以成长,可以脱胎换骨,可以努力付出,通过一次次的蜕变而看到剩下的20%!
没有设计图纸你敢盖楼吗
编程开发中最好的什么,是设计。运用架构思维、经验心得、才华灵感
,构建出最佳的系统。真正的研发会把自己写的代码当做作品来欣赏,你说这是一份工作,但在这样的人眼里这可不是一份工作,而是一份工匠精神。就像可能时而你也会为自己因为一个niubility
的设计而豪迈万丈,为能上线一个扛得住每秒200万访问量的系统会精神焕发。这样的自豪感就是一次次垒砖一样垫高脚底,不断的把你的视野提高,让你能看到上层设计也能知晓根基建设。可以把控全局,也可以治理细节。这一份份知识的沉淀,来帮助你绘制出一张系统架构蓝图。
二、开发环境
- JDK 1.8
- Idea + Maven
- 涉及工程三个,可以通过关注公众号:
bugstack虫洞栈
,回复源码下载
获取(打开获取的链接,找到序号18)
工程 | 描述 |
---|---|
itstack-demo-design-13-00 | 场景模拟工程;模拟一个上线流程审批的接口。 |
itstack-demo-design-13-01 | 使用一坨代码实现业务需求 |
itstack-demo-design-13-02 | 通过设计模式优化改造代码,产生对比性从而学习 |
三、责任链模式介绍
击鼓传雷,看上图你是否想起周星驰有一个电影,大家坐在海边围成一个圈,拿着一个点燃的炸弹,互相传递。
责任链模式的核心是解决一组服务中的先后执行处理关系,就有点像你没钱花了,需要家庭财务支出审批,10块钱以下找闺女审批,100块钱先闺女审批在媳妇审批。你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。
四、案例场景模拟
在本案例中我们模拟在618大促期间的业务系统上线审批流程场景
像是这些一线电商类的互联网公司,阿里、京东、拼多多等,在618期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近618有时候也有一些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。
而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线更严格,级别也更高,但对于研发人员来说同样是点击相同的提审按钮,等待审核。
接下来我们就模拟这样一个业务诉求场景,使用责任链的设计模式来实现此功能。
1. 场景模拟工程
itstack-demo-design-13-00
└── src
└── main
└── java
└── org.itstack.demo.design
└── AuthService.java
- 这里的代码结构比较简单,只有一个模拟审核和查询审核结果的服务类。相当于你可以调用这个类去审核工程和获取审核结构,这部分结果信息是模拟的写到缓存实现。
2. 场景简述
2.1 模拟审核服务
public class AuthService {
private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();
public static Date queryAuthInfo(String uId, String orderId) {
return authMap.get(uId.concat(orderId));
}
public static void auth(String uId, String orderId) {
authMap.put(uId.concat(orderId), new Date());
}
}
- 这里面提供了两个接口一个是查询审核结果(
queryAuthInfo
)、另外一个是处理审核(auth
)。 - 这部分是把由谁审核的和审核的单子ID作为唯一key值记录到内存Map结构中。
五、用一坨坨代码实现
这里我们先使用最直接的方式来实现功能
按照我们的需求审批流程,平常系统上线只需要三级负责人审批就可以,但是到了618大促时间点,就需要由二级负责以及一级负责人一起加入审批系统上线流程。在这里我们使用非常直接的if判断方式来实现这样的需求。
1. 工程结构
itstack-demo-design-13-01
└── src
└── main
└── java
└── org.itstack.demo.design
└── AuthController.java
- 这部分非常简单的只包含了一个审核的控制类,就像有些伙伴开始写代码一样,一个类写所有需求。
2. 代码实现
public class AuthController {
private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {
// 三级审批
Date date = AuthService.queryAuthInfo("1000013", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", "王工");
// 二级审批
if (authDate.after(f.parse("2020-06-01 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))) {
date = AuthService.queryAuthInfo("1000012", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", "张经理");
}
// 一级审批
if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))) {
date = AuthService.queryAuthInfo("1000011", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", "段总");
}
return new AuthInfo("0001", "单号:", orderId, " 状态:审批完成");
}
}
- 这里从上到下分别判断了在指定时间范围内由不同的人员进行审批,就像618上线的时候需要三个负责人都审批才能让系统进行上线。
- 像是这样的功能看起来很简单的,但是实际的业务中会有很多部门,但如果这样实现就很难进行扩展,并且在改动扩展调整也非常麻烦。
3. 测试验证
3.1 编写测试类
@Test
public void test_AuthController() throws ParseException {
AuthController authController = new AuthController();
// 模拟三级负责人审批
logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
logger.info("测试结果:{}", "模拟三级负责人审批,王工");
AuthService.auth("1000013", "1000998004813441");
// 模拟二级负责人审批
logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
AuthService.auth("1000012", "1000998004813441");
// 模拟一级负责人审批
logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
logger.info("测试结果:{}", "模拟一级负责人审批,段总");
AuthService.auth("1000011", "1000998004813441");
logger.info("测试结果:{}", "审批完成");
}
- 这里模拟每次查询是否审批完成,随着审批的不同节点,之后继续由不同的负责人进行审批操作。
authController.doAuth
,是查看审批的流程节点、AuthService.auth
,是审批方法用于操作节点流程状态。
3.2 测试结果
23:25:00.363 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
23:25:00.366 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟三级负责人审批,王工
23:25:00.367 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待二级审批负责人 张经理"}
23:25:00.367 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟二级负责人审批,张经理
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待一级审批负责人 段总"}
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟一级负责人审批,段总
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:审批完成
Process finished with exit code 0
- 从测试结果上可以看到一层层的由不同的人员进行审批,审批完成后到下一个人进行处理。单看结果是满足我们的诉求,只不过很难扩展和调整流程,相当于代码写的死死的。
六、责任链模式重构代码
接下来使用装饰器模式来进行代码优化,也算是一次很小的重构。
责任链模式可以让各个服务模块更加清晰,而每一个模块间可以通过next
的方式进行获取。而每一个next
是由继承的统一抽象类实现的。最终所有类的职责可以动态的进行编排使用,编排的过程可以做成可配置化。
1. 工程结构
itstack-demo-design-13-02
└── src
└── main
└── java
└── org.itstack.demo.design
├── impl
│ ├── Level1AuthLink.java
│ ├── Level2AuthLink.java
│ └── Level3AuthLink.java
├── AuthInfo.java
└── AuthLink.java
责任链模式模型结构
- 上图是这个业务模型中责任链结构的核心部分,通过三个实现了统一抽象类
AuthLink
的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。 - 一般在使用责任链时候如果是场景比较固定,可以通过写死到代码中进行初始化。但如果业务场景经常变化可以做成xml配置的方式进行处理,也可以落到库里进行初始化操作。
2. 代码实现
2.1 责任链中返回对象定义
public class AuthInfo {
private String code;
private String info = "";
public AuthInfo(String code, String ...infos) {
this.code = code;
for (String str:infos){
this.info = this.info.concat(str);
}
}
// ...get/set
}
- 这个类的是包装了责任链处理过程中返回结果的类,方面处理每个责任链的返回信息。
2.2 链路抽象类定义
public abstract class AuthLink {
protected Logger logger = LoggerFactory.getLogger(AuthLink.class);
protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
protected String levelUserId; // 级别人员ID
protected String levelUserName; // 级别人员姓名
private AuthLink next; // 责任链
public AuthLink(String levelUserId, String levelUserName) {
this.levelUserId = levelUserId;
this.levelUserName = levelUserName;
}
public AuthLink next() {
return next;
}
public AuthLink appendNext(AuthLink next) {
this.next = next;
return this;
}
public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
- 这部分是责任链,链接起来的核心部分。
AuthLink next
,重点在于可以通过next
方式获取下一个链路需要处理的节点。 levelUserId
、levelUserName
,是责任链中的公用信息,标记每一个审核节点的人员信息。- 抽象类中定义了一个抽象方法,
abstract AuthInfo doAuth
,这是每一个实现者必须实现的类,不同的审核级别处理不同的业务。
2.3 三个审核实现类
Level1AuthLink
public class Level1AuthLink extends AuthLink {
public Level1AuthLink(String levelUserId, String levelUserName) {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
Level2AuthLink
public class Level2AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-11 00:00:00");
private Date endDate = f.parse("2020-06-20 23:59:59");
public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
Level3AuthLink
public class Level3AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-01 00:00:00");
private Date endDate = f.parse("2020-06-25 23:59:59");
public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
- 如上三个类;
Level1AuthLink
、Level2AuthLink
、Level3AuthLink
,实现了不同的审核级别处理的简单逻辑。 - 例如第一个审核类中会先判断是否审核通过,如果没有审核通过则返回结果给调用方,引导去审核。(这里简单模拟审核后有时间信息不为空,作为判断条件)
- 判断完成后获取下一个审核节点;
super.next();
,如果不存在下一个节点,则直接返回结果。 - 之后是根据不同的业务时间段进行判断是否需要,二级和一级的审核。
- 最后返回下一个审核结果;
next.doAuth(uId, orderId, authDate);
,有点像递归调用。
3. 测试验证
3.1 编写测试类
@Test
public void test_AuthLink() throws ParseException {
AuthLink authLink = new Level3AuthLink("1000013", "王工")
.appendNext(new Level2AuthLink("1000012", "张经理")
.appendNext(new Level1AuthLink("1000011", "段总")));
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟三级负责人审批
AuthService.auth("1000013", "1000998004813441");
logger.info("测试结果:{}", "模拟三级负责人审批,王工");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟二级负责人审批
AuthService.auth("1000012", "1000998004813441");
logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟一级负责人审批
AuthService.auth("1000011", "1000998004813441");
logger.info("测试结果:{}", "模拟一级负责人审批,段总");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
}
- 这里包括最核心的责任链创建,实际的业务中会包装到控制层;
AuthLink authLink = new Level3AuthLink("1000013", "王工") .appendNext(new Level2AuthLink("1000012", "张经理") .appendNext(new Level1AuthLink("1000011", "段总")));
通过把不同的责任节点进行组装,构成一条完整业务的责任链。 - 接下里不断的执行查看审核链路
authLink.doAuth(...)
,通过返回结果对数据进行3、2、1级负责人审核,直至最后审核全部完成。
3.2 测试结果
23:49:46.585 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟三级负责人审批,王工
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待二级审批负责人 张经理"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟二级负责人审批,张经理
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待一级审批负责人 段总"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟一级负责人审批,段总
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0000","info":"单号:1000998004813441 状态:一级审批完成负责人 时间:2020-06-18 23:49:46 审批人:段总"}
Process finished with exit code 0
- 从上述的结果可以看到我们的责任链已经生效,按照责任链的结构一层层审批,直至最后输出审批结束到一级完成的结果。
- 这样责任链的设计方式可以方便的进行扩展和维护,也把if语句干掉了。
七、总结
- 从上面代码从if语句重构到使用责任链模式开发可以看到,我们的代码结构变得清晰干净了,也解决了大量if语句的使用。并不是if语句不好,只不过if语句并不适合做系统流程设计,但是在做判断和行为逻辑处理中还是非常可以使用的。
- 在我们前面学习结构性模式中讲到过组合模式,它像是一颗组合树一样,我们搭建出一个流程决策树。其实这样的模式也是可以和责任链模型进行组合扩展使用,而这部分的重点在于如何关联链路的关联,最终的执行都是在执行在中间的关系链。
- 责任链模式很好的处理单一职责和开闭原则,简单了耦合也使对象关系更加清晰,而且外部的调用方并不需要关心责任链是如何进行处理的(以上程序中可以把责任链的组合进行包装,在提供给外部使用)。但除了这些优点外也需要是适当的场景才进行使用,避免造成性能以及编排混乱调试测试疏漏问题。
八、推荐阅读
1. 重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」
2. 重学 Java 设计模式:实战原型模式「上机考试多套试,每人题目和答案乱序排列场景」
3. 重学 Java 设计模式:实战桥接模式「多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)场景」
4. 重学 Java 设计模式:实战组合模式「营销差异化人群发券,决策树引擎搭建场景」
5. 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」
重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」的更多相关文章
- 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...
- 详解java设计模式之责任链模式
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt175 从击鼓传花谈起 击鼓传花是一种热闹而又紧张的饮酒游戏.在酒宴上宾客依次 ...
- JAVA设计模式之责任链模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...
- java设计模式之责任链模式(Chain of Responsibility)
转自:http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html 在阎宏博士的<JAVA与模式>一书中开头是这样 ...
- java设计模式之责任链模式以及在java中作用
责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求.发出这个请求的客户端并不知道链上的哪一个 ...
- Java设计模式之责任链模式、职责链模式
本文继续介绍23种设计模式系列之职责链模式. 什么是链 1.链是一系列节点的集合. 2..链的各节点可灵活拆分再重组. 职责链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间 ...
- Java设计模式之八 ----- 责任链模式和命令模式
前言 在上一篇中我们学习了结构型模式的享元模式和代理模式.本篇则来学习下行为型模式的两个模式, 责任链模式(Chain of Responsibility Pattern)和命令模式(Command ...
- Java设计模式应用——责任链模式
生产一个产品,需要依次执行多个步骤,才能完成,那么是使用责任链模式则是极好的. 在性能告警模块开发过程中,创建一条告警规则需要执行阈值解析,中间表生成,流任务生成,规则入库,告警事件入库等诸多操作.如 ...
- 击鼓传花联想到了Java设计模式:责任链模式
目录 应用场景 简单示例 责任链模式 定义 意图 主要解决问题 何时使用 优缺点 击鼓传花的故事 应用场景 http web请求处理,请求过来后将经过转码.解析.参数封装.鉴权等一系列的处理(责任), ...
随机推荐
- centos安装以及网络配置
Linux安装 1.Linux安装完成后 第一个问题就是网络不通的问题 ,问题图片如下: 解决办法: 三种网络模式: 桥接模式:虚拟机和宿主机是兄弟关系,统一由宿主机连接的路由器分发ip NAT模式: ...
- [SD.TEAM语录]AC语录
决定做了就要马上去做,不要有任何犹豫 本站文章为宝宝巴士 SD.Team原创,转载务必在明显处注明:(作者官方网站:宝宝巴士) 转载自[宝宝巴士SuperDo团队] 原文链接: http:// ...
- MVC案例之通过配置切换底层存储源(面向接口)
1.深入理解面向接口编程: 在类中调用接口的方法,而不必关心具体的实现.这将有利于代码的解耦.使程序有更好的可移植性 和可扩展性 动态修改 Customer 的存储方式:通过修改类路径下的 switc ...
- 高吞吐量的分布式发布订阅消息系统Kafka之Producer源码分析
引言 Kafka是一款很棒的消息系统,今天我们就来深入了解一下它的实现细节,首先关注Producer这一方. 要使用kafka首先要实例化一个KafkaProducer,需要有brokerIP.序列化 ...
- python中的数据存储认识
声明:本人是一个初学者,博客内容基本也是一些基础的东西,如果说的有什么问题欢迎纠正. 前言 许多人初学python之前应该也学习过其他的语言,比如博大精深的c语言,笔者在学习python之前就学习过c ...
- ASP.NET使用HttpHandler进行页面静态化(自动生成页面)
这次的Demo是,一个根页面,点击链接创建子页面,子页面都是一个Template页面进行替换的 一个根页面 <%@ Page Language="C#" AutoEventW ...
- Java实现字母去重
描述 给定一个字符串S,每次操作你可以将其中任意一个字符修改成其他任意字符. 请你计算最少需要多少次操作,才能使得S中不存在两个相邻的相同字符. 输入 只包含小写字母的字符串S. 1 ≤ |S| ≤ ...
- java实现第四届蓝桥杯世纪末星期
世纪末星期 题目描述 曾有邪教称1999年12月31日是世界末日.当然该谣言已经不攻自破. 还有人称今后的某个世纪末的12月31日,如果是星期一则会- 有趣的是,任何一个世纪末的年份的12月31日都不 ...
- WPF 学习(一)
一.WPF介绍 WPF全称 Windows Presentation Foundation,干啥用的? 主要是用来制作Windows桌面客户端软件的. .Net平台下制作Windows桌面客户端软件主 ...
- 09 . Nginx配置LNMP和LNMT架构
安装LNMP架构 环境清单 list CentOS7.3 proxysql-2.0.12-1-centos7.x86_64.rpm mysql-5.7.23-1.el7.x86_64.rpm-bund ...