2017-09-06 37 views
8

我已阅读并感悟到自己,实体(数据对象 - 对于JPA或序列号)在他们注射是一个坏主意。这是我目前的设计(所有适当的领域都有getter和setter方法,以及serialVersionUID我跌幅为简洁)。如何更改设计以便实体不使用注射?

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

public class State implements Serializable { 

    List<AbstractCar> cars = new ArrayList<>(); 

    List<AbstractPlane> 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<Engine> engines = new ArrayList<>(); // Engine is another pojo 
} 

// etc. 

相反,每个具体型号的汽车都需要拥有一些行为也是数据的一些具体形式的经理:

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 < 0) 
      return manager.operate(data); 
     else 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. 

CarxManager@Stateless beans对数据执行操作(匹配CarxData )给予他们。他们自己进一步使用其他许多豆类的注射剂,它们都是AbstractCarManager的子类。有O(100)种车型和配套管理人员。

序列化State时的问题是,序列化抽象汽车的名单不与子类打针打得很好。我正在寻找一种能够将注入从数据保存过程中分离出来的设计。

我以前的相关问题:How to serialize an injected bean?How can I tell the CDI container to "activate" a bean?

+0

管理员网络由CDI或EJB构建,是吗?如果每种车型都有匹配的经理,为什么不让经理进行序列化呢?如果经理级别比汽车级别少,汽车级别以外的任何人都必须知道如何将经理与汽车连接起来,这是一种工厂,应该在连载过程中调用工厂并将经理放入汽车。如果汽车是相互连接的对象,管理儿童或邻居,而不是管理者,访问者应该处理创建,而另一位访问者应该处理操作功能 – aschoerk

+0

@aschoerk它如何提供帮助? 1.汽车豆仍然需要序列化,并包括注入。 2.管理者不知道实体树,他们的目的是操纵数据。 3.我正在序列化“国家” - 管理人员如何参与其中?每辆汽车有一位经理,汽车不相关。 – Mark

+0

听起来像你已经意识到你原来的问题是[XY问题]的例子(https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem),但在很大程度上这一个也是。你仍然关注依赖注入问题,而不是看你想解决的更大问题。在类名中使用诸如“Manager”和“Data”之类的词,并使用JPA将序列化类存储为字节数组,这是一个强烈的暗示,即您的整个解决方案正在吠叫错误的树(在我看来)。 –

回答

2

一种可能性是删除属性,所以它不会被序列化被拾起。这可以通过编程获得。

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

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

而且可能是使用JPA的@Transient工作:

@Inject 
@Transient 
Car2Manager manager; 

我没有测试了这个,所以它可能无法工作。

+0

因此,在第一个解决方案中,我将删除注入和内部操作,我会添加该行,是吗?我知道JPA'@ Transient',但现在我没有在该类中使用JPA。 – Mark

+0

@马克是的,正确的移动它在操作。 –

0

什么是切入点? 这是一个Web应用程序,休息服务,肥皂服务或事件调度程序?

注入框架几乎总是分开的数据和服务。数据总是POJO,绝对不包含业务逻辑。在这里,假设这是休息服务,我将执行以下操作:

public class SSOApplication { 

    public class State implements Serializable { 

     List<AbstractCar> cars = new ArrayList<>(); 

     List<AbstractPlane> 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<Engine> 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<T extends CarData> { 

     void operate(T car, int index); 

     default boolean canHandle(T carData) { 
      final TypeToken<T> token = new TypeToken<T>(getClass()) { 
      }; 

      return token.getType() == carData.getClass(); 
     } 
    } 

    @ApplicationScoped 
    public static class Car1Manager implements CarManager<Car1Data> { 

     public void operate(Car1Data car, int index) { 
     } 
    } 

    @ApplicationScoped 
    public static class Car2Manager implements CarManager<Car2Data> { 

     public void operate(Car2Data car, int index) { 
     } 
    } 

    @ApplicationScoped 
    public static class CarService { 

     @Any 
     @Inject 
     private Instance<CarManager<?>> 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); 
     } 
    } 
} 
+0

这是JAX-RS。为什么SSOApplication的所有类都是内部类? – Mark

+0

我只是把它们放在一起,以便我可以复制粘贴 – maress

+0

为什么'@ ApplicationScoped'?它不会比'@ Stateless'更糟吗?每次需要处理操作时,所有管理人员都可以通过流式传输来查找匹配结果,但效率不高。 – Mark

8

您可以使用存储库模式。将您的业务逻辑放入一个服务中,然后将存储库(将持久性机制抽象出来)和管理器注入到该服务中。存储库隐藏了业务服务中的持久性实现细节,实体仅仅是简单的POJO。

