重构手法——对象结构优化类 | 类关系解耦 | 提取超类

71 阅读5分钟

简介

“提取超类”(Extract Superclass)是一种重要的重构手法,用于处理多个类存在相似结构和行为的情况。当多个类有共同的属性、方法或行为时,将这些共性提取到一个新的超类中,可以减少代码重复,提高代码的可维护性和可扩展性。通过继承超类,子类可以复用超类的代码,并且可以根据自身需求进行扩展。

针对的症状(代码坏味道)

  • 代码重复:多个类中存在大量相同或相似的属性和方法,违反了代码复用原则。
  • 难以修改:当需要修改这些重复的代码时,需要在多个类中进行修改,容易出现遗漏,导致代码不一致。
  • 缺乏扩展性:如果需要添加新的共性功能,需要在每个类中重复添加,增加了开发和维护的成本。

提取超类(Extract Superclass)的详细步骤

  1. 识别共性
    • 分析多个类的属性和方法,找出它们之间的共同部分,包括字段、方法签名和方法实现。
  2. 创建超类
    • 根据识别出的共性,创建一个新的超类。超类的名称应能够清晰地描述其代表的抽象概念。
  3. 移动共性成员
    • 将共同的属性和方法从子类移动到超类中。如果方法的实现完全相同,可以直接移动;如果实现部分不同,可以在超类中定义抽象方法,由子类进行具体实现。
  4. 让子类继承超类
    • 修改子类的定义,让它们继承自新创建的超类。
  5. 调整子类代码
    • 移除子类中因移动到超类而变得多余的代码。如果子类需要对超类的方法进行扩展或修改,可以通过重写方法来实现。
  6. 测试
    • 编译代码:确保代码编译通过,没有任何语法错误。
    • 运行测试:运行所有相关的单元测试,确保重构操作没有引入新的错误。
    • 手动测试:如果有必要,进行手动测试以验证功能的正确性。
  7. 代码审查
    • 同行评审:让同事或其他团队成员审查你的更改,确保代码质量和可维护性没有下降。
    • 文档更新:如果项目有维护文档的习惯,记得更新相关文档,说明提取超类的影响。

示例

假设有 DogCat 两个类,它们都有 name 属性和 makeSound 方法,存在代码重复。

原始代码

class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void makeSound() {
        System.out.println(name + " says Woof!");
    }
}

class Cat {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void makeSound() {
        System.out.println(name + " says Meow!");
    }
}

重构步骤

  1. 识别共性DogCat 类都有 name 属性和 getName 方法,makeSound 方法虽然实现不同,但方法签名相同。
  2. 创建超类:创建 Animal 超类。
  3. 移动共性成员:将 name 属性和 getName 方法移动到 Animal 超类中,makeSound 方法定义为抽象方法。
  4. 让子类继承超类:让 DogCat 类继承自 Animal 超类。
  5. 调整子类代码:移除子类中重复的 name 属性和 getName 方法。

重构后代码

// 超类 Animal
abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract void makeSound();
}

// 子类 Dog
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(getName() + " says Woof!");
    }
}

// 子类 Cat
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(getName() + " says Meow!");
    }
}

练习

基础练习题

  1. 简单类的超类提取
    • 给定以下 Java 代码,RectangleSquare 类都有 widthheight 属性(Squarewidthheight 相等),以及计算面积的方法。请提取一个超类来减少代码重复。

      class Rectangle {
          protected int width;
          protected int height;
      
          public Rectangle(int width, int height) {
              this.width = width;
              this.height = height;
          }
      
          public int getArea() {
              return width * height;
          }
      }
      
      class Square {
          protected int side;
      
          public Square(int side) {
              this.side = side;
          }
      
          public int getArea() {
              return side * side;
          }
      }
      
  2. 多方法类的超类提取
    • 下面的 Java 代码中,CarTruck 类都有 startEnginestopEnginemove 方法。请提取一个超类来重构代码。

        class Car {
          public void startEngine() {
              System.out.println("Car engine started.");
          }
      
          public void stopEngine() {
              System.out.println("Car engine stopped.");
          }
      
          public void move() {
              System.out.println("Car is moving.");
          }
      }
      
      class Truck {
          public void startEngine() {
              System.out.println("Truck engine started.");
          }
      
          public void stopEngine() {
              System.out.println("Truck engine stopped.");
          }
      
          public void move() {
              System.out.println("Truck is moving.");
          }
      }
      

进阶练习题

  1. 超类提取与抽象方法应用
    • 在这段 Java 代码中,CircleTriangle 类都有计算面积的方法,但计算方式不同。请提取一个超类,将计算面积的方法定义为抽象方法。

        class Circle {
          private double radius;
      
          public Circle(double radius) {
              this.radius = radius;
          }
      
          public double getArea() {
              return Math.PI * radius * radius;
          }
      }
      
      class Triangle {
          private double base;
          private double height;
      
          public Triangle(double base, double height) {
              this.base = base;
              this.height = height;
          }
      
          public double getArea() {
              return 0.5 * base * height;
          }
      }
      
  2. 超类提取与子类扩展
    • 给定以下 Java 代码,StudentTeacher 类都有 nameid 属性,以及 getInfo 方法,但 Teacher 类还有 subject 属性。请提取一个超类,并让 Teacher 类扩展超类。

        class Student {
          private String name;
          private int id;
      
          public Student(String name, int id) {
              this.name = name;
              this.id = id;
          }
      
          public String getInfo() {
              return "Student: " + name + ", ID: " + id;
          }
      }
      
      class Teacher {
          private String name;
          private int id;
          private String subject;
      
          public Teacher(String name, int id, String subject) {
              this.name = name;
              this.id = id;
              this.subject = subject;
          }
      
          public String getInfo() {
              return "Teacher: " + name + ", ID: " + id + ", Subject: " + subject;
          }
      }
      

综合拓展练习题

  1. 多类超类提取与代码审查模拟
    • 考虑一个简单的 Java 图形绘制系统,有 LineRectangleCircle 类,它们都有 draw 方法,并且都有 color 属性。请提取一个超类来重构这些类。

    • 假设你完成了重构,请模拟一份简单的代码审查报告,指出重构后的优点和可能存在的潜在问题。

      class Line {
          private String color;
      
          public Line(String color) {
              this.color = color;
          }
      
          public void draw() {
              System.out.println("Drawing a line with color: " + color);
          }
      }
      
      class Rectangle {
          private String color;
      
          public Rectangle(String color) {
              this.color = color;
          }
      
          public void draw() {
              System.out.println("Drawing a rectangle with color: " + color);
          }
      }
      
      class Circle {
          private String color;
      
          public Circle(String color) {
              this.color = color;
          }
      
          public void draw() {
              System.out.println("Drawing a circle with color: " + color);
          }
      }