【问题标题】:Creating child objects on basis created parent object in java在java中创建的父对象的基础上创建子对象
【发布时间】:2017-11-09 17:47:19
【问题描述】:

我正在学习 java 设计模式,我想知道是否可以应用一些解决以下问题。我有 Solider 类和一些子类,例如:General 和 Sergeant。我正在创建 Solider 对象,在运行时我想将此对象更改为 General 或 Sergeant 对象,或者使用之前创建的 Solider 对象创建新的 Sergeant 或 General 对象:

 Solider s = new Solider(...);
 .....
 if (generalCondition) {
     General g = createGeneralFromSolider(s);
     //or better:
     //General g = promoteSoliderToGeneral(s);
 } else if (sergeantCondition) {
      Sergeant sr = createSergeantFromSolider(s);
      //or better:
      //Sergeant sr = promoteSoliderToSergeant(s);
 }

首先我决定在 General/Sergeant Class 中创建额外的构造函数:

Class General extends Solider {
    General(Solider s, Map<String, String> generalSpecificParams) {
        //first we are going to copy all solider params to general params (bad idea if we have a lot of params)
        this.setParamX(s.getParamX());
        ....
        //then we can assign the rest of general-specific params
        this.setGeneralSpecificParams(generalSpecificParams);
    }
}

并在方法 createGeneralFromSolider 中使用它,但我不确定它是否是优雅的方式。主要缺点是我创建了新对象,所以在调用 createGeneralFromSolider 后我在内存中有 2 个对象。我宁愿在内存中有一个对象:从 Solider 提升的 General/Sergeant(对象 General/Sergeant,之前是 Solider 对象)。我想知道我是否可以使用一些设计模式来解决它。我记得在 C++ 中有类似复制构造函数的东西,它通过一个接一个地分配所有参数来将所有参数从一个对象复制到另一个对象。在 Java 中我没有听说过类似的东西。

【问题讨论】:

  • 你确定要在这里使用继承吗?

标签: java design-patterns


【解决方案1】:

对于这种情况,您可能需要使用Factory 模式。 例如:

public class SoldierFactory {

   //use getSoldier method to get object of type Soldier 
   public Soldier getSoldier(String soldierType){
      if(soldierType == null){
         return null;
      }     
      if(soldierType.equals("case1")){
         return new General();

      } else if(soldierType.equals("case2")){
         return new Sergeant();

      } else if(.....

      }

      return null;
   }
}




public class FactoryPatternDemo {

   public static void main(String[] args) {
      SoldierFactory soldierFactory = new SoldierFactory();

      Soldier s1 = soldierFactory.getsoldier("case1");


   }
}

我认为在调用士兵工厂之前最好不要创建士兵。不管在运行时,你都会改变它,对吧?

【讨论】:

  • 一开始我有 Solider 对象。然后,我想在运行时更改它(或使用它创建新的)。我不确定这家工厂对我的情况有何帮助。
  • 您正在从头开始创建最终对象 - 而不是我想要的现有 Solider。如果您的工厂将使用给定的 Solider 对象 (factory.getSolider("caseX", solidetObj) 创建 Sergeant 或 General - 那么解决方案是可以接受的。
【解决方案2】:

首先,在构造子类的时候,像这样使用super作为构造函数的第一条语句:

class Soldier {
    private String rank; // e.g. Pvt, PFC, etc.
    private int yearsOfService;

    // ... (Standard constructor)
    public Soldier(Soldier s) {
        this.rank = s.rank; this.yearsOfService = s.yearsOfService;
    }
    // ... (Getters and Setters)
}

class Sergeant extends Soldier {
    private int subordinates;

    public Sergeant(Soldier s) {
        super(s)
        this.rank = "Sergeant"; // overwrites this Sergeant's rank
        this.subordinates = 0;
    }
}

您可以轻松地将其封装在 promoteSoldierToSergeant 方法中。但是,如果具有许多属性的类设计得天真,或者需要基于地图的解决方法,这可能会导致构造函数伸缩。为了解决这个问题,我个人是Builder pattern 的忠实粉丝,但您也可以考虑Factory pattern

阅读Clonable interface 可能最好解决您关于“复制构造函数”的问题,但请注意differences between shallow and deep copies,以及对您的类和数据结构的影响。

【讨论】:

    【解决方案3】:

    我认为你的方法是完全可以接受的。如果你有一个对象 X,你想把它变成 Y,你可以在 Y 构造函数中做,复制所有必要的字段。

    您也可以使用构建器或静态工厂方法,但无论哪种方式,您都必须复制字段,因为 java 中没有自动复制构造函数(除非您使用诸如 lombok 之类的专用库,它可以提供来自注解的全参数构造函数)

    您担心内存中有 2 个对象。但是如果你删除了对原始 Soldier 的所有引用,垃圾收集器就会销毁它。

    最后一件事,正如@tsolakp 所提到的,让General 从 Soldier 继承是一个好的设计吗?难道不能只是一个“等级”变量,或者类似的东西,来反映这种状态吗?在组合就足够的情况下过度使用继承是一个常见的错误,并且会导致更少的麻烦。

    【讨论】:

    • 在这里使用组合可能是一个好方法,但是当我需要将这些对象映射到 DB 对象时可能会导致其他麻烦。此外,继承更好地反映了现实:我们可以说General也是Solider(与组合方法中的“General has a Solider”相反)。
    【解决方案4】:

    使用Reflections 可以实现您想要的。

    这样您可以自动将字段从父类实例复制到子类。

    您的代码将如下所示:

    public static void copyObject(Object src, Object dest)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, SecurityException {
            for (Field field : src.getClass().getFields()) {
                dest.getClass().getField(field.getName()).set(dest, field.get(src));
            }
        }
    
        public static General createGeneral (Solider solider, String devision) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
            General general = new General();
            copyObject(solider, general);
            general.setDevision(devision);
    
            return general;
        }
    

    Field 导入是 java.lang.reflect.Field;

    ================================================ ==========================

    另一种方法是使用Apache Bean Utils

    然后,你可以像这样使用它的 cloneBean(Object Bean) 方法:

    General general = cloneBean(solider);
    

    将字段从士兵复制到通用,然后复制特定于子类(通用)的所有字段。

    ================================================ ==========================

    编辑:如果您打算将父类 Solider 用于“普通”士兵,那么引入另一个用于“普通”士兵的子类也是明智的" 士兵(我想您是根据您注释的方法名称 promoteSoliderToGeneral(Solider s) 来做的。

    因此,例如,您将有一个名为 MilitaryMan 的父类和 3 个扩展它的子类:SoliderGeneral中士

    这样,你可以统一处理所有的MilitaryMan。并且,您可以通过以下方式检查 MilitaryManSoliderGeneral 还是 Sergeant

    if (militaryMan instanceOf Solider) {
       // do solider specific processing
       ...
    } else if (militaryMan instanceof General) {
       ...
    } else if (militaryMan instanceof Sergeant) {
       ...
    }
    

    我认为这样会更干净。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-12
      • 2017-07-14
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      • 2015-07-07
      • 1970-01-01
      • 2015-11-17
      相关资源
      最近更新 更多