优秀的编程知识分享平台

网站首页 > 技术文章 正文

年终奖10w的同事,写的代码那叫一个优雅!

nanyue 2024-12-18 16:02:33 技术文章 7 ℃

Robert Martin曾说过"在代码阅读中说脏话的频率是衡量代码质量额唯一标准"。同时,代码的写法应当使别人理解它所需的时间最小化,也就是说我们写的代码是给人看的而不是给机器看的。那么,如何编写优雅代码呢?可以从思想层面和具体技巧层面来优化代码,思想层面指的是遵循面向对象设计原则,本期介绍的是具体技巧。

##1. 代码总是越短越好吗?

assert((!(bucket = findBucket(key))) || !bucket.isOccupied());
复制代码

上面这行代码虽然比较短,但是难以阅读。为了更好地阅读,我们做如下修改:

bucket = findBucket(key);
if(bucket != null){
  assert(!bucket.isOccupied());
}
复制代码

减少代码行数是一个好目标,但是让阅读代码的事件最小化是个更好的目标

2. 给重要语句添加注释

// Fast version of "hash = (65599*hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c
复制代码

上面这行代码如果没有添加注释,我们根本不知道是什么意思,但是有了这行注释,我们就知道通过移位操作来提升性能。

## 3. tmp的使用 tmp是我们经常用的,譬如说两个变量置换,都已变成约定俗成了。

tmp = right;
right = left;
left = tmp;
复制代码
String tmp = user.getName();
tmp += " " + user.getPhoneNumber();
tmp += " " + user.getEmail();
template.set("user_info",tmp);
复制代码

4.i,j,k,iter,it:只用做索引或者循环迭代

i,j,k,iter,it被用做索引或者循环迭代已成为业界规范了(i是index的缩写),例如:

for(int i=0;i<100;i++){
  for(int j=0;j<100;j++){
    ......
  }
}

Iterator<String> iter = list.iterator();
while(iter.hasNext()){
  ......
}
复制代码

如果我们在其他地方使用i,j,k,那么就会增加阅读者的时间。

5. 附带重要属性

我们把命名当做一种注释的方式,让它承载更多的信息!

6. 名字需要多长?

  • 在小的作用域中使用简短的名字
  • 在作用域大的可以使用长名字
if(debug){
  Map<String,Integer> m = new HashMap<>();
  lookUpNamesNumbers(m);
  print(m);
}
复制代码

7. 不要使用容易误解的名字

results = Database.all_objects.filter("year<=2011")
复制代码

上面这行代码结果现在包含哪些信息?filter是把年份小于等于2011年的数据过滤掉?还是保留?

8. 推荐用min和max来表示极限

MAX_ITEMS_IN_CART = 10;

if (shoppingCart.numOfItems()> MAX_ITEMS_IN_CART){
    error("Too many items in cart");
}
复制代码

9. 推荐用begin和end来表示包含/排除范围

begin表示包含,end表示排除,在Java中典型的例子就是String.substring()


String s = "Hello world";

s.substring(2,5);-> "llo"

复制代码

10.与使用者的期望相匹配

一般来说,getter方法就是获取一个字段的值,用户期待的是轻量级的方法,如果你要是在其中做了太多的计算,就应该考虑改名。


public double getMeanPrice(){

//遍历所有条目计算总价,然后计算平均价格

}

public double computeMeanPrice(){

//遍历所有条目计算总价,然后计算平均价格

}

复制代码

11.不要为那些从代码本身就能快速推断的事实写注释


public  class Account {  

    // Constructor

   public  Account(){

   }

   // Set the profit member to a new value    

   void setProfit(double profit){

         …….

   }   

    // Return the profit from this Account    

    double getProfit(){

        ….

    }

};

复制代码

12. 不要给不好的名字加注释--应该把名字改好

// Releases the handle for this key.This doesn't modify the actual registry.
void deleteRegistry(RegistryKey key)
复制代码

乍一看我们会误认为这是一个删除注册表的函数,可是注释里澄清它不就改动真正的注册表。因此,我们可以用一个更加自我说明的名字,例如:

void releaseRegistryHandle(registryKey key);
复制代码

13.为代码中的瑕疵写注释

// TODO:采用更快算法或者当代码没有完成时 // TODO(dustin):处理除JPEG以外的图像格式

14.为常量写注释

// users thought 0.72 gave the best size/quality tradeoff
image_quality = 0.72;

