首页 > 解决方案 > Java类层次结构重构而不改变调用代码,有可能吗?

问题描述

现在的情况:

在Java系统中,我们有一个名为Passenger的类,如下所示,比方说,

public Class Passenger{
//lots of member fields
private String firstName ;
private String lastName ;
private WhatEverAttribute att;

//lots of getters & setters
WhatEverAttribute getAtt(){ return att;}
void setAtt(WhatEverAttribute attIn){ this.att=attIn;}
...

//and lots of methods,for example
List<String> doWhatEverFuction(...){ return ... }
...
}

在其他地方的应用程序中,有很多地方会创建和使用这个类:

Passenger p1 = new Passenger();
p.setFirstName("blablabla")
p.setAtt(xxx);
Passenger p2 = new Passenger();
p2.setAtt(yyy)
List retl = p2.doWhatEverFuction(...);
...

系统之前只管理Air/Flight的乘客,所以Passenger类实际上是航空乘客的数据模型,

现在的问题是,我们需要扩展模型并制作层次结构,因为Passenger将是一个通用的Passenger模型,包含公共字段和功能,新模型AirPassenger和SeaPassenger将对其进行扩展: 在此处输入图像描述

所以一些常见的字段和功能会保留在Passenger中,在AirPassenger和SeaPassenger之间共享,但是大部分航空乘客特定的字段和功能会下推到AirPassenger,

然后每个人都知道我必须更改访问乘客的现有代码

   Passenger p = new Passenger();
   p.xxxxxx();

   AirPassenger p = new AirPassenger();
   p.xxxxxx();  

有很多地方,我不想在现有代码的很多地方手动更改它们,从整个应用程序访问乘客,

我想要的是在创建层次结构之后,其余代码仍然可以正常工作,通过利用一些技术技巧,我可以通过 new Passenger() 构造函数返回一个 AirPassenger,例如:

Passenger{

Passenger(){

return Passenger("Air")

}

Passenger(String type){
  Switch(type){
  ...
  case "Air": return new AirPassenger();
  ...
  }

}

}

通过 Java、CGLIB 或其他的一些动态特性,有可能吗?

标签: javadynamicjavassistcglib

解决方案


您最终选择的方法将取决于许多因素,主要是代码的使用方式和位置。如果您可以访问此类的所有用法,我强烈建议您不要使用您提出的解决方案 - 适当的继承层次结构将有助于保持您的代码井井有条,并可以在将来轻松扩展您的程序。许多 IDE(例如 IntelliJ)为智能重构代码和提取到新类提供了强大的功能,这将自动完成几乎所有工作并确保代码按预期运行。

如果可能,您的情况是使用抽象父类的经典案例。由于没有“普通乘客”这样的东西,因此更正确的层次结构设计是将乘客声明为抽象类。这种方式不能直接创建Passenger的实例,但是类本身可以包含继承类可以根据需要使用或覆盖的实现。您还可以向此类添加工厂方法,该方法将根据输入(如您所建议的那样)返回正确类型的新乘客。

编辑:

多态性和类层次结构在 Java 中以一种非常特殊的方式工作。也就是说,对程序员施加了一些限制,以保持代码的可读性和模块化。您所要求的(在您的澄清评论中)在您描述的方式中是不可能的。

您创建了两个新类:

class AirPassenger extends Passenger {

    ...

    public void doSomethingAir() {
    ...
    }

    ...
}

class SeaPassenger extends Passenger {
    ...

    public void doSomethingSea() {
    ...
    }

    ...  
}

您将它的一些方法重构到新的 AirPassenger 类中,并将一些保留在它们所在的位置。您会注意到的第一件事是,您不能使用现有的 Passenger 构造函数来返回 AirPassenger 或 SeaPassenger 的实例,因为构造函数是一个 void 方法并且没有返回值。因此,您需要提供一些构造工厂方法来创建 AirPassenger 或 SeaPassenger 的实例。

    Passenger createPassenger(String passengerType) { 
        switch (passengerType) {
            case "sea":
                return new SeaPassenger();
            default:
                return new AirPassenger();
        }
    }

您将方法从Passenger 重构到AirPassenger。这些方法不再存在于Passenger 类中,并且它们不能被Passenger 对象调用。但是,您可以使用显式类型转换将所有乘客对象重新转换为 AirPassenger,然后您将能够使用现在在 AirPassenger 中的所有方法。这也可以使用单一方法完成:

AirPassenger convertToAirPassenger(Passenger passenger) {
    return (AirPassenger) passenger;
}

推荐阅读