[关闭]
@Awille 2022-07-02T09:19:30.000000Z 字数 5274 阅读 127

Design - Hm

架构 设计模式


前言

程序架构、设计模式其实没有优劣之分, 各种架构其实都是在为业务场景服务的,一个好的设计,一定是贴合业务的,是适应业务的,并且能高效支撑业务的。

Procedure Oriented & Object Oriented

1、Procedure Oriented Programing(面向过程编程)

1.1、定义

Procedural programming is a programming paradigm, derived from imperative programming,1 based on the concept of the procedure call. Procedures (a type of routine or subroutine) simply contain a series of computational steps to be carried out.

过程调用,分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

1.2、目标

2、Object Oriented Programing(面向对象对象)

2.1、Definition

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).

以对象为基础,对象包含属性和方法,一切皆是对象。

2.2、特性

  1. 封装
    信息隐藏,将抽象数据类型和基于数据的操作封装在一起,尽可能的隐藏内部细节,只保留一些对外接口使之与外部发生联系。
  2. 继承
    使用已存在的类作为基础建立新类,增加新的数据过着新的功能。
  3. 多态
    方法可重载,子类的实例对象可以赋值给父类的引用变量,子类可以重写父类方法。 一个引用变量到底会指向那个类的实例对象,在程序需的运行期间才决定。

2.3、目标

重用性、灵活性、扩展性

3、PO VS OO

我们用一个例子来说明一下两种思想的差异。

3.1、实现一个产品A - 第一阶段

PO风格

public class ProductA {
    public static void main() {
        stepA();
        stepB();
        stepC();
        stepD();
    }
    private void stepA() {
        ...
    }
    private void stepB() {
        ...
    }
    private void stepC() {
        ...
    }
    private void stepD() {
        ...
    }
}

OO风格:

public class ProductA {
    public static void main() {
        new A().step();
        new B().step();
        new C().step();
        new D().step();
    }
}
public class A {
    public void step() {
        ...
    }
}
public class B {
    public void step() {
        ...
    }
}
public class C {
    public void step() {
        ...
    }
}
public class D {
    public void step() {
        ...
    }
}

总体看来,PO的方式确实代码更简洁、清晰。

3.2、实现一个产品B - 第二阶段

新增一个产品B, 他的功能跟产品A一样,就是在stepC里面还要新增一些操作。

PO风格:
第一个选择:直接把productA的代码复制一遍,建一个ProductB,然后把stepC的代码改一改。

第二个选择:尽量少增加多余的代码,沿用productA。

public class ProductA {
    public static void main() {
        stepA();
        stepB();
        if (A) {
            stepC();
        } else {
            stepC'();
        }
        stepD();
    }
}

OO风格:

public class C' extend C {
    @Override
    public void step() {
        ...
    }
}

3.3、实现相关产品C、D、E、F ... - 第三阶段

两方继续沿用原有方式扩展产品功能。

这里可以看到PO这边,如果是第一种方式,会出现很多的代码复制,同样的逻辑随处可见。假如这时候有个需求,要你在每个产品的stepA上面打个点,你还会想干吗?

如果是第二种方式,同一个类会疯狂扩张。现在你改动一个逻辑,你要浏览一堆无关的代码逻辑,改动一处,反复确认会不会对其他部分产生影响。

3.4、其他例子

话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽。……’。众文武齐呼:‘丞相好诗!’于是一臣子速命印刷工匠刻版印刷,以便流传天下。

“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当歌’较好!’,于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”

“样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽太过直接,应改问语才够意境,因此应改为‘对酒当歌,人生几何?……’当臣转告工匠之时,工匠晕倒……!”

“小菜你说,这里面问题出在哪里?”大鸟问道。小菜说:“是不是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整个刻板全部重新刻。”大鸟:“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白做。岂不妙哉。”

活字印刷:复用、灵活、易扩展

3.4、总结

PO:
PO是以流程编排的形式完成功能,在系统简单时:

但是随着系统逐渐复杂,其对复杂的逻辑逐渐表现的水土不服,会出现:

OO
面向对象基于封装、继承多态的特性,每一个类都只做一件事,各司其职,最终合作共赢!

在程序简单时,确实会略显冗余,但在大型复杂系统之下,其复用性、灵活性、扩展性确实很高,能在一定程度上提高我们的效率。

演化到大型系统开发,两种设计模式相辅相成、整体面向对象、局部面向过程。

4、面向接口编程

对于系统设计人员来说,里面具体的行为是不关心,更多的是行为抽象,协议抽象。 各个模块与各个模块中间,通过约定协议进行解耦,互相职期间只关心协议层面数据流通,隐藏各自协议细节。

以阿里巴巴盒马ISV生态POS作为背景

4.1、背景

盒马是零售业中第一个做自助收银POS,其自助收银机器,在盒马自营店以及大润发、三江、新华都等收购子公司全面铺开,数字化收银、节省人力,并沉淀了一套自助收银的产品链路。作为新零售开拓者,希望能把这一套自助收银体系拓展出去。

但是作为外部商家,后端系统一般不愿意交由我们进行开发,那么基于以往自助收银的经验需要沉淀一套协议,即满足收银的场景需求,又要满足各个商家部分定制化需求。

