Effective Java笔记:类层次优于标签类

“类层次优于标签类” 是一个重要的面向对象设计原则,它旨在帮助我们更好地设计具有清晰职责和良好扩展性的系统结构。这个原则指出,相比使用 “标签类”(Tagged Class),通过面向对象的继承机制构建合理的类层次结构,能够让代码更加清晰、直观和易于维护。


什么是标签类(Tagged Class)?

标签类(Tagged Class) 是一种反面设计模式,通常通过一个单一的类表示多种逻辑和行为,并使用“标签字段”来区分每种逻辑。例如:

public class Figure {
    enum Shape { RECTANGLE, CIRCLE };  // 标签字段,标识形状的类型
    final Shape shape;

    // 用于矩形的属性
    double length;
    double width;

    // 用于圆形的属性
    double radius;

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

    // 构造矩形
    public Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    // 计算面积(根据标签字段的类型分支处理)
    public double area() {
        switch (shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * radius * radius;
            default:
                throw new AssertionError(shape);
        }
    }
}

在这个例子中,Figure 使用一个 Shape 标签字段来标记它是一个矩形还是一个圆形,然后在构造器和方法中使用 switchif-else 的判断逻辑来操作不同的字段。

标签类设计的缺陷

  1. 职责不单一

    • Figure 试图通过单个类表达多个形状,每种形状都有自己的字段和逻辑,导致职责混乱。
    • 一个形状是矩形,另一个是圆形,它们的行为和数据完全不同,这种设计违背了单一职责原则(SRP - Single Responsibility Principle)。
  2. 易出错,难以维护

    • 很容易出现与逻辑无关的状态。比如,对于矩形, radius 字段是没有意义的,但它仍然可以被实例化。
    • 未来如果添加新的形状(如三角形),需要集中修改标签字段和所有相关的逻辑分支(如 switchif 判断),风险会随代码规模的增长而增加。
  3. 扩展困难

    • 如果需要添加新的形状,比如三角形,不仅要修改 Shape 枚举,还要对所有使用到 shape 标签字段的地方进行修改,特别是分支判断,这会违反 “开放-封闭原则(OCP)”。
  4. 浪费内存或语义模糊

    • 不同形状分别使用不同字段,但在内存中保存了所有字段,这种冗余不仅浪费资源,还可能在读取时混淆语义。

类层次结构优于标签类

通过 类层次结构 的设计思想,我们可以分离出不同逻辑的职责,让代码更加清晰、扩展更加方便、行为更加直观。

重构案例:用继承实现类层次结构

我们可以将上述 Figure 类重构为一个基类和两个子类:

// 抽象基类,定义公共行为
public abstract class Figure {
    public abstract double area();
}

// 矩形类
public class Rectangle extends Figure {
    private final double length;
    private final double width;

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

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

// 圆形类
public class Circle extends Figure {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

现在,每个形状的行为封装在独立的类中,RectangleCircle 专注于自己的逻辑,避免代码的混乱和重复。


标签类 vs 类层次结果的比较

特性标签类类层次结构
职责单一性一个类试图表示所有形状,职责混乱每个子类专注自己的行为与属性
可扩展性添加新形状需要修改多处代码添加新子类只需实现抽象方法
易维护性分支判断逻辑遍布代码多态减少了显式分支判断
错误风险无意义的字段始终存在,如矩形的 radius无冗余字段,语义更清晰
内存使用保存所有字段,浪费内存每个类仅保存与其相关的字段

多态的强大:通过类层次简化业务代码

多态是类层次结构的核心优势,我们可以用多态方法简化对形状的操作,而不需要 switchif 分支。

示例:统一调用 area 方法

public static void main(String[] args) {
    Figure rectangle = new Rectangle(4, 5);
    Figure circle = new Circle(3);

    System.out.println("Rectangle Area: " + rectangle.area());
    System.out.println("Circle Area: " + circle.area());
}
  • 在这个例子中,我们不需要关心 Figure 的具体类型,通过多态(area() 方法的动态绑定),自动调用各自子类的实现。这是设计良好的类层次结构的一个重要特点——消除显式的类型检查。

总结:类层次优于标签类的设计原则

1. 遵循单一职责原则

  • 每个类只专注于自身的行为和逻辑,避免混杂多个职责。
  • Rectangle 管理矩形的逻辑和属性,Circle 管理圆形的逻辑和属性。

2. 遵循开放-封闭原则

  • 使用类层次结构添加新功能时,不需要修改现有类,而是通过添加新子类来扩展行为。例如,为图形系统新增 Triangle(三角形)时,只需增加一个继承 FigureTriangle 类即可。

示例:为类层次结构添加新形状 Triangle

public class Triangle extends Figure {
    private final double base;
    private final double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }
}

3. 减少代码中的分支判断

  • 分支判断(如 switchif-else)可能造成代码冗长且难以维护,与其通过标签字段传递逻辑,不如利用多态直接分发行为。

4. 提高内存效率

  • 类层次结构中的每个类只包含自身的字段,避免了标签类设计中同时保存多个无用字段的资源浪费。

5. 增强类型系统的安全性

  • 类层次结构强迫用户提供类型安全的构造。
  • 基类抽象方法可以确保每个子类都必须实现特定的行为,符合多态的契约,这种强制性提升了类型安全性。

什么时候使用标签类?

虽然标签类通常不是设计的最佳选择,但在某些特定情况下,它依然有存在的意义:

  1. 当类的类型分支很少,且未来扩展几乎不可能时:

    • 如果 Shape 只有两种类型(矩形和圆形),且不打算扩展为复杂的层次模型,那么标签类的设计是可以接受的。
  2. 当所建类是值对象且不可变时:

    • 标签类在某些轻量级的不可变类(如 enum 类型)中可能是一个简单的解决方案。

最终总结:类层次优于标签类

如果一个类需要根据 “标签字段” 来调整逻辑,那通常可以被更好的设计替代。例如:

  • 类层次结构 构建不同行为的类别;
  • 多态 取代分支判断。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值