面向对象编程的思惟方式

我是从学习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的集合类Vector来实现对不定长数据集的存放,于是违反了面向对象设计的一个原则:在设计的时候不该过早的考虑具体程序语言的实现。因此必须用抽象的方法,和具体实现无关的方法来表达业务逻辑。

咱们知道,一般对具备集合特征的数据结构进行遍历一般可使用next和hasNext方法,next实现取下一个用户,hasNext判断是否还有元素。 所以咱们定义一个接口Iterator,这个接口中定义两个方法next和hasNext:

public interface Iterator {
  public boolean hasNext() {}
  public Object next()  {}
}

而User类的listUses方法返回值改成Iterator接口的实现类:

public class User {
  ...
  public Iterator listUsers() {
  }
  ...
}

这样就把User类的设计和具体的实现方法分离开了,由于此时任何实现了next()和hasNext()方法的类均可以作为listUsers的返回值,均可以被用来表达“用户列表”,而不只仅可使用Vector而已。好比,我能够用ArrayList来表达用户列表,由于ArrayList也实现了Iterator,固然我也能够本身专门写一个类来存放用户列表,只要实现next()和hasNext()方法就好了。

这样在具体的编写代码的时候,程序员具备了最大的灵活性,能够根据具体的状况,采用不一样的编程方法来存放用户列表。特别是下降了程序的耦合度,提升了程序的可移植性。对于上面那个JunkMail的listAllMail()方法也一样应该改成接口类型。

而后,在主程序里面就这样来使用User类的listUsers方法:

User myUser = new User();
Iterator iterator = myUser.listUsers();
while (iterator.hasNext()) {
  iterator.next();
}

这样就能够彻底不用考虑程序代码实现了,从高层次上把功能抽象出来,定义成为接口,同时又能够把系统设计的很合理,彻底根据业务的需求来进行设计。

结语

经过上面的几个例子的设计说明,使用面向对象的思惟方法,实际上是一个把业务逻辑从具体的编程技术当中抽象出来的过程,而这个抽象的过程是自上而下的,很是符合人类的思惟习惯,也就是先不考虑问题解决的细节,把问题的最主要的方面抽象成为一个简单的框架,集中精力思考如何解决主要矛盾,而后在解决问题的过程当中,再把问题的细节分割成一个一个小问题,再专门去解决细节问题。

于是一旦紧紧的抓住了这一点,你就会发如今软件设计和开发过程当中,你本身老是会不知不觉的运用面向对象的思惟方法来设计和编写程序,而且程序的设计和开发也变得再也不那么枯燥,而一个合理运用面向对象技术进行设计和架构的软件,更是具有了思惟的艺术美感。

最后,愿面向对象的思惟方法也能给您的程序设计之路带来创做的乐趣。程序员

相关文章
相关标签/搜索