首页 > 学院 > 开发设计 > 正文

献给面向对象的人:面向对象的思维方法

2019-11-18 12:05:14
字体:
来源:转载
供稿:网友

  我是从学习java编程开始接触OOP(面向对象编程),刚开始使用Java编写程序的时候感觉很别扭,因为我早以习惯用C来编写程序,很欣赏C的简洁性和高效性,喜欢C简练而表达能力丰富的风格,非凡忍受不了Java运行起来慢吞吞的速度,相对冗长的代码,而且一个很简单的事情,要写好多类,一个类调用一个类,心里的抵触情绪很强。
  
  我对Java的面向对象的特性琢磨良久,自认为有所领悟,也开始有意识的运用OOP风格来写程序,然而还是经常会觉得不知道应该怎样提炼类,面对一个具体的问题的时候,会觉得脑子里千头万绪的,不知道怎么下手,一不小心,又会回到原来的思路上去。
  
  举个例子,要发广告邮件,广告邮件列表存在数据库里面。倘若用C来写的话,一般会这样思考,先把邮件内容读入,然后连接数据库,循环取邮件地址,调用本机的qmail的sendmail命令发送。
  
  然后考虑用Java来实现,既然是OOP,就不能什么代码都塞到main过程里面,于是就设计了三个类:
  
  一个类是负责读取数据库,取邮件地址,调用qmail的sendmail命令发送;
  
  一个类是读邮件内容,MIME编码成Html格式的,再加上邮件头;
  
  一个主类负责从命令读参数,处理命令行参数,调用发email的类。
  
  把一件工作按照功能划分为3个模块分别处理,每个类完成一件模块任务。
  
  仔细的分析一下,就会发现这样的设计完全是从程序员实现程序功能的角度来设计的,或者说,设计类的时候,是自低向上的,从机器的角度到现实世界的角度来分析问题的。因此在设计的时候,就已经把程序编程实现的细节都考虑进去了,企图从底层实现程序这样的出发点来达到满足现实世界的软件需求的目标。
  
  这样的分析方法其实是不适用于Java这样面向对象的编程语言,因为,假如改用C语言,封装两个C函数,都会比Java实现起来轻松的多,逻辑上也清楚的多。
  
  我觉得面向对象的精髓在于考虑问题的思路是从现实世界的人类思维习惯出发的,只要领会了这一点,就领会了面向对象的思维方法。
  
  举一个非常简单的例子:假使现在需要写一个网页计数器,客户访问一次页面,网页计数器加1,计数器是这样来访问的
  
   引用:
      代码:
  
  http://hostname/count.cgi?id=xxx
  
  后台有一个数据库表,保存每个id(一个id对应一个被统计访问次数的页面)的计数器当前值,请求页面一次,对应id的计数器的字段加1(这里我们忽略并发更新数据库表,出现的表锁定的问题)。
  
  假如按照一般从程序实现的角度来分析,我们会这样考虑:首先是从HTTP GET请求取到id,然后按照id查数据库表,获得某id对应的访问计数值,然后加1,更新数据库,最后向页面显示访问计数。
  
  现在假设一个没有程序设计经验的人,他会怎样来思考这个问题的呢?他会提出什么样的需求呢?他很可能会这样想:
  
  我需要有一个计数器,这个计数器应该有这样的功能,刷新一次页面,访问量就会加1,另外最好还有一个计数器清0的功能,当然计数器假如有一个可以设为任意值的功能的话,我就可以作弊了。
  
  做为一个没有程序设计经验的人来说,他完全不会想到对数据库应该如何操作,对于HTTP变量该如何传递,他考虑问题的角度就是我有什么需求,我的业务逻辑是什么,软件应该有什么功能。
  
  按照这样的思路(请注重,他的思路其实就是我们平时在生活中习惯的思维方式),我们知道需要有一个计数器类 Counter,有一个必须的和两个可选的方法:
  
   引用:
    代码:
  getCount()  // 取计数器值方法
  resetCounter()  // 计数器清0方法
  setCount()  // 设计数器为相应的值方法 
  
  
  把Counter类完整的定义如下:
  
   引用:
    代码:
  public class Counter {
   public int getCount(int id) {}
   public void resetCounter(int id) {}
   public void setCount(int id, int currentCount) {}
  } 
  
  解决问题的框架已经有了,来看一下如何使用Counter。 在count.cgi里面调用Counter来计数,程序片断如下:
  
   引用:
    代码:
   // 这里从HTTP环境里面取id值
    ...
   Counter myCounter = new Counter(); // 获得计数器
   int currentCount = myCounter.getCount(id); // 从计数器中取计数
   // 这里向客户浏览器输出
  
  程序的框架全都写好了,剩下的就是实现Counter类方法里面具体的代码了,此时才去考虑具体的程序语言实现的细节,比如,在getCount()方法里面访问数据库,更新计数值。
  
  从上面的例子中看到,面向对象的思维方法其实就是我们在现实生活中习惯的思维方式,是从人类考虑问题的角度出发,把人类解决问题的思维方式逐步翻译成程序能够理解的思维方式的过程,在这个翻译的过程中,软件也就逐步被设计好了。
  
  在运用面向对象的思维方法进行软件设计的过程中,最轻易犯的错误就是开始分析的时候,就想到了程序代码实现的细节,因此封装的类完全是基于程序实现逻辑,而不是基于解决问题的业务逻辑。
  
  学习JDBC编程的经典错误问法是:“我怎样封装对数据库的select操作?”
  
  面向对象的设计是基于解决业务问题的设计,而不是基于具体编程技术的设计。我不会去封装select语句的,我只封装解决问题的业务逻辑,对数据库的读取是在业务逻辑的编码实现阶段才去考虑的问题。
  
  回过头看上面那个发广告邮件的例子,应该如何应用面向对象的思维方法呢?
  
  对于一个邮件来说,有邮件头,邮件体,和邮件地址这三个属性,发送邮件,需要一个发送的方法,另外还需要一个能把所有邮件地址列出来的方法。所以应该如下设计:
  
   引用:
    代码:
  类JunkMail
  
  属性:
   head
   body
   address
  方法:
   sendMail()  // 发送邮件
   listAllMail() // 列邮件地址
  
  用Java来表示:
  
  public class JunkMail {
   PRivate String head;
   private String body;
   private String address;
   public JunkMain() {  // 默认的类构造器
     // 从外部配置文件读邮件头和邮件体
     this.head=...;
     this.body=...;
   }
  
   public static boolean sendMail(String address) {
     // 调用qmail,发送email
   }
  
   public static Collection listAllMail() {
     // 访问数据库,返回一个邮件地址集合
   }
  } 
  
  当把JunkMail设计好了以后,再调用JunkMail类完成邮件的发送,将是非常轻松的事情。
  
  假如说传统的面向过程的编程是符合机器运行指令的流程的话,那么面向对象的思维方法就是符合现实生活中人类解决问题的思维过程。
  
  在面向对象的软件分析和设计的时候,要提醒自己,不要一上来就去想程序代码的实现,应该抛开具体编程语言的束缚,集中精力分析我们要实现的软件的业务逻辑,分析软件的业务流程,思考应该如何去描述和实现软件的业务。究竟软件只是一个载体,业务才是我们真正要实现的目标。
  
  但是在设计过程中,心里却往往在担心,假如我完全不去考虑程序代码的实现的话,那么我怎么知道我的设计一定合理呢?我怎么知道我设计的类、接口一定可以实现呢?所以经常可以看到的现象就是:
  
  在设计过程中,虽然知道不能过早考虑代码实现,但是每设计一个类,一个接口,心里都要不知不觉的用自己熟悉的编程语言大概的评估一下,看看能否编出来,因此,一不小心,就会又回到按照程序功能实现的思路进行设计的老路上去了。
  
  举个例子来说明,在做Web程序设计的时候,经常要碰到分页显示数据的情况。比如说需要把系统中所有的用户都列出来这样的功能。假设使用User类来表示用户,增加用户addUser(),删除用户deleteUser(),查询所有用户listUsers()方法。而数据库中有一个user表,一条记录是一个用户的信息。下面考虑一下User类的方法的实现:
  
  addUser()和deleteUser()方法都好实现,就是对数据库增加记录和删除记录。对于listUsers()方法,其实就是对user表的select,取出一个记录集。但是该怎么从listUsers()方法中得到所有用户的列表呢?
  
  一个方法调用的返回值只有一个,没有多个,所以很多情况下采用的办法就是返回值定义为集合类型,比如Vector。这样就可以在listUsers()方法的具体代码实现的时候,从数据库依次取出一个个记录,插入到Vector里面来。在主程序里面,调用listUsers()方法可以返回一个Vector,然后再对Vector遍历操作,就可以得到用户列表了。
  
   引用:
    代码:
  
  public class User {
  
   public static void addUser(...) {
    // 数据库insert一条记录
   }
  
   public static void deleteUser(...) {
    // 数据库delete一条记录
   }
  
   public Vector listUsers(...) {
    // 数据库select结果放到一个集合里面
   }
  } 
  
  这样的设计基本合理,但是仍然有点小问题。因为在设计的时候,就考虑到了用Java的集合类Vecto

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表