方法何时定义为静态或非静态的

在很久很久以前……(有一个庙……弄错频道了,转台转台……),程序员没有“对象”这个概念,于是就把所有代码都写在同一个地方,而为了方便,写了很多很多函数,这些函数都是可以直接调用的……

然后,面向对象的概念出台,为了迎合这一概念,人们开始把以前习惯写的“全局函数”归纳到不同的类下面,作为类的一个方法

然后,一个类,除了可以生成实例,然后通过实例使用以前我们叫“函数”的“方法”之外,还可以直接在类里面定义静态成员,这样就不需要先有实例了

然后,人们开始大量使用对象,甚至现在已经出现无数例子,是一个类没有它自己的状态(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特性,甚至连一行代码都不用改就可以达到上面代码的目的,限于篇幅,具体实现方法就不写出了。

只修改一处代码与四处修改成百上千处代码相比,谁好谁坏?

现在楼主应该理解这第二种情况为什么要用实例化类,不用静态类的原因只能有一条:为了满足系统复用性和扩展性需要。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值