JoeCao.github.io
JoeCao.github.io copied to clipboard
Python、Go、Java-从一个方法的归属问题引出的思考
Python、Go、Java-从一个方法的归属问题引出的思考
今天在讨论原有API系统重构的时候引出来一个问题,Java中有个ItemForm对象,是用于展示商品信息,和界面传递消息交互的。这个Form需要有个方法,要通过对应实体的类变量把Elasticsearch存储它的url地址拼接出来。
public class ItemForm {
private String itemId;
private String itemName;
private String tenantId;
public String toEsUrl() {
StringBuilder s = new StringBuilder();
s.append("tenant:").append(tenantId).append(",")
.append("itemId:").append(itemId);
return s.toString();
}
}
大家在讨论,这个方法是放到Form对象本身作为类方法存在,还是放到一个单独的工具类中。 这个问题挺有趣的,首先他和toString()方法不一样,只是给某个特定系统(搜索引擎)使用;但是又要访问Form对象内部的状态。如果能从多种语言设计习惯来分析分析,对我们理解各类语言设计哲学很有帮助
Java
从纯洁面向对象的设计来看,和对象属性相关的方法当然要归到类方法中,但是因为只是为了ElasticSearch使用,为了职责划分明确,单独设计一个ESAdapter的对象,为了方便属性的重用,这个ESAdapter需要继承原来的Form对象,再加上这个拼接ES地址的方法,为了这种设计理念,我们还经常使用对象拷贝的工具在多个对象之间赋值。
public class ESAdapter extends ItemForm {
public String toEsUrl() {
StringBuilder s = new StringBuilder();
s.append("tenant:").append(getTenantId()).append(",")
.append("itemId:").append(getItemId());
return s.toString();
}
}
这样设计导致复杂的继承关系,实在让人有点头大。Java的设计哲学就是这样的,值对象就是值对象,领域对象就是领域对象,严丝合缝,不能混用。 现在都推荐不用继承用组合了, OK
public class ESAdapter {
private ItemForm itemForm;
public ESAdapter(ItemForm form) {
this.itemForm = form;
}
public String toEsUrl() {
StringBuilder s = new StringBuilder();
s.append("tenant:").append(itemForm.getTenantId()).append(",")
.append("itemId:").append(itemForm.getItemId());
return s.toString();
}
}
虽然减少了继承,但是凭空多出来的一个ESAdapter对象,让人如鲠在喉。Java程序往往一大堆细小对象,逻辑在里面分散的开来。想设计一个内聚的逻辑不容易。
Python
同样的功能,如果放到Python Django之类的涨血模型(Rich Domain Object)设计的框架中,那自然毫无疑问的使用类方法中(非static方法),管你那么多设计原则呢,我都可以直接把Domain对象抛到页面去显示,Quick and Dirty用起来再说。
class ItemForm(forms.Form):
item_id = forms.CharField(label=u'商品编码')
item_name = forms.CharField(label=u'商品名称')
tenant_id = forms.CharField(lable=u'租户编码')
def to_esurl(self):
return "itemId:{0},tenantId:{1}".format(self.item_id, self.tenant_id)
c语言
如果是C语言,因为C语言里面没有对象,只有struct结构体,我们习惯单独设计一个工具方法Func做这个事情,数据和行为分离在不同的代码段中,后续接手的人,恐怕想不起来这个功能还有一个单独的方法处理,肯定自己又搞套新的方法去了,重复代码就这样产生。 当然有很多高手已经尝试过在C语言中实现继承和封装这样的OO机制,具体的我不熟悉,不敢乱说。
Go
最近的Go语言,他的设计哲学正好可以适配这样的场景的的,值对象就是struct,没有任何逻辑。所有的逻辑通过单独定义的func+receiver的方式附加到struct上面,同样实现了面向对象的设计,但是有免去了对象继承的麻烦。据说早期的面向对象设计就是将调用一个类方法称为**“向一个对象发送消息” **
type ItemForm struct {
ItemId string
ItemName string
TenantId string
}
func (form ItemForm) ToESUrl() string {
return fmt.Sprintf("tenant:%s,itemId:%s", form.TenantId, form.ItemId)
}
所以说,Go是专门为工程设计的语言,这句话是没错的,从设计哲学上就是实用派,但又坚持自己的原则。这点上看把Go称为Better C是挺有道理的。
思路和语言实现细节
虽然有人说,语言不重要,解决问题的思想最重要。但是对脚本语言/编译型语言、面向对象/函数式这些编程范式不相同的语言来说,差别就大了,甚至同样是面向对象,不同语言的细节也是不一样的。每一门语言是有他的特殊的设计理念的,如果不能掌握这理念,写出来代码没法发挥这门语言的优势,思想很重要,但是掌握语言的设计细节也很重要。
这篇文章是我在初学Go的时候的一点小小思考,我喜欢通过这样对比异同点的方式将新知识纳入我自己的知识体系中,个人觉得这种学习方法很有效,推荐给大家。不过示例代码都是随手写的,没有编译,不保证通过 :)。
版权说明
本文采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
关注我
微信公众号
哈哈,我发现我现在在某些方面也变成实用派了😄 这个问题,在Java中现在一定是简单的类成员。这几年受DDD和微服务的影响,觉得技术上的划分越来越没有义务上的划分来得重要,以前偏向按技术分层现在已经转换到按业务分区,哪怕是这个区内三层都合在一起,也不再觉得这是什么糟糕透顶的事了
@wanyouzhu 我现在也是这样了。下围棋以前都讲究所谓棋感、味道等等,AlphaGo出来才发现这些不过是人类在计算能力不足的情况下,做的一些基于经验的剪枝,很多感觉可能都是错的。而面向对象、代码坏味道这些理论也是在人类对代码掌握不够的情况下做的一些基于经验的判断,未来如果有人工智能写代码,很可能把我们颠覆掉。
机器更懂机器 🥇