如何优雅的在业务中使用设计模式(代码如诗)
前言
> 有段时间没写文章了,最近沉迷Rust,无法自拔,锈儿有毒;这真是门非常有趣的语言,很多地方的设计,真的是满足了我所有的向往。 > > 当然,这也不是一门简单的语言,提出所有权的概念,引入了极多符号:mut、&mut、ref mut、&、*、as_mut、as_ref。。。让人头秃。。。 > > 之前看到过一句话,觉得很不错:学习Rust并不会给你带来智商上的优越感,但或许会让你重新爱上编程。
大家如果阅读过一些开源框架的源码,可能会发现其中数不尽的抽象类,设计模式拈手而来,在功能框架中,可以使用设计模式随心所欲的解耦;在实际的复杂业务中,当然也可以应用合适的设计模式。
这篇文章,我会将实际业务较为常见场景,如何合适将设计模式应用其中
- 此处的应用绝不是生搬硬套,是我经过深思熟虑,并将较为复杂的业务进行全面重构后,得出的一套行之有效的思路历程
- 任何一个设计模式都是一个伟大的经验及其思想总结,千人千面,如果对文章中内容,有不同的意见,希望你能在评论中提出,我们共同探讨,共同进步
本文章是一篇弱代码类型文章,我会画大量的图片向大家展示,引用设计模式后,会对原有的业务流程,产生什么样的影响。
前置知识
这里,需要了解下基础知识,什么是责任链模式和策略模式
责任链模式,在很多开源框架中都是有所应用,你如果听到啥啥拦截器,基本就是责任链模式,责任链模式的思想很简单,但是有很多种实现方式
- 最简单的链表实现就和OkHttp的拦截器实现大相径庭
- OkHttp的拦截器实现和Dio拦截器实现结构相同,但遍历方式不一样
- 很多骚操作:我喜欢OkHttp的实现方式,喜欢dio的Api设计,结尾会给出一个结合这俩者思想的通用拦截器
策略模式,或是天生适合业务,同一模块不同类型业务,如果行为相同,或许就可以考虑使用策略模式去解耦了
责任链模式
这边用Dart写一个简单的拦截器,dart和java非常像
- 为了减少语言差异,我就不使用箭头语法了
- 下划线表示私有
用啥语言不重要,这边只是用代码简单演示下思想
此处实现就用链表了;如果,使用数组的形式,需要多写很多逻辑,数组的优化写法在结尾给出,此处暂且不表
结构
- 责任链的结构,通常有俩种结构
- 链表结构:链表构建责任链,十分便捷的就能和下一节点建立联系
- 数组结构:数组,用通用的List即可,方便增删,不固定长度(别费劲的用固定长度Array了,例如:int[]、String[])
- 实现一个链表实体很简单
abstract class InterceptChain<t> { InterceptChain? next; void intercept(T data) { next?.intercept(data); } }
实现
- 拦截器实现
/// 该拦截器以最简单的链表实现 abstract class InterceptChain<t> { InterceptChain? next; void intercept(T data) { next?.intercept(data); } } class InterceptChainHandler<t> { InterceptChain? _interceptFirst; void add(InterceptChain interceptChain) { if (_interceptFirst == null) { _interceptFirst = interceptChain; return; } var node = _interceptFirst!; while (true) { if (node.next == null) { node.next = interceptChain; break; } node = node.next!; } } void intercept(T data) { _interceptFirst?.intercept(data); } }
- 使用
- 调整add顺序,就调整了对应逻辑的节点,在整个责任链中的顺序
- 去掉intercept重写方法中的super.intercept(data),就能实现拦截后续节点逻辑
void main() { var intercepts = InterceptChainHandler<string>(); intercepts.add(OneIntercept()); intercepts.add(TwoIntercept()); intercepts.intercept("测试拦截器"); } class OneIntercept extends InterceptChain<string> { @override void intercept(String data) { data = "$data:OneIntercept"; print(data); super.intercept(data); } } class TwoIntercept extends InterceptChain<string> { @override void intercept(String data) { data = "$data:TwoIntercept"; print(data); super.intercept(data); } }
- 打印结果
测试拦截器:OneIntercept 测试拦截器:OneIntercept:TwoIntercept
策略模式
结构
- 策略模式最重要的:应该就是对抽象类的设计,对行为的抽象
实现
- 定义抽象类,抽象行为
/// 结合适配器模式的接口适配:抽象必须实现行为,和可选实现行为 abstract class BusinessAction { ///创建相应资源:该行为必须实现 void create(); ///可选实现 void dealIO() {} ///可选实现 void dealNet() {} ///可选实现 void dealSystem() {} ///释放资源:该行为必须实现 void dispose(); }
- 实现策略类
//Net策略 class NetStrategy extends BusinessAction { @override void create() { print("创建Net资源"); } @override void dealNet() { print("创建IO资源"); } @override void dispose() { print("创建Net资源"); } } ///IO策略 class IOStrategy extends BusinessAction { @override void create() { print("创建IO资源"); } @override void dealIO() { print("处理IO逻辑"); } @override void dispose() { print("释放IO资源"); } }
- 使用
void main() { var type = 1; BusinessAction strategy; //不同业务使用不同策略 if (type == 0) { strategy = NetStrategy(); } else { strategy = IOStrategy(); } //开始创建资源 strategy.create(); //......... 省略N多逻辑(其中某些场景,会有用到Net业务,和上面type是关联的) //IO业务:开始处理业务 strategy.dealIO(); //......... 省略N多逻辑 //释放资源 strategy.dispose(); }
- 结果
创建IO资源 处理IO逻辑 释放IO资源
适合的业务场景
这边举一些适合上述设计模式的业务场景,这些场景是真实存在的!
这些真实的业务,使用设计模式解耦和纯靠if else怼,完全是俩种体验!
代码如诗,这并不是一句玩笑话。
连环弹窗业务
业务描述
连环弹窗夺命call来袭。。。
- A弹窗弹出:有确定和取消按钮
- 确定按钮:B弹窗弹出(有查看详情和取消按钮)
- 查看详情按钮:C弹窗弹出(有同意和拒绝按钮)
- 同意按钮:D弹窗弹出(有查看和下一步按钮)
- 查看按钮:E弹窗弹出(只有下一步按钮)
- 下一步按钮:F弹窗弹出(结束)
- 下一步按钮:F弹窗弹出(结束)
- 查看按钮:E弹窗弹出(只有下一步按钮)
- 拒绝按钮:流程结束
- 同意按钮:D弹窗弹出(有查看和下一步按钮)
- 取消按钮:流程结束
- 查看详情按钮:C弹窗弹出(有同意和拒绝按钮)
- 取消按钮:流程结束
- 确定按钮:B弹窗弹出(有查看详情和取消按钮)
好家伙,套娃真是无所不在,真不是我们代码套娃,实在是业务套娃,手动滑稽.jpng
- 图示弹窗业务
直接开搞
看到这个业务,大家会去做呢?
- 有人可能会想,这么简单的业务还需要想吗?直接写啊!
- A:在确定回调里面,跳转B弹窗
- B:查看详情按钮跳转C弹窗
- 。。。
- 好一通套后,终于写完了
> 产品来了,加需求 > > B和C弹窗之间要加个预览G弹窗,点击B的查看详情按钮,跳转预览G弹窗,预览G弹窗只有一个确定按钮,跳转C弹窗
- 你心里可能要想了,这特么不是坑爹?
- 业务本来就超吉尔套,我B弹窗里面写的跳转代码要改,传参要改,而且还要加弹窗!
- 先要找产品撕逼,撕完毕后
- 然后继续在屎山上,小心翼翼的再拉了坨shit
- 这座克苏鲁山初成规模
> 产品又来了,第一稿需求不合理,需要调整需求 > > 交换C和D弹窗位置,D弹窗点击下一步的时候,需要加一个校验请求,通过后才能跳转到C弹窗
- 你眉头一皱,发现事情没有表面这么简单
- 由于初期图简单,几乎都写在一个文件里,眼花缭乱弹窗回调太多,而且弹窗样式也不一样
- 现在改整个流程,导致你整个人脑子嗡嗡响
- 心中怒气翻涌,找到产品说
- 回来,坐在椅子上,心里想:
- 老夫写的代码天衣无缝,这什么几把需求
- 可恶,这次测试,起码要给我多提十几个BUG
- 克苏鲁山开始狰狞
> 产品飘来,加改需求:如此,如此,,,这般,这般,,,
- 你....
> 产品:改下,,,然后,扔给你几十页的PRD
- 你看了看这改了几十版的克苏鲁山,这几十个弹窗逻辑居然都写在一个文件里,快一万行的代码。。。
- 心里不禁想:本帅比写的代码果然牛批,或许这就是艺术!艺术总是曲高和寡,难被人理解!而我的代码更牛批,连我自己都看不懂了!
- 心想:事了拂衣去,深藏功与名
重构
> 随着业务的逐渐复杂,最初的设计缺点会逐渐暴露;重构有缺陷的代码流程,变得势在必行,这会极大降低维护成本
如果心中对责任链模式有一些概念的话,会发现上面的业务,极其适合责任链模式!
对上面的业务进行分析,可以明确一些事
- 这个业务是一个链式的,有着较明确方向性,单向,从头到尾指向
- 业务拆分开,可以将一个弹窗作为颗粒度,一个弹窗作为节点
- 上级的业务节点可以对下级节点拦截(点击取消,拒绝按钮,不再进行后续业务)
重构上面的代码,只要明确思想和流程就行了
> 第一稿业务
- 业务流程
- 责任链
- 代码:简写
void main() { var intercepts = InterceptChainHandler<string>(); intercepts.add(AIntercept()); intercepts.add(BIntercept()); intercepts.add(CIntercept()); intercepts.add(DIntercept()); intercepts.add(EIntercept()); intercepts.add(FIntercept()); intercepts.intercept("测试拦截器"); }
> 第二稿业务
- 业务流程
- 责任链
- 代码:简写
void main() { var intercepts = InterceptChainHandler<string>(); intercepts.add(AIntercept()); intercepts.add(BIntercept()); intercepts.add(GIntercept()); intercepts.add(CIntercept()); intercepts.add(DIntercept()); intercepts.add(EIntercept()); intercepts.add(FIntercept()); intercepts.intercept("测试拦截器"); }
> 第三稿业务
- 业务流程
- 责任链
- 代码:简写
void main() { var intercepts = InterceptChainHandler<string>(); intercepts.add(AIntercept()); intercepts.add(BIntercept()); intercepts.add(GIntercept()); intercepts.add(DIntercept()); intercepts.add(CIntercept()); intercepts.add(EIntercept()); intercepts.add(FIntercept()); intercepts.intercept("测试拦截器"); }
> 总结
经过责任链模式重构后,业务节点被明确的区分开,整个流程从代码上看,都相当的清楚,维护将变的异常轻松;或许,此时能感受到一些,编程的乐趣了
花样弹窗业务
业务描述
这边来描述一个业务,这个业务场景真实存在某办公软件
- 进入APP首页,然后和后台建立一个长连接
- 后台某些工单处理后,会通知APP处理,此时app会弹出处理工单的弹窗(app顶部)
- 弹窗类型很多:工单处理弹窗,流程审批弹窗,邀请类型弹窗,查看工单详情弹窗,提交信息弹窗。。。
- 弹窗弹出类型,是根据后台给的Type进行判断:从而弹出不同类型弹窗、点击其按钮,跳转不同业务,传递不同参数。
分析
> 确定设计
这个业务,是一种渐变性的引导你搭建克苏鲁代码山
- 在前期开发的时候,一般只有俩三种类型弹窗,前期十分好做;根本不用考虑如何设计,抬手一行代码,反手一行代码,就能搞定
- 但是后来整个业务会渐渐的鬼畜,不同类型会慢慢加到几十种之多!!!
首先这个业务,使用责任链模式,肯定是不合适的,因为弹窗之间的耦合性很低,并没有什么明确的上下游关系
但是,这个业务使用策略模式非常的适合!
- type类型明确:不同类型弹出不同弹窗,按钮执行不同逻辑
- 抽象行为明确:一个按钮就是一种行为,不同行为的实现逻辑大相径庭
> 抽象行为
多样弹窗的行为抽象,对应其按钮就行了
确定、取消、同意、拒绝、查看详情、我知道了、提交
直接画图来表示吧
实现
来看下简要的代码实现,代码不重要,重要的是思想,这边简要的看下代码实现流程
- 抽象基类
/// 默认实现抛异常,可提醒未实现方法被误用 abstract class DialogAction { ///确定取消 void onConfirm() { throw 'DialogAction:not implement onConfirm()'; } ///取消 void onCancel() { throw 'DialogAction:not implement onCancel()'; } ///同意 void onAgree() { throw 'DialogAction:not implement onAgree()'; } ///拒绝 void onRefuse() { throw 'DialogAction:not implement onRefuse()'; } ///查看详情 void onDetail() { throw 'DialogAction:not implement onDetail()'; } ///我知道了 void onKnow() { throw 'DialogAction:not implement onKnow()'; } ///提交 void onSubmit() { throw 'DialogAction:not implement onSubmit()'; } }
- 实现逻辑类
class OneStrategy extends DialogAction { @override void onConfirm() { print("确定"); } @override void onCancel() { print("取消"); } } class TwoStrategy extends DialogAction{ @override void onAgree() { print("同意"); } @override void onRefuse() { print("拒绝"); } } //........省略其他实现
- 使用
void main() { //根据接口获取 var type = 1; DialogAction strategy; switch (type) { case 0: strategy = DefaultStrategy(); break; case 1: strategy = OneStrategy(); break; case 2: strategy = TwoStrategy(); break; case 3: strategy = ThreeStrategy(); break; case 4: strategy = FourStrategy(); break; case 5: strategy = FiveStrategy(); break; default: strategy = DefaultStrategy(); break; } //聚合弹窗按钮触发事件 BusinessDialog( //确定按钮 onConfirm: () { strategy.onConfirm(); }, //取消按钮 onCancel: () { strategy.onCancel(); }, //同意按钮 onAgree: () { strategy.onAgree(); }, //拒绝按钮 onRefuse: () { strategy.onRefuse(); }, //查看详情按钮 onDetail: () { strategy.onDetail(); }, //我知道了按钮 onKnow: () { strategy.onKnow(); }, //提交按钮 onSubmit: () { strategy.onSubmit(); }, ); }
- 图示
一个复杂业务场景的演变
我们看下,一个简单的提交业务流,怎么逐渐变的狰狞
我会逐渐给出一个合适的解决方案,如果大家有更好的想法,务必在评论区告诉鄙人
业务描述:我们的车子因不可抗原因坏了,要去维修厂修车,工作人员开始登记这个损坏车辆。。。
业务的演变
> 第一稿 > > 初始业务
登记一个维修车辆的流程,实际上还是满麻烦的
- 登记一个新车,需要将车辆详细信息登记清楚:车牌、车架、车型号、车辆类型、进出场时间、油量、里程。。。
- 还需要登记一下用户信息:姓名、手机号、是否隶属公司。。。
- 登记车损程度:车顶、车底、方向盘、玻璃、离合器、刹车。。。
- 车内物品:车座皮套、工具。。。
- 以及其他我没想到。。。
- 最后:提交所有登记好的信息
第一稿,业务流程十分清晰,细节复杂,但是做起来不难
> 第二稿(实际是多稿聚合):增加下述几个流程 > > 外部登记:外部登记了一个维修车辆部分信息(后台,微信小程序,H5等等),需要在app上完善信息,提交接口不同(必带车牌号) > > 快捷洗车:洗车业务极其常见,快捷生成对应信息,提交接口不同 > > 预约订单登记:预约好了车辆一部分一些信息,可快捷登记,提交接口不同(必带车牌号)
因为登记维修车辆流程,登记车辆信息流程极其细致繁琐,我们决定复用登记新车模块
- 因为此处逻辑大多涉及开头和结尾,中间登记车辆信息操作几乎未改动,复用想法是可行的
- 如果增加车辆登记项,新的三个流程必须也需要提交这些信息,复用势在必行
因为这一稿需求,业务也变得愈加复杂
> 第三稿 > > 现在要针对不同的车辆类型,做不同的处理;车类型分:个人车,集团车 > > 不同类型的登记,在提交的时候,需要校验不同的信息;校验不通过,需要提示用户,并且不能进行提交流程 > > 提交完,处理下通用也通用业务,然后跳转的某个页面
第三稿的描述不多,但是,也同样大大的增加了复杂度
- 尤其是不同类型校验过程还不同,还能中断后续提交流程
- 提交流程后,还需要跳转通用页面
开发探讨
第一稿
- 业务流程
- 开发
正常流程开发、、、
第二稿
- 业务流程
- 思考
对于第二稿的业务,可以好好的思考,可以怎么去开发?
开头和结尾需要单独写判断,去处理不同流程的业务,这至少要写俩个大的判断模块,接受数据的入口模块可能还要写判断
这样就非常适合策略模式去做了
开头根据执行的流程,选择相应的策略对象,后续将逻辑块替换抽象的策略方法就OK了,大致流程如下
第三稿
> 业务流程
> 探讨
-
第三稿的需求,实际上,已经比较复杂了
- 整个流程中掺杂着不同业务流程处理,不同流程逻辑又拥有阻断下游机制(绿色模块)
- 下游逻辑又会合流(结尾)的多种变换
-
在这一稿的需求
- 使用策略模式肯定是可以的
- 阻断那块(绿色模块)需要单独处理下:
抽象方法应该拥有返回值,外层根据返回值,判断是否进行后续流程
- 但!这!也太不优雅了!
-
思考上面业务一些特性
- 拦截下游机制
- 上游到下游、方向明确
- 随时可能插入新的业务流程。。。
可以用责任链模式!需要做一些小改动!这地方,我们需要将频繁变动的模块用责任链模式全都隔离出来
- 看下,使用责任链模式改造后流程图
> 浏览上述流程图可发现,本来是极度杂乱糅合的业务,可以被设计相对更加平行的结构
-
对于上述流程,可以进一步分析,并进一步简化:对整体业务分析,我们需要去关注其变或不变的部分
- 不变:整体业务变动很小的是,登记信息流程(主题逻辑这块),此处的相关变动是很小的,对所有流程也是共用的部分
- 变:可以发现,开头和结尾是变动更加频繁的部分,我们可以对此处逻辑进行整体的抽象
-
抽象多变的开头和结尾
- 所以我们抽象拦截类,可以做一些调整
abstract class InterceptChainTwice<t> { InterceptChainTwice? next; void onInit(T data) { next?.onInit(data); } void onSubmit(T data) { next?.onSubmit(data); } }
> 来看下简要的代码实现,代码不重要,主要看看实现流程和思想
- 抽象拦截器
abstract class InterceptChainTwice<t> { InterceptChainTwice? next; void onInit(T data) { next?.onInit(data); } void onSubmit(T data) { next?.onSubmit(data); } } class InterceptChainTwiceHandler<t> { InterceptChainTwice? _interceptFirst; void add(InterceptChainTwice interceptChain) { if (_interceptFirst == null) { _interceptFirst = interceptChain; return; } var node = _interceptFirst!; while (true) { if (node.next == null) { node.next = interceptChain; break; } node = node.next!; } } void onInit(T data) { _interceptFirst?.onInit(data); } void onSubmit(T data) { _interceptFirst?.onSubmit(data); } }
- 实现拦截器
/// 开头通用拦截器 class CommonIntercept extends InterceptChainTwice<string> { @override void onInit(String data) { //如果有车牌,请求接口,获取数据 //................. //填充页面 super.onInit(data); } } /// 登记新车拦截器 class RegisterNewIntercept extends InterceptChainTwice<string> { @override void onInit(String data) { //处理开头针对登记新车的单独逻辑 super.onInit(data); } @override void onSubmit(String data) { var isPass = false; //如果校验不过,拦截下游逻辑 if (!isPass) { return; } // ...... super.onSubmit(data); } } /// 省略其他实现
- 使用
void main() { var type = 0; var intercepts = InterceptChainTwiceHandler(); intercepts.add(CommonIntercept()); intercepts.add(CarTypeDealIntercept()); if (type == 0) { //登记新车 intercepts.add(CommonIntercept()); } else if (type == 1) { //外部登记 intercepts.add(OutRegisterIntercept()); } else if (type == 2) { //快捷洗车 intercepts.add(FastWashIntercept()); } else { //预约订单登记 intercepts.add(OrderRegisterIntercept()); } intercepts.add(TailIntercept()); //业务开始 intercepts.onInit("传入数据源"); //开始处理N多逻辑 //............................................................ //经历了N多逻辑 //提交按钮触发事件 SubmitBtn( //提交按钮 onSubmit: () { intercepts.onSubmit("传入提交数据"); }, ); }
总结
关于代码部分,关键的代码,我都写出来,用心看看,肯定能明白我写的意思
也不用找我要完整代码了,这些业务demo代码写完后,就删了
本栏目这个业务,实际上是非常常见的的一个业务,一个提交流程与很多其它的流程耦合,整个业务就会慢慢的变的鬼畜,充满各种判断,很容易让人陷入泥泞,或许,此时可以对已有业务进行思考,如何进行合理的优化
该业务的演变历程,和开发改造是本人的一次思路历程,如大家有更好的思路,还请不吝赐教。
通用拦截器
我结合OkHttp的思想和Dio的API,封装了俩个通用拦截器,这边贴下代码,如果哪里有什么不足,请及时告知本人
说明下:这是Dart版本的
抽象单方法
///一层通用拦截器,T的类型必须一致 abstract class InterceptSingle<t> { void intercept(T data, SingleHandler handler) => handler.next(data); } ///添加拦截器,触发拦截器方法入口 class InterceptSingleHandler<t> { _InterceptSingleHandler _handler = _InterceptSingleHandler( index: 0, intercepts: [], ); void add(InterceptSingle intercept) { //一种类型的拦截器只能添加一次 for (var item in _handler.intercepts) { if (item.runtimeType == intercept.runtimeType) { return; } } _handler.intercepts.add(intercept); } void delete(InterceptSingle intercept) { _handler.intercepts.remove(intercept); } void intercept(T data) { _handler.next(data); } } ///------------实现不同处理器 参照 dio api设计 和 OkHttp实现思想--------------- abstract class SingleHandler { next(dynamic data); } ///实现init处理器 class _InterceptSingleHandler extends SingleHandler { List<interceptsingle> intercepts; int index; _InterceptSingleHandler({ required this.index, required this.intercepts, }); @override next(dynamic data) { if (index >= intercepts.length) { return; } var intercept = intercepts[index]; var handler = _InterceptSingleHandler(index: index + 1, intercepts: intercepts); intercept.intercept(data, handler); } }
抽象双方法
///俩层通用拦截器,T的类型必须一致 abstract class InterceptTwice<t> { void onInit(T data, TwiceHandler handler) => handler.next(data); void onSubmit(T data, TwiceHandler handler) => handler.next(data); } ///添加拦截器,触发拦截器方法入口 class InterceptTwiceHandler<t> { _TwiceInitHandler _init = _TwiceInitHandler(index: 0, intercepts: []); _TwiceSubmitHandler _submit = _TwiceSubmitHandler(index: 0, intercepts: []); void add(InterceptTwice intercept) { //一种类型的拦截器只能添加一次 for (var item in _init.intercepts) { if (item.runtimeType == intercept.runtimeType) { return; } } _init.intercepts.add(intercept); _submit.intercepts.add(intercept); } void delete(InterceptTwice intercept) { _init.intercepts.remove(intercept); _submit.intercepts.remove(intercept); } void onInit(T data) { _init.next(data); } void onSubmit(T data) { _submit.next(data); } } ///------------实现不同处理器 参照 dio api设计 和 OkHttp实现思想--------------- abstract class TwiceHandler { next(dynamic data); } ///实现init处理器 class _TwiceInitHandler extends TwiceHandler { List<intercepttwice> intercepts; int index; _TwiceInitHandler({ required this.index, required this.intercepts, }); @override next(dynamic data) { if (index >= intercepts.length) { return; } var intercept = intercepts[index]; var handler = _TwiceInitHandler(index: index + 1, intercepts: intercepts); intercept.onInit(data, handler); } } ///实现submit处理器 class _TwiceSubmitHandler extends TwiceHandler { List<intercepttwice> intercepts; int index; _TwiceSubmitHandler({ required this.index, required this.intercepts, }); @override next(dynamic data) { if (index >= intercepts.length) { return; } var intercept = intercepts[index]; var handler = _TwiceSubmitHandler(index: index + 1, intercepts: intercepts); intercept.onSubmit(data, handler); } }
最后
第一次,写这种结合业务的文章
如有收获,还请点个赞,让我感受到你读有所获~~~~
感谢阅读,下次再会~~
</intercepttwice></intercepttwice></t></t></interceptsingle></t></t></string></string></t></t></t></string></string></string></string></string></string></t></t></t>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
简单、快捷、低成本的超写实虚拟人平台来了……
本篇文章探讨数字虚拟人大爆发前的必要条件。 虽然数字虚拟人越来越受关注,如果要迎来大爆发,虚拟偶像甚至能部分替代频频爆雷的真人偶像地位,但是还需要新平台的出现。 因为,数字虚拟人从表面上看是一种娱乐,但本质上,数字虚拟人是新的网络应用服务,像社交、电商、外卖一样,需要强大专门的、专为虚拟人/虚拟偶像服务的平台。 新平台首先要做到:将打造超写实数字虚拟人的高门槛、高投入真正降下来,变成普及化服务。解决现在如果想开发超写实虚拟人,花费很贵、难以实时直播、也无法快速批量生产内容等诸多问题。 比如一些超写实的数字虚拟人,目前也只是能做到发几张图片的程度,做到视频的很少,既少也不足。 某高知名度的新消费品牌,推出了广受好评的品牌虚拟人形象,但公布的也只是一张图片,据了解还在非常初期的阶段,并没有真正进入到动态内容开发。 目前市场上的虚拟偶像天团,只是几家娱乐大公司的烧钱游戏,一个4人虚拟偶像天团,可以在短短几年内烧掉近亿元的资金,而且还远没有达到高仿真的程度。 这些都亟待新平台的出现来解决,而众多巨头都在进行布局。 我最近发现,新发布的百度智能云“数字明星运营平台与灿星计划”,有可能就...
- 下一篇
Redis 实战篇:巧用Bitmap 实现亿级海量数据统计
在移动应用的业务场景中,我们需要保存这样的信息:一个 key 关联了一个数据集合。 常见的场景如下: 给一个 userId ,判断用户登陆状态; 显示用户某个月的签到次数和首次签到时间; 两亿用户最近 7 天的签到情况,统计 7 天内连续签到的用户总数; 通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。 所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。 如何选择合适的数据集合,我们首先要了解常用的统计模式,并运用合理的数据了性来解决实际问题。 四种统计类型: 二值状态统计; 聚合统计; 排序统计; 基数统计。 本文将由二值状态统计类型作为实战篇系列的开篇,文中将用到 String、Set、Zset、List、hash 以外的拓展数据类型 Bitmap 来实现。 文章涉及到的指令可以通过在线 Redis 客户端运行调试,地址:https://try.redis.io/,超方便的说。 寄语 多分享多付出,前期多给别人创造价值并且不计回报,从长远来看,这些付出都会成倍的回报你。 特别是刚开始跟别人合作...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8
- Hadoop3单机部署,实现最简伪集群
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2全家桶,快速入门学习开发网站教程