浅谈抽象设计的意义

        我们通常都知道程序设计要依赖抽象,而非依赖具体,包括源码依赖的依赖倒置原则等。因此我以及很多向我一样码龄不长的朋友可能有滥用抽象的嫌疑。随处可见的interface以及abstract method(以java语言来说),而自己却未必知道这些接口类/抽象类的利弊以及使用是否恰当。

        依赖抽象(以过程抽象来说)的真实含义是:函数签名要具有稳定性!因为过程抽象本身就是一种“微协议”,即约定我给你传入什么,你给我返回什么,并且力求幂等性(无副作用)。函数签名体现的是业务领域设计中的业务概念、逻辑上的约定,抽象绝不是在语言语法层面的狭义呈现;换言之,如果你在OOD阶段的设计没有抽象到点儿上,即使你用了interface等接口类,不管你怎么去进行拆分替换实现、使用依赖倒置等手段,大厦也终将倾倒。

      我们知道,OOP里去贯彻SOLID五大原则的最重要法宝就是继承和多态,那么结合上述观点也就是说,多态只是手段问题,而并非程序设计的本质!反之也就是说,能用OO手段去是实现的,在过程抽象单元——函数级别是都能实现的。想象一下在OOP以前,C甚至上古时代Lisp语言如何编程实现同样的需求,比如要实现策略模式。策略模式是23中常用设计模式,尤其是结构和行为模式的基石。其基本原理就是声明一个统一接口,不同的算法封装成不同的实现类来实现这个统一的接口。底层原理就是利用父类指针/引用来指向子类实现,重写父类声明的抽象函数。无论是C++的虚基函数,还是java的抽象方法以及python等语言,在运行时通过虚函数表来寻址动态绑定实现,大抵都是这么做的。这里重点想说,既然原理都一样,不要拘泥和纠结于语言层面的语法糖,而要抓住业务领域问题的本质去抽象、设计程序。

        不信吗?比如,我通篇不使用interface以及abstract method,清一色的使用class文件,一样能实现类似于多态效果的业务。只是将多态性退化到函数/方法级别上去实现而已。可以利用手段依然很多,比如重载、继承重写。例如实现超市促销打折功能,不同的促销活动满赠、直降、积分兑现等活动方案是典型的策略模式。我可以写discount1、discount2、discount3三个方法分别对应不同策略,需要增加实现和替换时就增加和替换相应函数即可。如果不同策略的入参类型有变化,甚至函数名都可以重载。你可能会讲,对于重载虚拟机是利用参数类型、个数不同来寻址,而入参的变化违背了方法签名的稳定性啊,这不矛盾吗?!没错,这就引出另一个重要结论:多态更重要的意义在于统一API,即统一的门面。典型的门面模式。那么此时,我不用abstract method,却要在统一的API下供客户端调用,于是就需要一个分发机制的调度员dispatcher,比如叫做“DiscountDispatcher”,它有一个dispatchDiscount()方法作为统一的门面去供client调用,可以传入不同的number标识打折方案等,内部自然是if/else或switch来指定具体的discount1、2、3实现。你可能不屑。因为有太多大牛发表过类似观点:“是时候用多态代替糟糕丑陋的if/else”了!在我看来,这真是打着“神医”的幌子卖狗皮膏药的伎俩,因为不乏架构咨询公司为了兜售自己的咨询服务出了很多类似将简单问题复杂化的书籍和方案。于是不明就里的人过分关注所谓的扩展性而不注重分析眼前需求,过渡设计模式滥用,使得代码绕来绕去费解不堪,本来几个函数就能解决的问题却搞得类爆炸。试想,在编码阶段,不管你用父类引用指向哪个子类实现,也不管你是new还是通过spring的IOC机制,使用@Qualifer注入Bean实现类,哪个不是在编译期就告诉了虚拟机它才有能力创建虚函数映射表?要知道,机器一点也不智能,它是个傻瓜,你不告诉它给他个驱动信号,它不可能知道下一步执行什么逻辑。

        因此,回到开篇的问题上来,不要被语言特性迷惑,要抓住业务中问题的本质,针对业务中稳定的部分进行抽象,隔离变化。说到语言特性,java中类是构造程序框架的基本单元,甚至可以说java是面向类编程的。你可层现想过OOP中的类的抽象概念和你在IDE里写的那个.class编译前的类文件不是一码事!尽管java强制类文件的命名和类同名。但试想,在python中这种弱类型,以函数和对象为一等公民的语言中,类在哪里?一个.py模块算是一个类么?显然不是。pyhton以及C#等其他很多语言,里实际上用命名空间这个概念来标识一个代码组织管理单元的,无论是文件、包、project等。这就表明,代码组织的结构、依赖关系管理,也就是架构的边界和业务用例的边界不是一回事。当然,语言特性终归是特性,各有利弊不能以偏概全。java这种强类型语言,好处多多,比如类型推断系统强大、编译检查方便,所有对象实例是一个模子刻出来的,排错容易。弊端是损失一些灵活性,例如使用lambda表达式不方便,闭包处理要定义函数式接口等。而在python等弱类型语言,对于上述两点,对象创建灵活至极。但也由于对象过于灵活,过于追求囊括一切、适用一切的哲学,就导致不同地方可能造出的对象三头六臂无头怪各不相同,很容易埋下潜在bug,甚至IDE都无能为例。其好处就是一切都是对象,函数也是对象,闭包操作如装饰器模式、短小精悍的lambda大行其道。对于语言的使用,能使用其80%稳定的部分即可;不必追求新特性、怪特性,应扬长避短。再次赘述:本文的中心观点只有一个——在我们程序设计过程,应始终瞄准业务真实含义的抽象,而不要陷入技术手段层面。我以为,在设计阶段一个好的业务抽象,即使不启动编码,应该能用UML等图示表述清晰、闭环自洽。

        

 

 

你可能感兴趣的:(设计模式)