在面向对象的编程中如何处理一些无法归类的对象
model
view
controller
包里面去。但是对于一些共用类就不那么好处理了,比如一些工具类,或者是共用对象,你对它一个类命名一个软件包吧好像有点浪费,但是他们之间又确实没啥关联。很常用的做法是全部放到一个类似util
的包里,但是最近看到一篇文章说这样过于简单粗暴,而且其他人也不好理解这样的分层,不知道各位是怎么处理这个问题的。
回复内容:
这种现象在所有的面向对象工程中都会遇到,在我们编码时,对一般的对象比如mvc的,我们可以把写好的类文件放到相应的model
view
controller
包里面去。但是对于一些共用类就不那么好处理了,比如一些工具类,或者是共用对象,你对它一个类命名一个软件包吧好像有点浪费,但是他们之间又确实没啥关联。
很常用的做法是全部放到一个类似util
的包里,但是最近看到一篇文章说这样过于简单粗暴,而且其他人也不好理解这样的分层,不知道各位是怎么处理这个问题的。
对于这种问题,没有什么硬性规定,重要的是团队内部必须形成规范,且团队的每个成员必须遵守这个规范,这样的话,就会降低新加入成员的熟悉成本。
我们团队内部对于项目公用的一些工具类(类似StringUtils,CollectionUtils等),也会以Util打成包;对于一些模块内部的共用对象,如果是一些enum类,则会以enums打成包;如果是一些模块(例如module1等)内部层与层之间的对象,则会先以dto命名包,再将其放入用其模块名命名的子包内,对于一些模块之间共用的对象,放入common命名的子包,其他共用的类,也会类似的先按照业务功能命名包名,然后在包内,按照不同模块划分子包。
最后一点重要的还是形成并遵守规范。
理论上“对象”应该是由“状态”(数据)与“行为”(逻辑)组合而成,其中状态就是类成员变量,行为就是类成员方法。
楼主所说的工具方法,在大多静态语言中,本身就是非面向对象的东西,又如何以面向对象的方式组织?以Java中常用的Apache Commons Lang库为例,其中的大半类都是各种静态方法组成,例如StringUtils等——静态方法本身就是纯逻辑,与对象无关,也不会保持状态,可以说和平时我们写过程式语言的函数几乎一模一样,所以这类方法参考Apache Commons库或是java.util包写就好了,也是业内通用的做法。
但对于楼主提到的这类工具方法,这里我想补充一下,也算对上段“大多静态语言”这个定语的解释。如果抛开语言限制,仅看面向对象,那么多工具方法的存在其实大半是不合理的。
先看一下JDK自带的工具类java.util.Arrays类,随便挑一个方法,例如Arrays.sort(int[])对数字数组排序:
public static void sort(int[] a); public static void sort(int[] a, int fromIndex, int toIndex);
这个方法的调用为:
int[] xx = ... java.util.Arrays.sort(xx); java.util.Arrays.sort(xx, 1, 10);
以上静态方法的方式看似很合理——因为本来就是个排序逻辑嘛,但如果写成这样呢:
int[] xx = ... xx.sort(); xx.sort(1, 10);
是不是更加OO?我本来就要对数组排序,为何不让数组给自己排序?再看org.apache.commons.StringUtils类,随便找个方法例如:
public static boolean isAlpha(CharSequence cs);
调用时你更想写成下面哪样?
org.apache.commons.StringUtils.isAlpha(aString);
aString.isAlpha();
是不是让你想起了 "string".trim() "string".indexOf() 这些方法?如果你想,可以把StringUtils里的所有方法都变成String的成员方法。。。。。那为毛JDK不把这些东东变成成员方法,而是让它们以静态方法的方式存在于那些丑陋的纯行为类呢?这其实是由各种Java语言特性导致的,例如:
- 对null的强制判断。使得string.isEmpty()有些鸡肋而StringUtils.isEmpty(String s)存在合理
- primitive类型的存在。使得int[].sort()的实现变为不可能,而int的行为也要靠包装类Integer来进行。
- 对象扩展方式单一。为了保持JDK的精炼,只能在String上提供有限的方法,而由于无法对String类进行扩展所以只能做成静态方法。
- 。。。
看看其他语言的情况:
1. JavaScript语言的string没有提供trim()方法,但我们可以扩展:
String.prototype.trim = function() { ... } "string ".trim();
2. Objective-C中对象扩展除了继承之外还可以使用Category的方式(而且Objective-C中对nil的设定灰常和谐)
@interface NSString (Cute) - (NSString *)beCute; @end ... [@"string" beCute];
因此,对于楼主的问题回答是:不是这些类无法归类,而是没有归好类或是语言特性使得很难去归类——只能归成Utils工具类了
最后,个人在这些年里见过的80%以上的 A.method(b) 其实都是可以变化成 b.method(),做到更加OO。其中最多最坑爹还最被广泛认同的一种写法就是:
userManager = ...UserManagerSingleton; userManager.save(user);
这里把明明是User的行为(save)拆分到独立的UserManager类里面搞得一个只有行为一个只有状态本就很奇怪了,还要把完全没有状态的UserManager实例化一个singleton出来,做一个OO假象给别人看。。。骗谁呢。。。还好Rod Johnson后来自己也承认Spring提倡的这种写法其实是在延续EJB时代的“事务脚本”,是anti-OO的东东。国内技术环境浮躁,不少人都是从SSH学起,还以为自己一直在写OO的代码,还以为自己懂OO。。。。
这就是我不提倡纯面向对象的原因之一, 很多时候可以作为一个函数的东东, 一定要被包装成一个类, 加上一个命名空间, 写成一个静态方法.
如果用一个比较好的语言系统, 应该首先语言标准库就提供很多重要的函数和类
其次是第三方库, 作为vendor
然后是本身有价值的东西, 可以有common, 或者内部开源成为一个第三方库, 规范接口.
我不提倡在一个公司的几个projects之间share common, 否则其他团队的修改会bug/crash你的产品, 不如将原来的common fork出来加以修改.
最后是和这个项目的直接相关的代码逻辑
团队沟通是成本最高的, 唯一的办法, 控制开发团队人数
关键还是在于你对代码的组织能力和对业务逻辑的建模能力。你的类名是否起得合适,能否描述这个类的真正业务职责。你的包名、类名应该反映你的业务逻辑,而不是简单的model,controll,view,这种名字只是反映的软件的架构,无法反映你真正的业务,我觉得是应该避免的。
我觉得是可以在controller和model间再思考的。
mvc也只是一种代码组织形式而已,但不要被mvc限定住,一个良好的层次可以不只是mvc。
如果你的业务场景里这三层是很简单,比如“数据库取下数据,controller里稍微加工下,view显示”,那么mvc就够了。
可如果controller下面的数据组织相当复杂,比如需要操作很多其他的系统,或是经过复杂的业务处理后才能得到最终的一份业务数据。那么controller和model之间可以做的文章还是很多的。
那这介于controller和model之间代码如何组织,可能是初步采用面向对象的思路后遇到的烦恼,但当真正理解面向对象对象的概念后其实情况还是可以很清楚的。觉得可以参考下一些关于“设计模式”的东西。
可以架设出很多个属于service(在本地对很多功能进行包装)的类,或者甚至把较独立的功能就封装成内部的api(http的server)。
面向对象本身就是坑,不是什么东西都一定要用类来包装的,比如函数,比如静态方法,比如全局对象?做java的有没有考虑过单例这么自然的东西为什么到java里就成了一个模式?
上一篇: 哪位高手能帮忙写一个正则表达式
下一篇: 创建可读性的随即字符串