面向对象的思维方法

    |     2015年4月10日   |   Java面向对象   |     0 条评论   |    1570

从学 Java 才真正碰到 OOP。习惯用 C 的人,往往会嫌 Java 慢、代码长、一件小事要拆好多类。抵触很正常;真正别扭的,是不知道该怎么提炼类,一不小心又滑回过程式思路。

面向对象的精髓,是从现实世界的人类思维习惯出发:先谈业务要什么,再翻译成程序能懂的结构。封装的是业务逻辑,不是 select 语句。

思路 怎么切 问题
自底向上(机器视角) 按读库、编码、解析参数拆模块 类跟着实现细节走,换 C 写两个函数更清楚
自上而下(业务视角) 先有计数器、邮件这些“东西”和它们会做的事 过早想 JDBC / Vector,又会掉回实现驱动

一、发广告邮件:别按程序员的手拆类

邮件列表在数据库里。用 C 会想:读邮件内容 → 连库循环取地址 → 调本机 qmail 的 sendmail。改用 Java 后,怕全塞进 main,于是拆三个类:一个读库并发送,一个读内容做 MIME,一个主类解析命令行。这是按实现步骤自底向上切的,设计时已经把底层细节想完了。

从业务看,一封邮件有头、体、地址;要能发送,还要能列出所有地址。类应该是 JunkMail,而不是“读库类 / 编码类 / 参数类”。

public class JunkMail {
    private String head;
    private String body;
    private String address;

    public JunkMail() {
        // 从外部配置文件读邮件头和邮件体
        this.head = ...;
        this.body = ...;
    }

    public static boolean sendMail(String address) {
        // 调用 qmail,发送 email
        return true;
    }

    public static Collection listAllMail() {
        // 访问数据库,返回一个邮件地址集合
        return null;
    }
}

(原文构造方法误写成 JunkMain,这里按类名改回 JunkMail。)类设计好了,调用发送会轻松很多。qmail、JDBC 是方法体里的事,不是类的切分依据。

二、网页计数器:从需求倒推接口

访问 http://hostname/count.cgi?id=xxx,后台表里每个 id 对应一个计数值(这里忽略并发锁表)。过程式会想:从 HTTP GET 取 id → 查表 → 加 1 → 更新 → 显示。

没有编程经验的人会说:我要一个计数器,刷新页面加 1,最好能清 0,再能设成任意值就可以“作弊”。他不会先想数据库和 HTTP 变量。按这个习惯,需要一个 Counter 类:

public class Counter {
    public int getCount(int id) {}
    public void resetCounter(int id) {}
    public void setCount(int id, int currentCount) {}
}

// 在 count.cgi 里调用
Counter myCounter = new Counter();
int currentCount = myCounter.getCount(id);

框架先有了,getCount() 里再去访问数据库。学习 JDBC 时最容易问错的是:“我怎样封装对数据库的 select?”——设计阶段封装的是业务,读库是编码实现阶段的事。

三、用户列表:返回接口而不是 Vector

Web 里常要分页列出所有用户。User 有 addUser、deleteUser、listUsers。增删对应 insert / delete 很好办;listUsers 本质是 select 出一个记录集。方法只能有一个返回值,于是很多人返回 Vector,在方法里把记录一条条塞进去。主程序再遍历 Vector。

这样已经能用,但设计时就绑死了 Java 的集合类,违反“设计阶段不过早考虑具体语言实现”。对集合遍历,抽象成 hasNext / next 更稳:

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

public class User {
    public static void addUser(...) { /* insert */ }
    public static void deleteUser(...) { /* delete */ }
    public Iterator listUsers() { /* 返回任意实现了 Iterator 的对象 */ }
}

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

任何实现了 next / hasNext 的类都能表达“用户列表”,不必绑死 Vector;ArrayList 可以,自己写一个类也可以。耦合度下来,实现阶段才有最大灵活性。JunkMail 的 listAllMail() 同样应该改成接口类型。

四、自上而下,先主后次

过程式符合机器跑指令的流程;面向对象符合人解决问题的过程:先把主要矛盾收成简单框架,再把细节拆成小问题。设计时心里常担心“不考虑代码,怎么知道类一定能实现”,于是每设计一个接口都用熟悉的语言默默评估一遍——一不小心又回到按程序功能切类。

抓住这一点,设计和编码会不那么枯燥;合理运用面向对象的软件,会带一点思维上的干净。软件只是载体,业务才是目标。

一句话总结:先按人类业务习惯抽象类和接口,再在方法体里写 JDBC 和集合;切类按“有什么、能干什么”,不要按“先读库再发信”的实现步骤。

转载请注明来源:面向对象的思维方法
本文链接地址:https://ai.zhousir.top/?p=150
回复 取消