#前言#
人类思维的形成,是一个不断对行为和现象进行分辨、抽象、提炼的过程。
建立系统的思维模型,长远看有助于较高精准度和较高效率地反向指导我们的行为,解决遇到的现象问题;具体看有助于我们对功能和产品的架构设计更加合理。
TOB产品往往更加注重面向对象的规划思维,而面向对象的产品观是一个非常大的话题。
00
什么是面向对象的思维
面向对象(Object Oriented,OO)的概念来自编程语言和编程思路,其基本思想是从现实世界中客观存在的事物出发,来构造软件系统,并在这个过程中不断把事物的各部分拆分成一个一个的对象来进行模型塑造和搭建。
与之相对应的是面向过程,面向过程(Procedure Oriented)是一种以过程为中心的编程思想。
以使用冰箱的例子来说,面向过程的思路如下。
1) 从菜市场带回了一筐菜。
2) 从框中找到白菜,将冰箱门打开,把白菜放进冷藏区去,把冰箱门关闭。
3) 从框中找到牛肉,将冰箱门打开,把牛肉放进冷冻区,把冰箱门关闭
4) 做饭的时候到了,想吃白菜,把冰箱门打开,找到冷藏区,找到白菜并拿出来。
5) 做饭的时候到了,想吃牛肉了,把冰箱门打开,找到冷藏区,找到牛肉并拿出来。
6) 从超市带回了一个蛋糕,因为是蛋糕,所以要把蛋糕找来新鲜的袋子密封好,将冰箱门打开……
我们发现,对于要装进冰箱的这些白菜、牛肉、蛋糕等,可以精准地描述其操作步骤,就像SOP(Standard Operating Procedure,标准作业程序)一样。
如果家里成员多个,这份SOP就可以给每个人作为执行标准。随着家庭成员变多,想放进冰箱的东西的种类也多了起来,这时候刚才的SOP就不够用了。
随着事物增多、场景增多、客户对事物的要求增多,这份SOP就会越发膨胀,维护成本增大,传达和记忆成本增大,抄一份给到另一个家庭使用的适用性降低等弊端,这就是面向过程的缺点。
尽管它能讲清楚每一条具体的线索,但是复用性和兼容性很差。
现在改用面向对象的思路去分析这个事情,我们首先会发现,无论想往冰箱里装什么,都可以描述为拿到某个东西,打开冰箱,根据某些条件,放进某个区域,再关闭冰箱。如此一来就把事件统一化了,模型就出现了。
之后把任何一个能装进冰箱的事物装进冰箱,都可以套入这个模型中,就可以一步一步运行下去,省去了单独定义的麻烦。
于是我们发现,产品的复用性和面向对象的思维有类似之处,都是更加通用和兼容。这一思想对产品经理设计产品,规划方案有很大的指导意义。
可以与开发的实现思路产生共鸣和增益,有利于产品的架构和扩展。
实际上面向对象的思维不仅是笼统的概念,在具体的工作中也有场景和针对性。
01
模块低耦合与迪米特原则
我们知道,面向对象的系统是由很多对象组成的,对象和对象之间交互,形成了整个系统。
既然对象和对象之间存在交互,那么对象和对象之间就必然存在依赖关系。
依赖即耦合,太多的类耦合在一起相互依赖,就会导致系统的可维护性牵一发而动全身。
那么,如何才能避免这种情况呢?这就要求我们在设计中处理类和类之间的交互时,遵循迪米特法则。
迪米特法则(Law Of Demeter,LOD)又称为“最少知识原则”:一个软件实体应当尽可能少地与其他实体发生相互作用。
这样,当一个模块修改时,就会尽量少地影响其他的模块,扩展会相对容易。
迪米特法则是对软件实体之间通信的限制,它对软件实体之间通信的宽度和深度提出了要求。
这种要求的结果就像是只与你直接的朋友通信,不要跟陌生人说话一样。其好处就是高效。
再打个比方,军队里面有元帅、军官和士兵,元帅认识军官,军官认识自己管辖的士兵。
元帅要攻击敌军,他不必直接对士兵下命令,只需要下命令给自己的军官,由军官将指令转发给自己所辖士兵即可。
用迪米特法则解释,元帅和军官、军官和士兵是“朋友”,元帅和士兵是“陌生人”,元帅只应该与自己直接的“朋友”——军官说话,不要跟“陌生人”——士兵说话。
那么,如何界定朋友和陌生人呢?迪米特法则指出,做为“朋友”的条件如下。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
迪米特法则指出:就任何对象而言,在该对象的方法内,我们只应该调用属于“朋友”对象的方法。也就是说,如果两类不必直接通信,那么这两个类就不应当发生直接的相互作用。
如果其中的一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。如图19-1所示,甲不同陌生人说话,但是可以通过二者共同的朋友进行关联。
迪米特法则下的产品设计风格,尤其适合做大型复杂系统设计指导原则。
缺点是会造成系统的不同模块之间的通信效率降低,使系统的不同模块之间不容易协调。
同时,因为迪米特法则要求类与类之间尽量不直接通信,类之间的通信需要通过第三方转发的方式,这直接导致了系统中存在大量的中介类,为了传递类与类之间的相互调用关系增加了系统的复杂度。
解决这个问题的方式是使用“依赖倒转原则”,使调用方和被调用方之间有一个抽象层,被调用方在遵循抽象层的前提下就可以自由的变化。
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点:降低了类之间的耦合度,提高了模块的相对独立性;由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。
在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
02
功能复用与里氏替换原则
在产品设计时,可以将相似的子功能,归类为一个父类功能,构建出模型,那么这个模型下的所有子类都遵循父类的原则。
举个例子,销售订单作为父类,那么子类有手动创建订单、接口抓取订单、excel导入订单等。
父类订单需要进行订单检查、风控、配货履约、发货妥投等,这时候子类订单都可以复用。
但是有一天,来了一个采购订单,虽然都是订单,但是显然不能放在一起,因为该采购订单作为销售订单子类的时候不能完全继承销售订单的功能。
我们在复用、聚合功能的时候,要用枚举的方式分析子类,确保子类功能与父类功能在复用段的一致性,为保证复用安全稳定,尽量避免子类发生变异,若必须变化,就可能要提出来单独完成,不能再继续继承父类功能。
这在面向对象的设计理念中,对应的是里氏替换原则。
通俗来讲就是子类可以扩展父类的功能,但不能改变父类原有的功能。
也就是说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
生活中也有很多类似的例子,例如,企鹅、鸵鸟和几维鸟从生物学的角度来划分,它们属于鸟类。
从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,所以它们不能定义成“鸟”的子类。
在项目中, 采用里氏替换原则时, 尽量避免子类的“个性”。
一旦子类有“个性”, 这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀。
把子类单独作为一个业务来使用,又会让代码间的耦合关系变得扑朔迷离,缺乏类替换的标准。
03
功能可扩展与开闭原则
“开闭原则”是指的“软件对扩展开放,对修改关闭”。
对修改关闭不是说软件设计不能做修改,只是尽量不要做不必要的修改。
怎么才能做到呢?那就是有相应的扩展性。
其实,软件有相应的扩展性是好处,但是不能说每个地方都有扩展。
反而造成了代码的臃肿。所以这里的扩展与修改关闭是有限制的。
“开闭原则”是面向对象设计的终极目标。我们也可以说成开闭原则是其他原则的核心。比如我们平常喝水用的一次性纸杯。
平常人只是用来装水,喝完水就扔了。这就是这个纸杯的生命周期。
纸杯这一生只完成了它的一个功能:装水。纸杯此时就很封闭了,没有什么扩展性。
此时,用这个纸杯装一朵花苗,纸杯有了它的另外一个扩展性。
纸杯不仅有装水、种花苗的用途,以后还可以有装小垃圾、冲茶、回收等功能。我们用图19-2来直观地说明一下纸杯的设计。
对产品设计的启发就是,我们要预先判断,以后可能会根据需求的变动而扩展。
在实际的产品工作中,扩展性和兼容性对SaaS至关重要。
比如客户提出,希望30天内录入的商品标记为“新品”,为了在后续流程的作业中,通过这个标记着重分析关注该商品。
那么最好的方案可能是增加一个标签字段,“新品”只是其中一个枚举值。
因为商品的属性字段可以随着业务发展而不够用,为了扩展性好,“标签”这样的字段,就可以容纳很多个枚举型字段。
以后可以增加“爆品”“风险品”“试卖品”等。类似的还有,在设计计算规则时,开始需求是做一个加法的操作,我们想加法以后可能会做一个需求变更:加入其他的算法法则。
这个预判会导致以后的扩展、需求变更的难易程度。
对于TOB产品,很多时候要留出足够的扩展接口。比如一个中台系统,在有ERP的情况下,商品资料可以从ERP中获取,但是若没有ERP,将中台作为底层平台,就要支持在中台手动编辑的功能。
04
产品兼容性与依赖倒置原则
所谓依赖倒置原则有两层意思,一层是产品设计要依赖于抽象,不要依赖于具体。
因为通常SaaS系统是庞大的且客户各型各色,若是面向过程的开发,当下层剧烈变化时,上层也要跟着变化,这就会导致模块的复用性降低而且大大提高了开发的成本。
另一层是应用中的重要策略决定应该更加以业务为导向,也就是由高层的模块为核心。
处于高层的模块应优先于低层的模块,迫使低层模块发生改变。
高层模块不应该过于依赖低层模块。它们都应该依赖于抽象。
面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让客户程序依赖于抽象,实现的细节也依赖于抽象。
即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。
这大大降低了客户程序域实现细节的耦合度,因此对产品经理或开发人员抽象能力要求高。
比如商品中台对接底层的ERP系统,但不同的ERP中的字段会有差异。
那么在处理商品中台的商品资料接口时候,就应用以中台自身的需要来设计,让不同的ERP去适应中台的接口,而不用关心各个ERP究竟有什么差异,更不要修改逻辑去迎合新的ERP系统。
当然前提是自己设计的时候也要做足够调用。因为中台才是更加接近客户终端的高层应用。