4.2、整体架构设计

cd85ec8292ccdb22098e55384d0545d.jpg-278.5kB

以定义的的DataProtocol为媒介,拍平各个商家之间的底层差异。APP运行时通过配置化路由 定位到不同商家的底层实现,做到支持新商家入驻,端上只配置,0开发。

5、面向切面编程 Aspect Oriented Program (AOP)

刚刚上面提到了,通过定义协议层的方法,我们拍平了不同商家之间的数据化差异,但是有一些流程差异怎么处理。 这种差异不止发生在不同商家之间,也发生在不同的场景之间。 比如结算场景,正常流程为 购物车页->结算页, 某些场景可能出现购物车页->优惠确认页->结算页, 或者餐饮场景下 购物车页->手机号录入页-> 餐桌号录入页->结算页。 这种流程化的差异怎么产出一套平台的解决方案。

在探讨上面的流程之前,我们先认识一下Spring里面一个很核心的编程思想AOP。

5.1、背景

我们在开发一些业务功能的时候,总是会在里面穿插一些跟业务吴福安的代码,比如性能统计、日志、事务管理等。 而且即使一行代码的时候,我也需要把整个模块依赖进来。

5.2、目标

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

简单来说,我希望在主流程的代码中 能隔离这种我不关心的流程与依赖。

5.3、基本概念

advice的类型,也可以理解为advice想要切入的节点位置,可分为下面几类:

try{
    try{
        //@Before
        method.invoke(..);
    }finally{
        //@After
    }
    //@AfterReturning
}catch(){
    //@AfterThrowing
}

还有一种是@Around 比较高级,将原方法的代理调用权让给你,你自行安排调用时机。
一个例子:

@Aspect
public class MyAspectLog {
    /**
     * 方法执行完后执行的方法
     */
    @After(value="execution(* cn.xh.dao.UserDao.addUser(..))")
    public void log(){
        System.out.println("记录日志");
    }
}

5.4、总结

AOP的思想在一定程度上是改变了我们的编程习惯的,在其思想下的代码工程中,隔离工作可以说是做的特别优秀的。

image.png-45.5kB

image.png-32.8kB

6、Alibaba TMF交易框架 & POS 实践

6.1、简介

上面讲到了一个流程化差异的业务场景,在经过AOP的熏陶后,我们是不是可以借鉴上面的思想寻找破局。

假设POS现在作为平台,我只想关心我的事情,我的任务就是正常流转整个收银流程。

我的收银流程为:
主链路功能:

image.png-12.4kB

UI功能:

image.png-4.5kB

以上的流程我们我们称为Ability, 我们会根据业余划分出不同的域,每个域下有不同的ability, 每个ablity抽象的节点称为扩展点。

我只抽象流程,不关心数据,上层的APP直接调用每个流程,具体每个节点是怎样的实现,我不想关心,也不需要关心。 对于UI展示,我只需要从有关UI的ability上获取UI的数据,我以平台定义的UI规则进行渲染。

6.2、TMF下的开发模式

上述我们提到我们定义了不同的域,每个域下定义了ability, 每个ability定义了不同的切点。 一个产品功能,一般为一个业务需求, 每个业务需求思考自己需要实现切点,可能是一个ability下的一个切点,也可能是一个ablity下的多个切点,又可能是多个abliity下多个切点的组合, 这里可以把我们的业务需求理解成切面。 这个业务需求的实现自己启一个模块,把设计该业务的数据管理都封装到该模块下。

6.3、TMF织入过程

业务app依赖需要的产品需求模块,最终在编译过程中根据注解生成一份针对各个切点不同实现的配置文件,可以定义每个切点实现的优先级,每个切点是否运行的统一开关。

最终在app运行时根据配置文件加载各个切点的实现。

image.png-23.9kB

6.4、总结

优点:
1、业务与业务直接完全隔离,改动影响面可评估,测试工作量小
2、业务逻辑一键上下线
3、开放性强,可让外部团队开发特定模块而不需关心整体平台逻辑
4、业务可视化,所有扩展点设计业务可见
5、复用性强,同样业务服用到不同端,基本没有迁移成本,一个依赖解决所有问题
6、结构清晰

缺点:
1、开发上,有时候为支持一个很小的功能,可能都要新建一个产品模块,某种情况下有杀鸡用牛刀的嫌疑
2、开发上,开发新产品时,重新生成所有扩展点配置耗时长
3、开发上,由于扩展点运行的优先级问题,在业务与业务之间有一定依赖关系的背景下,可能发生逻辑错误引发故障
4、该开发模式,需要平台具有一定的稳定性,需要业务积累到一定程度时,相对稳定时使用,有一定的权威性,否则可能抽象的流程节点并不通用,做需求经常改动。
5、学习成本,新人来到团队里面,甚至代码都找不到,整个运行模式刚开始在不了解这套思想的情况下云里雾里,而且整个开发的思维都要转化成平台思维,当遇到当前的节点定义不满足这个需求开发
6、由于启动时存在冲配置里加载各个 advice的织入过程,第一次调用服务可能会加载慢(B端通过启动预加载解决)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注