技术开发 频道

敏捷开发的必要技巧(三)

将抽象类变成接口

    现在,看一下Shape这个类。它本身没有实际的方法,所以,它更应该是一个接口。

interface Shape { void draw(Graphics graphics); } class Line implements Shape { ... } class Rectangle implements Shape { ... } class Circle implements Shape { ... }

改进后的代码

    改进后的代码就像下面这样:

interface Shape { void draw(Graphics graphics); } class Line implements Shape { Point startPoint; Point endPoint; void draw(Graphics graphics) { graphics.drawLine(getStartPoint(), getEndPoint()); } } class Rectangle implements Shape { Point lowerLeftCorner; Point upperRightCorner; void draw(Graphics graphics) { graphics.drawLine(...); graphics.drawLine(...); graphics.drawLine(...); graphics.drawLine(...); } } class Circle implements Shape { Point center; int radius; void draw(Graphics graphics) { graphics.drawCircle(getCenter(), getRadius()); } } class CADApp { void drawShapes(Graphics graphics, Shape shapes[]) { for (int i = 0; i < shapes.length; i++) { shapes[i].draw(graphics); } } }

    现在如果我们想要支持更多的图形(比如三角形),上面所有的类都不用修改,我们只需要创建一个新的类Triangle就行了。

另一个例子

    让我们来看另外一个例子。在当前的系统中,有三种用户:常规用户、管理员和游客。常规用户必须每隔90天修改一次密码(更频繁也行)。管理员必须每30天修改一次密码。游客则不需要修改。常规用户和管理员可以打印报表。
    先看一下当前代码:

class UserAccount { final static int USERTYPE_NORMAL = 0; final static int USERTYPE_ADMIN = 1; final static int USERTYPE_GUEST = 2; int userType; String id; String name; String password; Date dateOfLastPasswdChange; public boolean checkPassword(String password) { ... } } class InventoryApp { void login(UserAccount userLoggingIn, String password) { if (userLoggingIn.checkPassword(password)) { GregorianCalendar today = new GregorianCalendar(); GregorianCalendar expiryDate = getAccountExpiryDate(userLoggingIn); if (today.after(expiryDate)) { //提示用户修改密码 ... } } } GregorianCalendar getAccountExpiryDate(UserAccount account) { int passwordMaxAgeInDays = getPasswordMaxAgeInDays(account); GregorianCalendar expiryDate = new GregorianCalendar(); expiryDate.setTime(account.dateOfLastPasswdChange); expiryDate.add(Calendar.DAY_OF_MONTH, passwordMaxAgeInDays); return expiryDate; } int getPasswordMaxAgeInDays(UserAccount account) { switch (account.getType()) { case UserAccount.USERTYPE_NORMAL: return 90; case UserAccount.USERTYPE_ADMIN: return 30; case UserAccount.USERTYPE_GUEST: return Integer.MAX_VALUE; } } void printReport(UserAccount currentUser) { boolean canPrint; switch (currentUser.getType()) { case UserAccount.USERTYPE_NORMAL: canPrint = true; break; case UserAccount.USERTYPE_ADMIN: canPrint = true; break; case UserAccount.USERTYPE_GUEST: canPrint = false; } if (!canPrint) { throw new SecurityException("You have no right"); } //打印报表 } }

用一个对象代替一种类别

    根据前面讲的解决方法,要去掉类别代码,我们只需要为每种类别创建一个子类,比如:

abstract class UserAccount { String id; String name; String password; Date dateOfLastPasswdChange; abstract int getPasswordMaxAgeInDays(); abstract boolean canPrintReport(); } class NormalUserAccount extends UserAccount { int getPasswordMaxAgeInDays() { return 90; } boolean canPrintReport() { return true; } } class AdminUserAccount extends UserAccount { int getPasswordMaxAgeInDays() { return 30; } boolean canPrintReport() { return true; } } class GuestUserAccount extends UserAccount { int getPasswordMaxAgeInDays() { return Integer.MAX_VALUE; } boolean canPrintReport() { return false; } }

    但问题是,三种子类的行为(即代码)都差不多一样,getPasswordMaxAgeInDays这个方法就一个数值不同(30、90或者Integer.MAX_VALUE)。canPrintReport这个方法也只有一个数值不同(true或false)。这三种用户类型只需要用三个对象代替就可以,无须特地新建三个子类。

class UserAccount { UserType userType; String id; String name; String password; Date dateOfLastPasswdChange; UserType getType() { return userType; } } class UserType { int passwordMaxAgeInDays; boolean allowedToPrintReport; UserType(int passwordMaxAgeInDays, boolean allowedToPrintReport) { this.passwordMaxAgeInDays = passwordMaxAgeInDays; this.allowedToPrintReport = allowedToPrintReport; } int getPasswordMaxAgeInDays() { return passwordMaxAgeInDays; } boolean canPrintReport() { return allowedToPrintReport; } static UserType normalUserType = new UserType(90, true); static UserType adminUserType = new UserType(30, true); static UserType guestUserType = new UserType(Integer.MAX_VALUE, false); } class InventoryApp { void login(UserAccount userLoggingIn, String password) { if (userLoggingIn.checkPassword(password)) { GregorianCalendar today = new GregorianCalendar(); GregorianCalendar expiryDate = getAccountExpiryDate(userLoggingIn); if (today.after(expiryDate)) { //提示用户修改密码 ... } } } GregorianCalendar getAccountExpiryDate(UserAccount account) { int passwordMaxAgeInDays = getPasswordMaxAgeInDays(account); GregorianCalendar expiryDate = new GregorianCalendar(); expiryDate.setTime(account.dateOfLastPasswdChange); expiryDate.add(Calendar.DAY_OF_MONTH, passwordMaxAgeInDays); return expiryDate; } int getPasswordMaxAgeInDays(UserAccount account) { return account.getType().getPasswordMaxAgeInDays(); } void printReport(UserAccount currentUser) { boolean canPrint; canPrint = currentUser.getType().canPrintReport(); if (!canPrint) { throw new SecurityException("You have no right"); } //打印报表. } }

    注意到,用一个对象代替类别同样可以移除switch或者if-then-else-if。

总结类别代码移除

    要移动一些类别代码和switch表达式有两种方法:
    1. 用基于同一父类的不同子类来代替不同的类别;
    2. 用一个类的不同对象来代替不同的类别。
    当不同的类别具有较多不同行为时,使用第一种方法。当这些类别的行为非常相似,或者只是差别在一些数值上时,使用第二个方法。

普遍的代码异味

    类别代码和switch表达式是比较普遍的代码异味。此外,还有一些其他代码异味也很普遍。下面是一些异味的列表:
    代码重复
    太多的注释
    类别代码(type code)
    switch或者长串if-then-else-if
    想给一个变量、方法或类名取个合适的名字,却怎么也取不好
    用类似XXXUtil、XXXManager、XXXController和其他类似命名
    在变量、方法或类名中使用这些单词“And”、“Or”等
    一些实例中的变量有时有用,有时没用
    一个方法的代码太多,或者说方法太长
    一个类的代码太多,或者说类太长
    一个方法有太多参数
    两个类都引用了彼此(依赖于彼此)

0
相关文章