当前位置: 首页 > news >正文

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;}@Overridepublic double area() {return length * width;}
}// 圆形类
public class Circle extends Figure {private final double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic 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;}@Overridepublic double area() {return 0.5 * base * height;}
}

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

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

4. 提高内存效率

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

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

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

什么时候使用标签类?

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

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

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

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

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

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

  • 类层次结构 构建不同行为的类别;
  • 多态 取代分支判断。
http://www.dtcms.com/a/333399.html

相关文章:

  • k8s单master部署
  • 用 Enigma Virtual Box 将 Qt 程序打包成单 exe
  • QT|windwos桌面端应用程序开发,当连接多个显示器的时候,如何获取屏幕编号?
  • 【C#补全计划】委托
  • 基于RobustVideoMatting(RVM)进行视频人像分割(torch、onnx版本)
  • 【opencv-Python学习笔记(5):几何变换】
  • 补充日志之-配置文件解析指南(Centos7)
  • 容器内部再运行Docker(DinD和DooD)
  • CUDA中的基本概念
  • Linux软件编程:进程线程(线程)
  • 结构体(Struct)、枚举(Enum)的使用
  • 基于SpringBoot的房产销售系统
  • 护栏卫士碰撞报警系统如何实时监测护栏的状态
  • 系统时钟配置
  • 38 C++ STL模板库7-迭代器
  • 用ICO图标拼成汉字
  • BFS和codetop复习
  • 复杂度扫尾+链表经典算法题
  • Klipper-probe模块
  • H5449G降压恒流无人机照明驱动芯片方案24V/36V/48V/72V降6V12V9V /8A替换NCL30160
  • 探索无人机图传技术:创新视野与无限可能
  • C#WPF实战出真汁06--【系统设置】--餐桌类型设置
  • Linux 系统中, LANG 和 LC_ALL变量有什么区别与联系?
  • 文档对比(java-diff-utils)
  • lidar2imu/auto_caliban以及manual_calib安装过程
  • 8.15网络编程——UDP和TCP并发服务器
  • qs是什么?
  • Python入门第3课:Python中的条件判断与循环语句
  • Ubuntu20.04下Remmina的VNC密码忘记后重置
  • 手机场景性能测试中的部分关键指标