代码重构相关内容
提炼父类
当我们发现多个类中存在相同的属性和方法,或者这些类都需要实现某些共同的行为时,就可以考虑使用提炼l父类来减少代码冗余并提高代码复用性。
什么时候需要提炼父类
- 多个类中存在相同的属性和方法,但它们不是一个继承关系,使用提炼超类可以将这些类中相同的属性和方法提取到一个新的父类中。
- 多个类需要实现相同的行为,但它们不是一个继承关系。
- 多个类是一个继承关系,但是它们中的某些属性和方法只在子类中出现,使用提炼超类可以将这些属性和方法提取到一个新的父类中,以便让子类继承它们。
提炼父类流程
- 创建一个新的父类,并将要提取的共同属性和方法都移动到新的父类中。
- 在新的父类中定义一个抽象方法或虚拟方法,用于子类中特有的行为或属性。
- 修改原来的子类,使它们继承新的父类,并重写新父类中的抽象方法或虚拟方法。
- 删除子类中已经移动到父类中的共同属性和方法。
一个提炼父类的例子
public class Point {
protected int x;
protected int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public abstract class Shape {
protected Point point;
public Shape(Point point) {
this.point = point;
}
public abstract double getArea();
}
public class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(Point point, int width, int height) {
super(point);
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
public class Circle extends Shape {
private int radius;
public Circle(Point point, int radius) {
super(point);
this.radius = radius;
}
@Override
public double getArea()
假设我们有一个Shape类和两个子类Rectangle和Circle,它们都有相同的属性x和y表示坐标,我们可以使用Extract Superclass将它们的相同属性提取到一个新的父类Point中。
提取子类的公共逻辑
如果某个接口存在多个实现类的时候,这些实现类存在相同的代码逻辑,可以将子类中的公共代码上移至父类中。或者提供一个抽象父类中。
需要提取那些内容
- 子类中存在重复代码。
- 子类中一些方法虽业务不同但是存在逻辑相同之处。那么考虑有些内容是否可以提炼方法称为一个新的方法然后提取到父类中
- 子类的一些行为可以算是父类的行为的一种变化,可以将属于父类的行为上移到父类中。
如何提取子类公共代码
- 首先要分析子类,找出大多数子类都具有的行为。
- 如果这些行为在子类方法中的某些代码段中,尝试使用提炼方法方式,将这些逻辑抽象出来。
- 将这些相同或相似行为的方法上移至父类中。
- 设置父类方法访问权限,确保子类可以使用。
- 删除子类中对应方法,修改子类调用。
- 测试。
构造方法本体上移到父类中
构造方法本体上移,将子类中重复的构造方法体上移到父类中,从而减少重复代码,提高代码复用性。
何时构造方法上移
使用构造方法上移主要考虑下面几个因素
- 构造方法在多个子类中重复出现,并且它们的实现代码相同或相似。
- 需要父类控制部分内容的初始化时。
- 当创建新的子类时,需要使用构造方法本体上移可以提高代码的复用性和可扩展性。
如何操作
- 首先需要确定重构的构造方法在多个子类中重复出现,并且它们的实现代码相同或相似。
- 在父类中创建一个新的构造方法,包含之前在子类中的重复代码。
- 将这些重复代码从子类的构造方法中删除,只保留调用父类的构造方法。
- 确保新的父类构造方法和子类构造方法有相同的参数列表和访问控制修饰符。
- 调整子类调用,改为调用父类构造方法。
- 测试。
需要注意的是将子类构造方法提升到父类后,这些构造方法会被所有子类使用,如果不仔细考虑代码中可能存在的细微差异,这个重构技巧可能会导致一些潜在的问题。此外,如果某个子类具有特定的初始化逻辑,可能不适合使用构造方法本体上移。
一个简单的例子
重构前的代码
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// ...
}
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age);
this.breed = breed;
}
// ...
}
public class Cat extends Animal {
private String color;
public Cat(String name, int age, String color) {
super(name, age);
this.color = color;
}
// ...
}
重构后的代码:
java
Copy code
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// ...
}
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age);
this.breed = breed;
}
// ...
}
public class Cat extends Animal {
private String color;
public Cat(String name, int age, String color) {
super(name, age);
this.color = color;
}
// ...
}
在重构后的代码中,Animal类包含了子类中重复的构造方法实现。通过使用构造方法本体移动到父类技巧,可以减少代码的重复,并提高代码的可维护性和可扩展性。
子类字段移动到父类
和提取公共代码类似,提取公共字段是将多个子类中具有相同的字段提升到父类中。字段提升到父类时需要注意,这些字段需要与父类的属性具有相似的特性时,这些字段会作为父类的属性存在。如果这样就需要保证这些字段,在所有子类中都发挥了作用。
子类字段作为父字段时需要考虑下面因素
- 多个子类都拥有的字段,且这些字段移动到父类中,可以作为父类业务字段存在。
- 字段在子类中应该代表相同的含义和用途。
具体操作流程
- 找出子类之间重复的字段,并确保这些字段在子类中含义相同。
- 在父类中添加新的字段,并设置访问权限。
- 删除子类中同名字段以及字段的访问方法,将访问方法移动到父类中。
- 修改子类中这些字段的调用方式。
- 测试。
需要注意的是,如果抽取到父类中的字段名称,和某些子类的变量重名时,要注意修改子类变量名,避免混淆。
将父类方法实现和字段移动到子类中
只有个别类使用的公共内容
如果父类中某些方法或者字段,只在某个或某些子类中使用。这里需要考虑将这些方法或者字段作为子类中的内容,而不应该在父类中实现。
需要注意一种情况。如果一个字段或者方法在多个子类中使用,但是子类中它们所代表的含义和用法完全脱离的父类的约定,两者仅仅是使用相同的名称。这个时候应该考虑是否为子类创建独立的字段或者方法用来区分父类的内容,而不是移除父类的方法。
如何移除父类方法实现
- 确认需要下推的方法。可以通过查看代码的调用关系来确定。
- 删除父类实现,并标记为abstract。
- 在子类中创建该方法的具体实现。
- 测试。
如何移除父类字段
- 确认需要下推的字段。可以通过查看代码的调用关系来确定。
- 在父类中将该字段的访问修饰符改为protected或private,这样就不会在子类之外被访问。
- 在子类中创建该字段的具体实现。
- 修改代码中使用该字段的地方,确保它们引用的是子类中的字段。
- 测试。
一个移除父类方法实现的例子
重构前
public class Animal {
public void move() {
System.out.println("Animal is moving");
}
}
public class Bird extends Animal {
public void move() {
System.out.println("Bird is flying");
}
}
重构后的代码
public abstract class Animal {
public abstract void move();
}
public class Bird extends Animal {
public void move() {
System.out.println("Bird is flying");
}
}
合并子类
将一个子类与其父类合并为一个类或者将多个子类合并。这个过程中,我们需要将子类中的特定属性和方法进行合并,。这样可以简化代码结构,避免维护多个类造成的麻烦,提高代码的可读性和可维护性。
使用移除子类主要考虑下面几个因素
- 子类与父类的区别很小或者子类之间的区别很小,甚至只是一小部分属性或方法的差别。
- 子类的数量比较少,而且它们都实现了父类的大部分功能;
- 重构后不会增加过多的复杂性。
如何合并子类和父类
- 分析哪些子类可以合并。如果多个子类合并,是否存在重名但是业务不同的方法和参数,注意区分。
- 将子类字段、方法移动到父类或者新的类中。
- 将对子类的调用替换为对父类或者新的类的调用。
- 如果合并的多个子类有业务上差别,可以在构造方法中添加标识属性来进行区分。
下面是一个合并子类的例子
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age, PetType.CAT);
}
@Override
public void bark() {
System.out.println("Meow!");
}
}
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age, PetType.DOG);
}
@Override
public void bark() {
System.out.println("Woof!");
}
}
重构后
public class Pet extends Animal {
public Pet(String name, int age, PetType type) {
super(name, age, type);
}
@Override
public void bark() {
if (getType() == PetType.CAT) {
System.out.println("Meow!");
} else if (getType() == PetType.DOG) {
System.out.println("Woof!");
}
}
}
我们创建一个新的Pet类,用于代替Cat和Dog类
Animal cat = new Pet("Tom", 2, PetType.CAT);
cat.bark(); // Meow!
Animal dog = new Pet("Buddy", 3, PetType.DOG);
dog.bark(); // Woof!
使用Pet类来创建一个猫或狗对象,并调用bark方法:
使用委托关系取代继承关系
将继承关系中的子类使用委托来实现的重构技巧,主要用于减少子类的数量并增强系统的灵活性。
使用继承的问题
当对象的行为有明显的类别之分,使用继承方式我们可以把共用的数据和行为放在超类中,每个子类根据需要覆写部分特性。这样很容易就完成需求的实现、
但是继承存在两个问题。
首先是继承关系都是单一的继承,我们只能继承一个类,而实际中业务可能需要多个方向上的扩展。另外一个问题继承给类之间引入了非常紧密的关系。在超类上做任何修改,都很可能破坏子类,如果两个类的逻辑分处不同的模块、由不同的团队负责,问题就会更麻烦。
需要注意的是,继承并非是不好的内容,大部分时候使用继承就能达到我们需要的效果,不会带来问题。一般是从继承开始,如果开始出现问题,再转而使用委托。
而使用委托时,对于不同的变化原因,我可以委托给不同的类。委托是对象之间常规的关系。和继承相比其接口更加清晰,类之间的耦合很少。这样在实现某些逻辑时
替换子类
什么时候使用委托类替换子类
- 子类之间的差异不够明显或者不够重要。
- 子类的数量过多,那么使用委托来取代子类,从而消除子类,使得代码更加清晰简洁。
如果使用委托类取代子类
我们将一些差异作为委托类的实现来传递。
- 创建一个空的委托类,这个类的构造方法应该接受所有子类内容,并且经常以参数的形式接受一个指回超类的引用。
- 在超类中添加一个字段,用于安放委托对象。
- 修改子类的创建逻辑,使其初始化上述委托字段,放入一个委托对象的实例。
- 选择一个子类中的方法,将其移入委托类。
- 如果这个方法用到的其他元素也应该被移入委托对象,就把它们一并搬移。
- 如果这个方法用到的元素应该留在超类中,就在委托对象中添加一个字段,令其指向超类的实例。
如果使用委托类取代子类非常简单的例子
// 父类
class Animal {
private BehaviorDelegate behaviorDelegate;
public Animal(BehaviorDelegate behaviorDelegate) {
this.behaviorDelegate = behaviorDelegate;
}
public void move() {
behaviorDelegate.move();
}
public void sound() {
behaviorDelegate.sound();
}
}
// 子类
class Cat extends Animal {
public Cat() {
super(new CatBehaviorDelegate());
}
}
// 子类
class Dog extends Animal {
public Dog() {
super(new DogBehaviorDelegate());
}
}
// 委托接口
interface BehaviorDelegate {
void move();
void sound();
}
// 委托类
class CatBehaviorDelegate implements BehaviorDelegate {
public void move() {
System.out.println("Cat is moving...");
}
public void sound() {
System.out.println("Meow...");
}
}
// 委托类
class DogBehaviorDelegate implements BehaviorDelegate {
public void move() {
System.out.println("Dog is moving...");
}
public void sound() {
System.out.println("Woof...");
}
}
使用继承+委托的方式,使得子类逻辑实现更加灵活。子类除了继承Animal
的特性并对其进行扩展之外。通过传递的委托类不同,也会表现不同的行为。子类最终表现出来的形式会根据Animal
的实现和CatBehaviorDelegate
的实现结合而来。
替换父类
什么时候使用委托类替换父类
当一个子类继承了超类的很多方法,但是只使用其中的几个方法,而且这些方法并不是在子类中实现的,此时就可以考虑使用使用委托关系取代父类重构手法。使用这种重构技术可以将超类的某些行为委托给其他对象来实现,这样可以使得子类只关注它需要的行为,而不必继承整个超类的行为。
何如使用委托类取代父类
- 创建一个新的代理类,该类包含超类的一个实例变量。
- 将超类中需要委托给其他对象实现的行为抽象出来,并在代理类中实现这些行为。
- 将超类中需要委托的实例变量和方法从超类中删除,代理类中添加相应的实例变量和方法,并使用代理对象进行调用。
- 测试。
使用委托类取代父类的例子
// 超类
public class Superclass {
public void operation1() {
// 实现代码
}
public void operation2() {
// 实现代码
}
public void operation3() {
// 实现代码
}
public void operation4() {
// 实现代码
}
}
// 子类
public class Subclass extends Superclass {
public void operation5() {
// 实现代码
}
}
// 代理类
public class Delegate {
private Superclass superclass;
public Delegate(Superclass superclass) {
this.superclass = superclass;
}
public void operation1() {
superclass.operation1();
}
public void operation2() {
superclass.operation2();
}
public void operation3() {
superclass.operation3();
}
public void operation4() {
superclass.operation4();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Superclass obj = new Subclass();
Delegate delegate = new Delegate(obj);
delegate.operation1();
delegate.operation2();
delegate.operation3();
delegate.operation4();
}
}