它看起来有点像下面的Foo是实体栏的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); 

    } 
} 

参见:Repository Pattern Step by Step Explanationhttp://deviq.com/repository-pattern/。一些框架例如Spring数据JPA或deltaspike已经实现了存储库模式适合你,你需要做的是提供如下所示的界面,他们在后台生成的实现:

@Repository 
public interface CarRepository extends EntityRepository<Car, UUID> {} 

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

要找到一个很好的解决了这个问题倒是有很多不同的因素,其中有许多是非常大的话题与他们写的很多书,但我会尽我所能来说明基于这些我的思维来解决上述问题。

并抱歉,因为我毫不怀疑您知道其中的许多这些,但为了清楚起见,我将承担有限的知识。

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

模型驱动开发

在这个问题给出的模型中,我们有一个叫做国家,其中包含AbstractPlanesAbstractCars东西。您正在使用JPA来坚持,这实际上是您的飞机和汽车的集合。首先在软件中调用任何一个状态是一种难闻的气味,因为几乎所有的事情都有某种状态,但是调用我们在这里的合计状态更不合理。

状态如何区别?是一个状态和另一部分状态的一部分是否是所有飞机和汽车属于状态的单个实例。这种情况下飞机和汽车之间的关系是什么?飞机列表和列表中的汽车与单个实体有什么关系?

那么,如果国家竟是机场和我们有兴趣在许多飞机和汽车是如何目前在地面上,那么这可能是正确的模式。如果是一个机场,它将有一个名称或身份,如其机场代码,但它不会,所以......

...在这种情况下,看起来状态是一个对象,它被用来方便我们访问对象模型。 因此,我们通过实施考虑来有效地推动我们的模型,反过来我们应该反过来推动我们的模型实现。

条款像CarData也是出于同样的原因问题,建立一个汽车实体,然后一个单独的对象来存储其数据混乱和困惑。

未能正确获得模型会导致软件最多混淆,最坏的情况是完全无法使用。这是造成IT项目失败的最大原因之一,而这个项目越是越难做对。


修订后的示范

因此,从模式,我明白,我们有汽车我们有飞机,它们的实例都用自己独特的身份的实体。在我看来,它们是独立的事物,因此将它们包裹在某个整体实体中没有意义。

public class Plane {...} 

public class Car {...} 

另一个考虑是在模型中使用抽象类的,一般我们要应用有利于组成了继承因为继承会导致隐藏的行为,它可以使一个模型难以阅读的原则。例如,为什么我们有一个ProperllerPlane和一个EnginePlane?当然,螺旋桨只是一种发动机?我已经大大简化了模型:

public class Plane implements Serializable { 

    @Id 
    private String name; 
    private String model; 
    private List<Engine> engines; 

平面是一个具有自己的属性和身份的实体。没有必要增加额外的类,它们在现实世界中不存在仅仅用于存储属性的内容。该引擎对象是目前代表在平面上使用的发动机的类型的枚举:跟踪

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<Engine> engines = new ArrayList<Engine>(); 
     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<Engine> engines = new ArrayList<Engine>(); 
     engines.add(PROPELLER); 
     spitFire.setEngines(engines); 
     spitFire.setModel("Spitfire"); 
     spitFire.setName(name); 
     return spitFire; 
    } 
} 

我们也在这里做的是分离出关切根据单一职责原则每类只应该真正做到一件事。


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

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

void operate(int i) {..} 

通过构建代码这种方式,您去耦从它们是如何持续(资料库),从该在你的问题中提到对它们进行操作的服务模型(实体和值对象):

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

+0

我同意这是最好的方法。有关此设计模式的非常详细的概述,请查看伟大的Eric Evans书籍Domain Driven Design(https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)。 – 6006604

+0

我不明白这个答案。你能展示它如何与我的课程一起工作吗? – Mark

+0

嗨马克,如果你更换汽车吧,那么你基本上有它,如果我这周得到时间,我会尝试添加更多的细节。 –

0

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

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

我介绍了一些内在的服务,将在分享帮助操作和使用Car1Manager它。您的AbstractCar类当然也会丢失它的操作方法,因为从现在开始您的服务将处理它。因此,现在叫car1.operate(I),你将不得不作出通过服务这样一个电话:

public class SampleCar1ServiceUsage{ 
    @Inject 
    Car1InnerService car1InnerService; 

    public void carManipulator(List<Car1> 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介绍儿童类似的功能(甚至提取一些抽象如果有必要,比如AbsractCarInnerService,它将定义操作方法或某些接口,如果你不想在其中任何其他固体方法,将做相同的操作)。然而,这个答案仍然与@Justin Cooke的答案有关,我认为你应该检查他在帖子中提到的那些模式。

相关问题