在很久很久以前……(有一个庙……弄错频道了,转台转台……),程序员没有“对象”这个概念,于是就把所有代码都写在同一个地方,而为了方便,写了很多很多函数,这些函数都是可以直接调用的……
然后,面向对象的概念出台,为了迎合这一概念,人们开始把以前习惯写的“全局函数”归纳到不同的类下面,作为类的一个方法
然后,一个类,除了可以生成实例,然后通过实例使用以前我们叫“函数”的“方法”之外,还可以直接在类里面定义静态成员,这样就不需要先有实例了
然后,人们开始大量使用对象,甚至现在已经出现无数例子,是一个类没有它自己的状态(state,意思是保存在实例里面的变量的值),只对外提供公用的方法,也就是这个类只是一系列逻辑上联系比较紧密的“函数”的集合,但是人们仍然不用静态来定义这些“函数”,而是创建一个这个类的实例,然后通过这个实例去调用这些与这个实例完全没关系的方法……
在这个时候我的逻辑就混乱了,为什么在这种情况下,仍然不用静态成员呢?我最近看到很多个例子都是这样,看上去就像不喜欢直接从 Math 对象里面调用数学函数 (例如 Math.Pow(x, y)),而必须先弄一个新的“数学”对象(Math myMathObject = new Math()),然后再调用方法(myMathObject.Pow(x, y))……这不是多此一举么?
呵呵,我来解答一下楼主的疑问。
楼主的所谓“泛对象”化,其实不是问题。
针对楼主所说的问题,有两种情况:
1、不要迷信你看到的所谓经典,当你认为它错误时,首先应该去怀疑它,而不是推翻你自己
错误的OO用法象天上的星星一样多。不要以为Java/DotNet这样的业界标准就一定是100%的榜样,Java/DotNet虽然号称以OO为己任,但是其中的API和OO用法问题也照样是连篇累牍。所以不要看到别人的程序这样用,或者书籍上这样用,就认为OO是这样用的。
举一个简单例子,我见过有人这样做:
代码:
//DotNet程序,本类是DB数据操作层类的实现
public class DB
{
//获取所有用户的列表
public DataSet GetAllUsers();
//获取指定ID的用户
public User GetUserByID(string a_id);
...
}
这个类中没有一个数据成员,也就是楼主所谓的State。既然没有State,也搞这种实例化类来进行数据操作,明显是错误的OO用法。
用实例化类的最主要理由就是:实例化一个类是为了获得类对象的数据成员或者状态。
这个问题一直在某个CRM系统中使用了数年,后来被我重新培训之后,这伙人才认识到数年的惯例用法原来是错误的。正确的实现应该如下:
代码:
public class DB
{
//获取所有用户的列表
public static DataSet GetAllUsers() { ... }
//获取指定ID的用户
public static User GetUserByID(string a_id) { ... }
...
}
上面的类改成静态方法才是正确的OO用法。
楼主看到的绝大多数情况应该都是类似这种错误的用法,用者自己往往还没有意识到,这不是OO的正确理解和用法。
2、不得不做的妥协。
还有一种用法,原本确实可以用静态成员或静态类来实现,但是出于复用性或者扩展性的需要,从而放弃静态类,转用实例化类。
在一些大型的、设计规范严谨的系统中,Java/DotNet中也能看到,往往可以看到这种类设计妥协的出现。
这种用法不是错误的用法,恰恰相反,是真正透彻理解了OO以后才能做到的用法。
举个例子。
假设要做一个数据库操作类,但是,根据系统扩展性和复用性的需要,要求这个数据库操作类必须能够适应SQLServer和DB2两种数据库。
如果用静态方法来实现,就只能这样:
代码:
public class DB_AllInOne
{
//执行一段SQL
public static int ExecuteSQLs_MSSQL(string[] a_sqls) { ... }
public static int ExecuteSQLs_DB2(string[] a_sqls) { ... }
//执行一个存储过程
public static int ExecuteProcedure_MSSQL(string a_procedure, object[] a_args) { ... }
public static int ExecuteProcedure_DB2(string a_procedure, object[] a_args) { ... }
}
或者这样实现:
代码:
public class DB_MSSQL
{
//执行一段SQL
public static int ExecuteSQLs(string[] a_sqls) { ... }
//执行一个存储过程
public static int ExecuteProcedure(string a_procedure, object[] a_args) { ... }
}
public class DB_DB2
{
//执行一段SQL
public static int ExecuteSQLs(string[] a_sqls) { ... }
//执行一个存储过程
public static int ExecuteProcedure(string a_procedure, object[] a_args) { ... }
}
上面的代码有问题,如果要使用这种静态方法的类,就必须将具体的实现类与代码死死绑定在一起。
例如下面的代码:
代码:
...
switch(DBSYSTEM)
{
case MSSQL:
DB_MSSQL.ExecuteSQLs(new string[] { "select * from Table1 where Field1='%test%' " });
break;
case DB2:
DB_DB2.ExecuteSQLs(new string[] { "select * from Table1 where Field1='%test%' " });
break;
}
...
一旦象上面代码这样绑定了具体实现,那么系统就失去了复用性和扩展性。想想看,如果现在要求再兼容Oracle数据库,那么就不得不在所有执行数据库操作的地方都加入对Oracle的判断和执行,一个典型的中小型系统,至少有数以近百处的地方会调用数据库操作,那么就要修改这些地方,一旦遗漏或者疏忽了某些地方,就会产生严重的BUG问题。
所以,业界也认识到提高复用性和扩展性是多么重要的一个目标。
于是,在这种情况下,优秀的OO设计师和程序员会将原本的静态用法转变为实例用法,从而大大提高了系统的复用性和扩展性。
下面是修改以后的代码:
代码:
//数据库操作的通用接口
public interface IDB
{
//执行一段SQL
int ExecuteSQLs(string[] a_sqls);
//执行一个存储过程
int ExecuteProcedure(string a_procedure, object[] a_args);
}
//MSSQL的接口实现
public class DB_MSSQL : IDB
{
//执行一段SQL
public int ExecuteSQLs(string[] a_sqls) { ... }
//执行一个存储过程
public int ExecuteProcedure(string a_procedure, object[] a_args) { ... }
}
//DB2的接口实现
public class DB_DB2 : IDB
{
//执行一段SQL
public int ExecuteSQLs(string[] a_sqls) { ... }
//执行一个存储过程
public int ExecuteProcedure(string a_procedure, object[] a_args) { ... }
}
除了去掉static和增加了一个接口以外,似乎没有太大的改变,但是从OO角度来看,可是翻天覆地的变化。
我们如何使用这种实例类?看如下例子:
代码:
...
//只需在此处创建一次,即可永久使用
IDB db = null;
switch(DBSYSTEM)
{
case MSSQL:
db = new DB_MSSQL();
break;
case DB2:
db = new DB_DB2();
break;
//如果将来需要增加对其他数据库的支持,只需修改此处一段代码即可
}
...
//使用DB,看,只需要用接口实例即可,无需知道实现类是MSSQL还是DB2,这样就大大提高了系统复用性和扩展性
db.ExecuteSQLs(new string[] { "select * from Table1 where Field1='%test%' " });
...
而且,对于Java/DotNet现代语言来说,通过Reflection特性,甚至连一行代码都不用改就可以达到上面代码的目的,限于篇幅,具体实现方法就不写出了。
只修改一处代码与四处修改成百上千处代码相比,谁好谁坏?
现在楼主应该理解这第二种情况为什么要用实例化类,不用静态类的原因只能有一条:为了满足系统复用性和扩展性需要。