如何改变设计,使实体不使用注射?

我已经阅读并开始意识到自己的实体(数据对象 – 用于JPA或序列化)注入其中是一个坏主意。 这是我当前的设计(所有相应的字段都有getter和setter,以及为了简洁而放弃的serialVersionUID )。

这是父对象,它是实体组合图的头部。 这是我序列化的对象。

 public class State implements Serializable { List cars = new ArrayList(); List planes = new ArrayList(); // other objects similar to AbstractPlane as shown below } 

AbstractPlane及其子类只是没有注入的简单类:

 public abstract class AbstractPlane implements Serializable { long serialNumber; } public class PropellorPlane extends AbstractPlane { int propellors; } public class EnginePlane extends AbstractPlane { List engines = new ArrayList(); // Engine is another pojo } // etc. 

相比之下,每种具体类型的汽车都需要一个管理员来保存一些行为以及一些特定forms的数据:

 public abstract class AbstractCar implements Serializable { long serialNumber; abstract CarData getData(); abstract void operate(int condition); abstract class CarData { String type; int year; } } public class Car1 extends AbstractCar { @Inject Car1Manager manager; Car1Data data = new Car1Data(); // (getter exists per superclass requirement) void operate(int i) { // logic looks weird but makes the example if (i  1) return manager.operate(data, i); } class Car1Data extends CarData { int property1; { type = "car1"; year = 1; } } } public class Car2 extends AbstractCar { @Inject Car2Manager manager; Car2Data data = new Car2Data(); void operate(int i) { if (i < 31) return manager.operate(data); } class Car2Data extends CarData { char property2; { type = "car2"; year = 12; } } } // etc. 

CarxManagerCarxManager bean, CarxData给予它们的数据(匹配的CarxData )执行操作。 他们自己进一步使用许多其他bean的注入,它们都是AbstractCarManager子类。 有O(100)车型和匹配经理。

序列化State时的问题是序列化抽象汽车列表与子类中的注入不相符。 我正在寻找一种将注射与数据保存过程分离的设计。

我以前的相关问题: 如何序列化注入的bean? 如何告诉CDI容器“激活”一个bean?

可能是删除属性,因此序列化程序不会拾取它。 这可以通过编程方式实现。

 private Car2Manager getCar2Manager() { CDI.current().select(Car2Manager.class).get(); } 

认为这是一个干净的解决方案,但它应该是一个可行的“解决方案”

也可能使用JPA的@Transient

 @Inject @Transient Car2Manager manager; 

我没有测试过这个,所以它可能不起作用。

您可以使用存储库模式。 将业务逻辑放入服务中,并将存储库(将抽象机制抽象化)和管理器注入其中。 存储库隐藏了业务服务中的持久性实现细节,而实体只是简单的POJO。

它看起来像下面的Foo是实体Bar的id:

 public class CarService { @Inject CarRepository carRepository; @Inject CarManager manager; piblic void operate(final Foo foo) { Bar myBar = carRepository.retrieve(foo); manager.doSomethingTo(myBar); carRepository.persist(myBar); } } 

另请参阅: 存储库模式逐步说明 , http://deviq.com/repository-pattern/ 。 一些框架,如Spring Data JPA或deltaspike已经为您实现了存储库模式,您需要做的就是提供如下所示的接口,并在后台生成实现:

 @Repository public interface CarRepository extends EntityRepository {} 

标记回答您的更多细节请求我将提供一个改进的解决方案,因为问题中的示例对我来说真的没有意义,并且展示了一些导致有问题的软件的反模式。

找到问题的一个很好的解决方案涉及很多不同的考虑因素,其中很多是非常大的主题,有许多关于它们的书籍,但我会尽力说明我的思想,以解决上述问题。

并且道歉,因为我毫不怀疑你知道其中许多,但为了清楚起见,我将假设有限的知识。

解决这个问题的第一步不是关于代码,而是关于模型本身,模型驱动开发在Eric Evan的书中被广泛涵盖,如下面的评论所述。 该模型应该驱动实现,并且应该作为分层体系结构的一部分存在于其自己的层上,并且由实体,值对象和工厂组成。

模型驱动开发

在问题中给出的模型中,我们有一个名为State的东西,它包含AbstractPlanesAbstractCars 。 您正在使用JPA来保持状态 ,这实际上是您的飞机和汽车的总和。 首先在软件中调用任何状态都是一种难闻的气味,因为几乎所有东西都具有某种状态,但是调用我们在这里所拥有的东西是一种聚合状态甚至没有意义。

一个国家与另一个国家有何不同? 一辆汽车是一个国家的一部分,是另一个国家的另一部分,还是所有飞机和汽车都属于一个国家的情况 。 在这种情况下,飞机和汽车之间的关系是什么? 飞机列表和汽车列表如何与单个国家实体有任何关系?

好吧,如果State实际上是一个机场而且我们对当前有多少架飞机和汽车感兴趣,那么这可能是正确的型号。 如果国家是一个机场,它将有一个名称或身份,如机场代码,但它没有,所以……

…在这种情况下,似乎State是一个对象,它被用作方便我们访问对象模型的对象。 因此,我们应该通过实施考虑有效地推动我们的模型,当我们应该反过来并从我们的模型推动我们的实现时。

CarData这样的术语出于同样的原因也存在问题,创建Car实体然后单独的对象来存储其数据是混乱和混乱的。

未能使模型正确导致软件最好混淆,最糟糕的是完全不起作用。 这是IT项目失败的最大原因之一,项目越大,这项工作就越难以实现。


修订模型

所以从模型中我了解到我们有汽车 ,我们有飞机 ,其中的实例都是具有自己身份的独特实体。 在我看来,它们是分开的东西,因此坚持将它们包含在某些聚合实体中是毫无意义的。

 public class Plane {...} public class Car {...} 

另一个考虑因素是在模型中使用抽象类,通常我们希望应用有利于组合而不是inheritance原则,因为inheritance可能导致隐藏的行为,并且它可能使模型难以阅读。 例如,为什么我们有ProperllerPlaneEnginePlane ? 当然,螺旋桨只是一种发动机? 我大大简化了模型:

 public class Plane implements Serializable { @Id private String name; private String model; private List engines; 

Plane是一个具有自己的属性和身份的实体。 没有必要在现实世界中仅存储属性的其他类。 引擎对象当前是表示平面中使用的引擎类型的枚举:

 public enum Engine { PROPELLER, JET } 

如果引擎本身需要身份,就像在现实生活中引擎序列号和事物被跟踪一样,那么我们会将其更改为对象。 但我们可能不希望允许访问它,除非通过Plane实体实例,在这种情况下,Plane将被称为聚合根 – 这是一个高级主题,我会推荐Evan的书来获取有关聚合的更多详细信息。

Car实体也是如此。

 @Entity public class Car implements Serializable{ @Id private String registration; private String type; private int year; 

以上是您在模型基础上提供的所有内容。 然后我创建了几个工厂类来处理这些实体的实例创建:

 public class CarFactory { public Car makePosrche(final String registrationNumber) { Car porsche = new Car(); porsche.setRegistration(registrationNumber); porsche.setType("Posrshe"); porsche.setYear(1986); return porsche; } } public class PlaneFactory { public Plane makeSevenFourSeven(final String name) { Plane sevenFourSeven = new Plane(); List engines = new ArrayList(); engines.add(JET); engines.add(JET); engines.add(JET); engines.add(JET); sevenFourSeven.setEngines(engines); sevenFourSeven.setName(name); return sevenFourSeven; } public Plane makeSpitFire(final String name) { Plane spitFire = new Plane(); List engines = new ArrayList(); engines.add(PROPELLER); spitFire.setEngines(engines); spitFire.setModel("Spitfire"); spitFire.setName(name); return spitFire; } } 

我们在这里所做的是根据单一责任原则将问题分离出来,每个class级应该只做一件事。


现在我们有了一个模型,我们需要知道如何与它进行交互。 在这种情况下,我们很可能如果使用JPA将汽车保持在一个名为Car和飞机的表中。 我们将通过存储库,CarRepository和PlaneRespository提供对这些持久化实体的访问。

然后,您可以创建名为services的类,这些类会注入存储库(以及您需要的任何其他内容)以对汽车和飞机的实例执行CRUD(创建读取更新删除)操作,这也是您可以将业务逻辑应用于这些操作的点。 比如你的方法:

 void operate(int i) {..} 

通过以这种方式构建代码,您可以将模型(实体和值对象)与它们如何持久存储(存储库)以及如您在问题中提到的对其进行操作的服务分离:

我正在寻找一种将注射与数据保存过程分离的设计。

什么是切入点? 这是一个Web应用程序,一个rest服务,一个soap服务,还是一个调度程序事件?

注入框架几乎总是将数据和服务分开。 数据始终是POJO,完全不包含业务逻辑。 在这里,假设这是一个rest服务,我将执行以下操作:

 public class SSOApplication { public class State implements Serializable { List cars = new ArrayList<>(); List planes = new ArrayList<>(); // other objects similar to AbstractPlane as shown below } public abstract class AbstractPlane implements Serializable { long serialNumber; } public class PropellorPlane extends AbstractPlane { int propellors; } public class EnginePlane extends AbstractPlane { List engines = new ArrayList<>(); // Engine is another pojo } public abstract class AbstractCar implements Serializable { long serialNumber; abstract CarData getData(); } public static class CarData { String type; int year; } public class Car2Data extends CarData { char property2; { type = "car2"; year = 12; } } public static class Car1Data extends CarData { int property1; { type = "car1"; year = 1; } } public static class Car1 extends AbstractCar { @Override CarData getData() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } public static class Car2 extends AbstractCar { @Override CarData getData() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } public static interface CarManager { void operate(T car, int index); default boolean canHandle(T carData) { final TypeToken token = new TypeToken(getClass()) { }; return token.getType() == carData.getClass(); } } @ApplicationScoped public static class Car1Manager implements CarManager { public void operate(Car1Data car, int index) { } } @ApplicationScoped public static class Car2Manager implements CarManager { public void operate(Car2Data car, int index) { } } @ApplicationScoped public static class CarService { @Any @Inject private Instance> carManagers; public void operate(int index, AbstractCar car) { final CarData carData = car.getData(); final CarManager carManager = carManagers.stream() .filter((mng) -> mng.canHandle(carData)) .findFirst() .orElse(IllegalArgumentException::new); carManager.operate(carData, index); } } } 

如果你可以改变你的流量,也许你可以这样做:

 class Car1InnerService { @Inject Car1Manager manager; void operate(int i, Car1 car) { if (i < 0) return manager.operate(car.getData()); else if (i > 1) return manager.operate(car.getData(), i); } } } 

我介绍了一些内部服务,它将在Car1上运行并使用Car1Manager。 您的AbstractCar类当然也会丢失它的操作方法,因为从现在起您的服务将处理它。 所以现在不必调用car1.operate(i),你必须通过这样的服务进行调用:

 public class SampleCar1ServiceUsage{ @Inject Car1InnerService car1InnerService; public void carManipulator(List carlist){ int i = 0; //I don't know why you need this param therefore i just increment it for(Car1 car: carlist){ car1InnerService.operate(i, car); i++; } } } 

当然,您应该为每个其他AbsractCar子项引入类似的function(如果需要,甚至可以提取一些抽象,例如AbsractCarInnerService,它将定义操作方法或某些接口,如果您不想要任何其他固体方法,它将执行相同操作) 。 然而,这个答案仍然与@Justin Cooke的答案有某种关系,在我看来你绝对应该检查他在post中提到的那些模式。