【问题标题】:Refactoring abstract Java class with many child classes重构具有许多子类的抽象 Java 类
【发布时间】:2009-09-21 20:21:55
【问题描述】:

我正在寻找重构此场景的最佳方法(更好的设计,最少的工作)的想法。 从以下示例抽象类开始(实际上还有更多的字段、方法和抽象方法):

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。问题是如何在运行时更新carObject 的同时,最大限度地减少对这 100 多个子类的更改代码(最好保持不变)。

注意我被要求处理这段代码,所以我一开始就没有在这种情况下编写它。

【问题讨论】:

    标签: java design-patterns refactoring abstract-class


    【解决方案1】:

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

    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% 的代码没有引用具体的汽车类(只有基类),所以你应该能够相当容易地改变事情。当然,不看真实代码很难说。

    【讨论】:

    • 挑剔:应该是:blue6.setEngine(Engine.V6);因为气缸实际上是发动机的属性,而不是汽车。某些属性是不可变的,例如manufactureYear,并且在对象创建后无法更改。类不应公开此类属性的设置器。
    • 我一般同意 setter,除非您想遵循 JavaBean 规范,否则参数化构造函数会失去其价值,因为需要非参数化构造函数。关于引擎 - 当然,如果他正在建模超出气缸数的引擎细节或需要策略模式或类似的东西,但在这里看起来他只需要一个平面 int 而不需要更多细节。
    【解决方案2】:

    这取决于您对创建这些对象的代码的控制程度。我将假设这种设计存在的原因在汽车示例中有点丢失,但如果对象是通过调用 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; }
    }
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        因此你不应该只使用不同的子类不同的参数。

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

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

        之前:

        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 );
        ....
        

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

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

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

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

          Car xyz = new WhatEver( number );
        

        对于

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

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

        【讨论】:

          猜你喜欢
          • 2015-01-06
          • 2015-05-30
          • 2010-11-26
          • 1970-01-01
          • 2018-09-10
          • 2023-03-27
          • 2018-06-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多