// as long as it's >= 2*num_processors,that's good enough
NUM_THREADS = 8;

// impose a reasonable limit - no human can read that much anywhere
const int MAX_RSS_SUBSCRIPTIONS = 1000;
复制代码

15. 站在读者的角度写注释

struct Recoder {
    vector<float> data;
    ......
    void clear(){
        // 每个人读到这里都会问,为啥不直接调用data.clear()
        vector<float>().swap(data);
    }
}
复制代码

如果有一个好的注释可以解答读者的疑问,将上述进行如下修改:强制Vector真正地把内存归还给内存分配器,详情请查阅STL swap trick。

16. 公布可能的陷阱

void sendMail(String to,String subject,String body);
复制代码

这个函数由于需要调用外部服务器发送邮件,可能会很耗时,有可能导致使用者的线程挂起。需要将这段描述放到注释中。

17. 条件语句中参数的顺序

一般原则:将变量放在左边,常量放在右边。更宽泛地说,将比较稳定的变量放在右边,变化较大的放在左边。如 if ( length >= 10) 而不是 if ( 10 <= length)。但是,在非“大小”比较的情况下,上面的原则似乎不起作用,例如验证一个请求参数是否为某个特定值:if ( request.getParameterValue("name")).equals("Brandon")),此时将常量"Brandon"可以避免出现空指针的情况(上行的参数没有name或者值为空)。

18. if/else语句块的顺序

if/else书写规范:首先处理正逻辑而不是负逻辑,例如 if(ok),而不是if(!ok);其次处理掉简单的情况,这有利于让if和else处理代码在同一个屏幕内可见。

19. 通过提早返回减少嵌套

使用提前返回的机制,可以把函数的嵌套层级变浅。举个栗子,没有使用提前返回的代码:

