使用许多子类重构抽象Java类

时间:2009-09-21 20:21:55

标签: java design-patterns refactoring abstract-class

我正在寻找关于重构此场景的最佳方法的想法(更好的设计,最小的努力)。 从以下示例开始抽象类(实际有更多字段,方法和抽象方法):

abstract class Car
{
    private int manufactureYear;
    // ... many more fields that are hard to clone

    public Car(int manYear)
    {
        this.manufactureYear = manYear;
    }

    abstract public Color getColor();
    abstract public int getNumCylinders();
}

有很多子类(比如100)扩展了这个类。这些儿童课程被认为是汽车的“规格”。以下是两个例子:

class CarOne extends Car
{
    private static Color COLOR = Color.Red;
    private static int CYLINDERS = 4;

    public CarOne(int manYear)
    {
        super(manYear);
    }

    public final Color getColor();
    {
        return COLOR;
    }

    public final int getNumCylinders() 
    {
        return CYLINDERS;
    }
}

class CarOneThousand extends Car
{
    private static Color COLOR = Color.Black;
    private static int CYLINDERS = 6;

    public CarOneThousand(int manYear)
    {
        super(manYear);
    }

    public final Color getColor();
    {
        return COLOR;
    }

    public final int getNumCylinders() 
    {
        return CYLINDERS;
    }
}

在运行时期间,汽车对象被实例化并使用:

CarOne carObject = new CarOne(2009);
carObject.getColor();
carObject.getNumCylinders();

然而,在获得一些外部数据后,我发现汽车被重新粉刷,发动机也发生了变化。汽车的新规格成为:

class ModCar extends Car
{
    private static Color COLOR = Color.Blue; 
    private static int numCylinders = 8;

    public ModCar (int manYear)
    {
        super(manYear);
    }

    public final Color getColor();
    {
        return COLOR;
    }

    public final int getNumCylinders() 
    {
        return numCylinders;
    }
}

因此,确实需要将这些规范“应用”到新的carObject,而无需修改manufactureDate等现有字段。问题是如何最小化这些100多个子类的更改代码(最好不要触及它们),同时能够在运行时更新carObject

N.B。我被赋予了这个代码的工作,所以我没有在这个条件下写它开始。

4 个答案:

答案 0 :(得分:8)

根据说明和示例,您不恰当地使用继承。看起来您正在创建许多类,您应该使用单个类和许多对象实例。如果是这样,您也不需要设计模式来解决问题。如果没有进一步澄清问题,这应该足够了:

class Car
{
    private int manufactureYear;
    private Color color;
    private int numCylinders;

    public int getManufactureYear() { return manufactureYear; }
    public void setManufactureYear(int manufactureYear) { this.manufactureYear = manufactureYear; }

    public Color getColor() { return color; }
    public void setColor(Color color) { this.color = color; }

    public int getNumCylinders() { return numCylinders; }
    public void setNumCylinders(int numCylinders) { this.numCylinders = numCylinders; }
}

示例用法:

// make a blue 6-cylinder:
Car blue6 = new Car();
blue6.setColor(BLUE);
blue6.setCylinders(6);

// make a red 4-cylinder:
Car red4 = new Car();
red4.setColor(RED);
red4.setCylinders(4);

// Uh-oh, they painted my red car!
red4.setColor(YELLOW);

如果要最小化更改,可以使用上面重构的Car类,然后清理子类以便它们利用它。类似的东西:

class CarOne extends Car { // extends my version of Car...

    private static Color COLOR = Color.Red;
    private static int CYLINDERS = 4;

    public CarOne() {
      setColor(COLOR);
      setNumCylinders(CYLINDERS );
    }

    // getters deleted, base class has them now
}

由于实际上有一个基类,我的猜测是99%的代码没有引用具体的汽车类(只有基类),所以你应该能够相当容易地改变它。当然,如果没有看到真正的代码,很难说。

答案 1 :(得分:1)

这取决于您对创建这些对象的代码的控制程度。我将假设这种设计存在的原因在汽车示例中有点丢失,但是如果通过调用new来创建对象,那么除了更改它们之外你几乎没有做任何事情,尽管你可以使用这个答案的其余部分提出了一种更灵活的方式来改变它们。

如果您可以控制他们的创建,那么使用合成并返回一种不同类型的汽车对象的工厂会覆盖您关注的特定参数,并为其余参数调用原始对象将允许您影响对特定实例的更改不改变所有原始类。类似的东西:

Car carOne = CarFactory.makeCar("CarOne", 2009);

然后在makeCar方法中,您可以决定是否返回CarOne对象或复合实现:

public class CompositeCar extends Car {


      private Car original;
      private Color myColor;

      public CompositeCar(Car original, Color myColor) {
          this.original = original;
          this.myColor = myColor;
      }

      public int getYear() { return original.getYear(); }

      public Color getColor() { return myColor; }
}

答案 2 :(得分:0)

如果您的案例(或整个类组)具有复杂的构造逻辑,我还建议您查看the Builder Pattern,特别是如果某些汽车需要某些字段,并且不同的字段集是其他人需要。

答案 3 :(得分:0)

您的子类不提供不同的行为,只提供不同的数据

因此,您不应仅使用不同的子类来使用不同的参数。

我建议在你的基础案例中添加一个“getCar”方法,并将其用作工厂方法。

添加Color和Cylinder属性并从......加载它们适合您的需要,它可能是数据库,属性文件,模拟对象,来自互联网,来自宇宙的地方......等等。 / p>

在:

Car car = new CarOne(2009); // Using new to get different data....
carObject.getColor();
carObject.getNumCylinders();

后:

class Car {
    // Attributes added and marked as final.
    private final Color color;
    private final int numberCylinders;
    // original 
    private final int manufacteredYear;

    public static Car getCar( String spec, int year ) {

          return new Car( year, 
                          getColorFor( spec ) , 
                          getCylindersFor(spec) );

    }

    // Make this private so only the static method do create cars. 
    private Car( int year, Color color, int cylinders ) {
         this.manufacturedYear = year;
         this.color = color;
         this.numberCylinders = cylinders;
    }

    // Utility methods to get values for the car spec.
    private static final getColorFor( String spec ) {
       // fill either from db, xml, textfile, propertie, resource bundle, or hardcode here!!!
       return ....
    }
    private static final getCylindersFor( String spec ) {
       // fill either from db, xml, textfile, propertie, resource bundle, or hardcode here!!!
       return .... 
    }

    // gettes remain the same, only they are not abstract anymore.
    public Color getColor(){ return this.color; }
    public int getNumCylinders(){ return this.numberCylinders; }


}

因此,不是直接创建新车,而是从getCar方法获取它:

Car car = Car.getCar("CarOne", 2009 );
....

我不建议你让你的汽车“可变”,因为它可能带来微妙的不良副作用(这就是我将属性标记为最终的原因)。因此,如果您需要“修改”汽车,最好分配新属性:

 Car myCar = Car.getCar("XYZ", 2009 );
 .... do something with car
 myCar = Car.getCar("Modified", 2009 );
 //-- engine and color are "modified" 

此外,您甚至可以映射整辆车,因此您只使用一个实例。

通过这样做,您不必在代码中添加setter。您唯一需要做的就是搜索和替换

  Car xyz = new WhatEver( number );

对于

 Car xyz = Car.getCar("WhatEver", number );

其余的代码应该无需更改即可运行。