我们用钻头,目的不是为了钻他两下,而是为了想要一个窟窿眼。
面向对象也一样,用OOP只是手段,写出好维护的代码才是目的。
不是为了面向对象而强行面向对象,是通过吸收面向对象的精华,写更优秀的代码。
1.面向对象拆解
面向对象能流行,因为确实很优秀。
- 可复用,不用子类多写代码,父类方法就能给子类方法复用。
- 灵活扩展,尽管父类已经定义了主体逻辑,但子类可以自由选择怎么实现。
- 好维护,符合开闭原则,对添加子类开放,对修改父类关闭。对子类的改动不用担心影响全局(不可能一点都不改吧)。
那go语言跟普通面向对象语言差异这么大,是怎样仍然完美拥有这些优点呢。
1.1映射
如果把 Java 类拆解到go里,属性就是struct,方法就是interface。但构造方法不在其列。
比如java类可以这样写
1 | class Bird{ |
go 里可以这样写
1 | type IBird interface{ |
go 语言里,返回值类型是重点。返回值类型不是定义的struct,而是interface。
那么能不能不返回定义的 interface,而是返回定义的 struct呢?
答案是不行。这就涉及到两种语言对代码复用的实现方式。
1.2继承和组合
在java这类面向对象的语言上,复用是通过继承的方式来实现的。
子类继承父类,子类完全可以代替父类来使用。
1 | class A{ |
上述操作是完全没问题的,因为 B 也是 A的一种。
但是在go里,上述就行不通了。
go的复用是通过组合的方式来实现的。没有父类子类的概念,而是超集的概念。
超集可以执行子集的方法,但是不支持作为子集类型被传入。
1 | type Base struct{} |
所以你来我往大家操作的类型都是 interface。
1.3殊途同归
但回过头仔细想想,一般情况下,Java里所有的属性都建议设为private,不对外开放。外部只能调用方法来处理。跟go里也差不多。
这种机制在java里只是写起来有些死板,但是在go里,直接就被定死了,想要灵活,想要复用就只能返回 interface。
这样一想,写java的时候念头都通达了。OOP的时候不用再想着和谁干点什么,而是想着找个能干的就行,管他是谁呢。
2.面向对象实战
众所周知,百闻不如一见,百看不如一干。所以我们以一个线上需求实践一下。
需求:将多个数据源提供的数据入库,各个数据源提交来的字段不一样,但最终落地的数据字段是一致的。
2.1 代码
下面的代码不是很规范,用了几个魔数,类型还用了map。忽略细节,看本质。
真正写代码不会有人这样写的。
真正写代码不会有人这样写的。
真正写代码不会有人这样写的。
dddd
无封装写法
1 | func saveData(request map[string]string) { |
简单封装写法
1 | type IExtract interface { |
其实还可以封装得再给力一点,比如
- 分到不同的文件,改动一个逻辑的时候尽量不影响其他逻辑。
- 干掉那个Switch,让他自己动(反射、map或者init)。
后面有机会再说。
2.2分析
封装了,代码反倒更长了。
所谓一寸长,一寸强,有谁会拒绝更长的呢。
复用性:只要在 AbstractExtractor 名下定义的方法, Extractor1 和 Extractor2都能调用。
灵活扩展:如果要增加一种数据源,可以采用近似于新加子类的方式操作
好维护:假如 Extractor2 和 Extractor1 某个地方不一样,自己改自己的就行了,不用担心影响全局。
上面不就是一个典型的工厂模式吗
2.3拓展
那么好好的面向对象怎么不能用了,就算用了经典的面向对象,现有的特性应该也可以完全保留。
好端端的,为什么非要用这种方式拆开呢?
业务还没写好,就不想这种终极问题了。