static bool checkUserAuthority()
        {
            bool a, b, c, d, e;

            if (a)
            {
                if (b)
                {
                    if (c)
                    {
                        if (d)
                        {
                            if (e)
                            {
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }
复制代码

使用了提前返回的代码:

static bool checkUserAuthority()
        {
            bool a, b, c, d, e;

            if (!a)
                return false;

            if (!b)
                return false;

            if (!c)
                return false;

            if (!d)
                return false;

            if (!e)
                return false;

            return true;
       
        }
复制代码

##20. 通过 "总结变量" 增加可读性

if(request.user.id == document.owner_id){
    // user can edit this document ...
}

if(request.user.id != document.owner_id){
    // document is read-only...
}
复制代码

通过观察,我们提取一个变量final boolean user_owns_document=(request.user.id == document.owner_id),接着代码就可以修改成:

if(user_owns_document){
 // user can edit this document ...
}
if(!user_owns_document){
    // document is read-only...
}

复制代码

21. 减少控制流变量

在while、for等循环语句中,我们通常使用自定义的bool变量,来控制流转。

boolean done = false;
while(/* condition */ && !done){
    ...
    if(...){
        done = true;
        continue;
    }
}
复制代码

以我们的经验,"控制流变量" 可以通过优化程序结构、逻辑来消除。

while(/* condition */){
    ...
    if(...){
        break;
    }
}
复制代码

22. 缩小变量的作用域

void foo(){
    int i = 7;

    if(someCondition){
        // i is used only within this block
    }

}

void foo(){
    if(someCondition){
        int i = 7;
        // i is used only within this block
    }
}
复制代码

23. 不要为了共享而把变量设置为类的字段

public class LargeClass{
    String s;
    void method1(){
        s = ...
        method2();
    }
    void method2(){
        //使用s
    }
}
复制代码

通过参数传递来实现数据共享

public class LargeClass{
    void method1(){
        String s = ...
        method2(s);
    }
    void method2(String s){
        //使用s
    }
}
复制代码

24. 不要把所有变量都定义在开头

把所有变量定义在开头是C语言的风格,面向对象语言习惯将变量定义在离它开始使用的地方。

public void foo(){
  boolean debug = false;
  String[] pvs;
  String pn;
  String pv;
  ...
}
复制代码

除了上述建议之外,我们还可以参考阿里Java规范,关注微信号:"木可大大",发送"阿里Java规范"即可获得相关资料。


作者:木可大大
链接:https://juejin.cn/post/6844903597755334664
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


大家如果阅读过一些开源框架的源码,可能会发现其中数不尽的抽象类,设计模式拈手而来,在功能框架中,可以使用设计模式随心所欲的解耦;在实际的复杂业务中,当然也可以应用合适的设计模式。

这篇文章,我会结合较为常见的实际业务场景,探讨如何使用合适的设计模式将业务解耦

  • 此处的应用绝不是生搬硬套,是我经过深思熟虑,并将较为复杂的业务进行全面重构后,得出的一套行之有效的思路历程
  • 任何一个设计模式都是一个伟大的经验及其思想总结,千人千面,如果对文章中内容,有不同的意见,希望你能在评论中提出,我们共同探讨,共同进步

本文章是一篇弱代码类型文章,我会画大量的图片向大家展示,引用设计模式后,会对原有的业务流程,产生什么样的影响。

前置知识

这里,需要了解下基础知识,什么是责任链模式和策略模式

责任链模式,在很多开源框架中都是有所应用,你如果听到啥啥拦截器,基本就是责任链模式,责任链模式的思想很简单,但是有很多种实现方式

  • 最简单的链表实现就和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("处理Net逻辑");
  }

  @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弹窗弹出(结束) 拒绝按钮:流程结束 取消按钮:流程结束 取消按钮:流程结束

好家伙,套娃真是无所不在,真不是我们代码套娃,实在是业务套娃,手动滑稽.png

  • 图示弹窗业务

直接开搞

看到这个业务,大家会去怎么做呢?

  • 有人可能会想,这么简单的业务还需要想吗?直接写啊! A:在确定回调里面,跳转B弹窗 B:查看详情按钮跳转C弹窗 。。。
  • 好一通套后,终于写完了

产品来了,加需求

B和C弹窗之间要加个预览G弹窗,点击B的查看详情按钮,跳转预览G弹窗;预览G弹窗只有一个确定按钮,点击后跳转C弹窗

  • 你心里可能要想了,这特么不是坑爹? 业务本来就超吉尔套,我B弹窗里面写的跳转代码要改,传参要改,而且还要加弹窗!
  • 先要去找产品撕比,撕完后 然后继续在屎山上,小心翼翼的再拉了坨shit 这座克苏鲁山初成规模

产品又来了,第一稿需求不合理,需要调整需求

交换C和D弹窗位置,逻辑调整:D弹窗点击下一步的时候,需要加一个校验请求,通过后跳转到C弹窗,点击查看按钮跳转弹窗F

  • 你眉头一皱,发现事情没有表面这么简单 由于初期图简单,几乎都写在一个文件里,眼花缭乱弹窗回调太多,而且弹窗样式也不一样 现在改整个流程,导致你整个人脑子嗡嗡响
  • 心中怒气翻涌,找到产品说
  • 回来,坐在椅子上,心里想: 老夫写的代码天衣无缝,这什么恶心的需求 可恶,这次测试,起码要给我多提十几个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明确:不同类型弹出不同弹窗,按钮执行不同逻辑
  • 抽象行为明确:一个按钮就是一种行为,不同行为的实现逻辑大相径庭

抽象行为

多样弹窗的行为抽象,对应其按钮就行了

确定、取消、同意、拒绝、查看详情、我知道了、提交

直接画图来表示吧

实现

来看下简要的代码实现,代码不重要,重要的是思想,这边简要的看下代码实现流程

  • 抽象基类
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("测试拦截器");
}
复制代码
  • 实现逻辑类
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;
  }

  //聚合弹窗按钮触发事件(不同弹窗的确定按钮,皆可聚合为一个onConfirm事件,其它同理)
  BusinessDialog(
    //通过传入的type,显示对应类型的弹窗
    type: type,
    //确定按钮
    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(RegisterNewCarIntercept());
  } 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(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 {
  /// span: 设置该参数,可控跨越多级节点
  /// 默认0,则不跨越节点(遍历所有节点)
  next(dynamic data, {int span = 0});
}

///实现init处理器
class _InterceptSingleHandler extends SingleHandler {
  List<InterceptSingle> intercepts;

  int index;

  _InterceptSingleHandler({
    this.index = 0,
    required this.intercepts,
  });

  @override
  next(dynamic data, {int span = 0}) {
    if ((index + span) >= intercepts.length) return;

    var intercept = intercepts[index + span];
    var handler = _InterceptSingleHandler(
      index: index + (span + 1),
      intercepts: intercepts,
    );

    intercept.intercept(data, handler);
  }
}
复制代码

Tags:

最近发表
标签列表