原来Unix设计实现正是这种思想应用的典范!

2022-10-28 14:22:22 浏览数 (1)

概述

今天跟大家分享一个程序员的底层思维-分治思维。

分治(Divid and Conquer)思想,是一种古老的、非常有效的思想。传说,罗马帝国的凯撒大帝就是采用这一思想策略征服了高卢人。

分治,从字面理解,就是分而治之;然而,分而治之并不是分治的全部。

实际上,除了我们常规理解的”分治“ 即”分解 治理“,”分治并“ 也是一种分治。分治并的过程是”分解 治理 合并“,合并的过程往往容易被忽视,但是实际应用中却很常见。比如,分治算法都有一个先分后合的过程;

再比如,分布式系统架构比如Hadoop,Map是分的过程,而Reduce是合的过程。

大部分问题可以通过分治解决,比如设计模式(管道模式)中的分治、团队拆分、分布式服务拆分等,然而另一些问题需要分治并,即分治算法和Hadoop来解决。

今天跟大家分享的管道模式其实就是分治思想的体现。

什么是管道模式

图一:家庭的自来水前置VS后置过滤器,经过层层过滤,能帮我们过滤源头自来水的铜锈、颗粒杂质等;让我们喝到没有杂质的纯净水。

其实上述的这种过滤器层层处理的方式就是典型的分治思想。

在计算机世界中,也有很多这种思想的应用。

最有名的当属UNIX或者Linux中的管道了。

大家先看一个例子:

这个命令行中间起连接作用的竖杠符号”|“就是所谓的管道,类似自来水的管道,起到了阀门和连接的作用。

同样在管道设计模式中,主要有以下两个角色。

(1)阀门(Valve):用于处理数据的节点。

(2)管道(pipeline):用于组织各个阀门,串接各个阀门完成工作。

Valve和pipeline源自一些开源中间件比如Tomcat等术语。实现思路上使用一个单向链表数据结构作为管道的实现。

如下图所示。在一个pipeline中,由Head Valve接受一个输入,再经由一个或多个Valve进行处理,最后由Tail Valve输出结果。

有什么用?解决了什么问题

我举几个现实生活中的场景举例说明一下:

1.微服务拆分

看了图一的管道示意图,大家有没有发现跟我们平时在开发的分布式应用很像,团队内部拆分了很多应用,调用的时候,形成了上下游调用关系,比如订单服务->库存服务等。

这个就是分治思想的体现。

2. 团队组织架构的拆分

一个技术团队,需要区分前端、后端、测试、UI、运维等吧。

...

代码实现

接下来我们用Java代码来实现一个管道模式。

我们先定义一个阀门接口:

代码语言:javascript复制
/**
 * 阀门接口
 */
public interface Valve {

    Valve getNext();

    void setNext(Valve v);

    void invoke(String s);
}

有一个setNext方法,每一个阀门处理完后需要流转进入下一个阀门。invoke方法是每一个阀门的处理入口。

我们再定义一个阀门抽象类

代码语言:javascript复制
/**
 * 阀门抽象类
 */
public abstract class AbstractValve implements Valve{

    private Valve next;


    @Override
    public Valve getNext() {
        return next;
    }

    @Override
    public void setNext(Valve valve) {
        next=valve;
    }

    @Override
    public abstract void invoke(String s) ;
}

我们抽象一个公共的阀门抽象类。 此类实现了阀门接口。对setNext和getNext进行了简单封装。 对invoke方法进行abstract修饰,目的是各个子类自己去实现该方法。 比如具体的阀门:前置过滤器 Or 后置管理器等等。按照自己的特性过滤自己的行为。妥妥的模板设计模式。

接下来是具体的抽象子类,也就是各个真正的阀门实现类。

这些个阀门类,各自处理各自领域的事情,比如阀门1处理的逻辑是:替换“11”字符串成“first”内容。

阀门2做的事情类似...

它们处理成功后会调用下一个“阀门”。

阀门1代码:

代码语言:javascript复制
public class FirstValve extends AbstractValve{
    @Override
    public void invoke(String s) {
        s=s.replace("11","first");
        System.out.println("after first valve handled :s=" s);
        getNext().invoke(s);
    }
}

阀门2代码:

代码语言:javascript复制
public class SecondVavle extends AbstractValve{
    @Override
    public void invoke(String s) {
        s=s.replace("22","second");
        System.out.println("after second valve handled :s=" s);
        getNext().invoke(s);
    }
}

尾阀门代码:

代码语言:javascript复制
public class TailValve extends AbstractValve{
    @Override
    public void invoke(String s) {
        s=s.replace("33","third");
        System.out.println("after tail valve handled :s=" s);
    }
}

然后我们定义一个管道接口

代码语言:javascript复制
/**
 * 管道接口
 */
public interface Pipeline {

    Valve getHead();

    Valve getTail();

    void setTail(Valve v);

    void addValve(Valve v);
}

此接口起到了串联各个阀门的作用。内部包含头部、中间、尾部阀门。

最后定义一个管道的实现类"StandardPipeline"

代码语言:javascript复制
public class StandardPipeline implements Pipeline {
    protected Valve head;
    protected Valve tail;
    @Override
    public Valve getHead() {
        return head;
    }
    @Override
    public Valve getTail() {
        return tail;
    }
    @Override
    public void setTail(Valve v) {
        tail = v;
    }
    @Override
    public void addValve(Valve v) {
        if (head == null) {
            head=v;
            v.setNext(tail);
        }else {
            Valve current=head;
            while (current!=null){
                if(current.getNext()==tail){
                    current.setNext(v);
                    v.setNext(tail);
                    break;
                }
                current=current.getNext();
            }
        }
    }
}

addValve方法用于固定“首阀门”,然后在“首阀门”存在的情况下源源不断的有序的插入”阀门“直到"Tail阀门"节点。

本地Demo测试一下 我们写一个本地Demo来跑下,看下效果

代码语言:javascript复制
public class ClientDemo {

    public static void main(String[] args) {
        String s="11,22,33";
        System.out.println("Input :" s);
        StandardPipeline pipeline=new StandardPipeline();
        TailValve tailValve=new TailValve();
        FirstValve firstValve=new FirstValve();
        SecondVavle secondVavle=new SecondVavle();
        pipeline.setTail(tailValve);
        pipeline.addValve(firstValve);
        pipeline.addValve(secondVavle);
        pipeline.getHead().invoke(s);
    }
}

输出:

代码语言:javascript复制
Input :11,22,33
after first valve handled :s=first,22,33
after second valve handled :s=first,second,33
after tail valve handled :s=first,second,third

结尾

通过以上步骤,我们用代码简单实现一个“管道模式”。 这里简单说下工作流:我们国内很多公司包括大厂都比较热衷所谓的工作流引擎,然而本质上,很多他们本质需求纯粹只是对业务逻辑的组件化“分治”。并无真正的可视化、流程编排的需求。 所以如果纯粹只是业务逻辑上的分治,觉得没必要引入太重的工作流引擎,凭空增加了不必要的复杂度。 简单的Pipeline是解决此类问题的选择之一。之后我会跟上另一篇姊妹篇文章:《分治思维之责任链模式》也是这个思想的体现,请大家期待...

0 人点赞