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

    技术2022-05-11  62

    在很久很久以前……(有一个庙……弄错频道了,转台转台……),程序员没有“对象”这个概念,于是就把所有代码都写在同一个地方,而为了方便,写了很多很多函数,这些函数都是可以直接调用的……然后,面向对象的概念出台,为了迎合这一概念,人们开始把以前习惯写的“全局函数”归纳到不同的类下面,作为类的一个方法然后,一个类,除了可以生成实例,然后通过实例使用以前我们叫“函数”的“方法”之外,还可以直接在类里面定义静态成员,这样就不需要先有实例了然后,人们开始大量使用对象,甚至现在已经出现无数例子,是一个类没有它自己的状态(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特性,甚至连一行代码都不用改就可以达到上面代码的目的,限于篇幅,具体实现方法就不写出了。只修改一处代码与四处修改成百上千处代码相比,谁好谁坏?现在楼主应该理解这第二种情况为什么要用实例化类,不用静态类的原因只能有一条:为了满足系统复用性和扩展性需要。 


    最新回复(0)