Effective Java 3rd 条目23 类层级优于标签类

偶尔,你可能遇见一个类,它的实例有两个或者更多的特点(flavor),而且包含了一个标签(tag)域表明这个实例的特点。比如,考虑如下类,它可以代表一个圆形或者长方形:

// 标签类 - 大大次于类层级! 
class Figure { enum Shape { RECTANGLE, CIRCLE };
    // 标签域 - 这个图形的形状 
    final Shape shape;
    
    // 这些域仅仅当形状是RECTANGLE时使用
    double length; 
    double width;

    // 这个域仅仅当形状是CIRCLE时使用
    double radius;

    // 圆形的构造子 
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius; 
    }

    // 长方形的构造子 
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width; 
    }

    double area() {
        switch(shape) { 
            case RECTANGLE:
                return length * width; 
            case CIRCLE:
                return Math.PI * (radius * radius); 
            default:
                throw new AssertionError(shape); 
        }
    }
} 

这样的标签类有许多缺点。它们堆满了样板代码,包括enum声明、标签域和switch语句。因为多个实现杂乱混合在单个类中,所以进一步损害了可读性。因为实例承担着属于其他特点的不相关域,所以内存占用增加了。域不能够变成final,除非构造子初始化不相关域,这导致了更多的样板代码。构造子必须设置标签域,而且在没有编译器协助下初始化正确的数据域:如果你初始化了错误域,那么这个程序将会在运行时失败。你不能够添加新的特点到一个标签类,除非你改变这个源文件。如果你确实要添加一个特点,你必须记得添加一个case到每个switch语句,否则这个类将会在运行时失败。最后,实例的数据类型没有表明它的特点。简而言之,标签类是冗长的、容易出错的和低效的

幸运的是,像Java这样的面向对象语言提供了一个更好的替代方法,定义可以代表多个特点的单个数据类型:子类型化。标签类只是类型层级的苍白的模仿

为了把标签类变成类层级,首先定义一个抽象类,它包含了为标签类中每个方法的抽象方法,这个标签类行为依赖于标签值。在Figure类中,只有一个这样的方法,即area。这个抽象类是类层级的根。如果有任何方法的行为不依赖于标签值,把该方法放入到这个类。相似地,如果所有特点使用的任何数据域,把它们放到这个类。Figure类中没有这样的独立于特点的方法或者域。

其次,为原来的标签类的每个特点,定义一个根类的具体子类。在我们的例子中,有两个:圆形和长方形。在每个子类中包含特属于这个特点的数据域。在我们的例子中,radius特属于圆形,length和width属于长方形。而且包含根类中每个抽象方法的恰当实现。以下是相应于原来Figure类的类层级:

// 标签类的类层级替代 
abstract class Figure { 
    abstract double area(); 
}

class Circle extends Figure { 
    final double radius; 
    
    Circle(double radius) { 
        this.radius = radius; 
    } 
    
    @Override double area() { 
        return Math.PI * (radius * radius); 
    } 
}

class Rectangle extends Figure { 
    final double length; 
    final double width;

    Rectangle(double length, double width) { 
        this.length = length; 
        this.width = width; 
    } 

    @Override double area() { 
        return length * width; 
    }
}

这个类层级更正了前面陈述的标签类的每个缺点。这个代码是简单和明确的,没有包含原版里面的样板代码。每个特点的实现分配了它自己的类,这些类没有负担不相关数据域。所有的域是final的。编译器保证了每个类的构造子初始化了它的数据域,而且保证了每个类为根类中声明的每个抽象方法有一个实现。这消除了由于缺少switch的case运行时失败的可能性。多个程序员可以独立地和共同地扩展这个层级,而不需要访问根类的源代码。每个特点相关联的单独数据类型,让程序员表明这个变量的特点,而且限制变量和输入参数到指定特点。

类层级的另外一个优点在于,它们变得反应了类型之间的自然层级关系,使得有更好的灵活性和更好的编译时类型检查。假设原来例子中的标签类也允许正方形。这个类层级可以变成反应这个事实:正方形是长方形的特殊类型(假设两者都是不可变的):

class Square extends Rectangle { 
    Square(double side) { 
        super(side, side); 
    } 
}

需要注意的是,上面层级中的域是直接访问的,而不是通过访问器方法。这是为了简单起见,如果层级是公开的,那么应该是一个糟糕的设计(条目16)。

总之,标签类很少是适合的。如果你倾向于编写有明显标签域的类,那么考虑这个标签是否移除,而且这个类是否可以用层级替代。当你遇见一个已经存在的带有标签域的类,考虑把它重构为一个层级。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容