如何更改设计以使实体不使用注入?

How to alter the design so that entities don't use injections?

我已经阅读并意识到自己在实体(数据对象 - 用于 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 bean,它们对提供给它们的数据(匹配的 CarxData)执行操作。他们自己进一步使用许多其他 bean 的注入,并且他们都是 AbstractCarManager 的 sub类。有O(100)种车型和匹配的经理。

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

我之前的相关问题: and How can I tell the CDI container to "activate" a bean?

一种可能性是删除 属性,这样它就不会被序列化程序拾取。这可以通过以编程方式获取它来实现。

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

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

使用 JPA 也可能有效 @Transient:

@Inject
@Transient
Car2Manager manager;

我没有测试过这个,所以它可能行不通。

切入点是什么? 这是 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);
        }
    }
}

您可以使用存储库模式。将您的业务逻辑放入服务中,并将存储库(它抽象了持久性机制)和管理器注入其中。存储库对业务服务隐藏了持久性实现细节,实体只是简单的 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);

    }
}

另请参阅:Repository Pattern Step by Step Explanation, http://deviq.com/repository-pattern/。 Spring Data JPA 或 deltaspike 等一些框架已经为您实现了存储库模式,您需要做的就是提供如下所示的接口,它们会在后台生成实现:

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

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

要找到一个好的解决方案涉及到很多不同的考虑,其中很多是非常大的话题,有很多关于它们的书,但我会尽力根据这些来说明我的想法来解决以上问题。

很抱歉,我相信你知道其中的很多,但为了清楚起见,我将假设知识有限。

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

模型驱动开发

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

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

好吧,如果 State 实际上是一个 Airport 并且我们想知道目前地面上有多少架飞机和汽车,那么这可能是正确的模型。如果 State 是一个机场,它将有一个名称或标识,例如机场代码,但它没有,所以...

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

CarData 这样的术语也是有问题的,因为同样的原因,创建一个 Car 实体然后一个单独的对象来存储它的数据是混乱和混乱的。

未能获得正确的模型会导致软件充其量是混乱的,最坏的情况是完全无法运行。这是 IT 程序失败的最大原因之一,项目越大,这些东西就越难正确。


修改后的模型

所以从模型中我了解到我们有 CarsPlanes,它们的实例都是具有自己的唯一实体身份。在我看来,它们是独立的东西,因此将它们包裹在某个聚合实体中是没有意义的。

public class Plane {...}

public class Car {...}

另一个考虑是在模型中使用抽象classes,一般我们希望应用优先组合而不是继承的原则,因为继承会导致隐藏行为,它会使模型难以阅读。例如,为什么我们有一个 ProperllerPlane 和一个 EnginePlane?螺旋桨肯定只是一种发动机吗?我大大简化了模型:

public class Plane implements Serializable {

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

位面是一个有自己属性和身份的实体。不需要额外的 classes 来代表现实世界中的任何东西,只是为了存储属性。引擎对象目前是一个枚举,表示飞机使用的引擎类型:

public enum Engine {
    PROPELLER, JET
}

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

汽车实体也是如此。

@Entity
public class Car implements Serializable{

    @Id
    private String registration;
    private String type;
    private int year;

以上是问题中提供的模型基础所需的全部内容。然后我创建了几个工厂 classes 来处理这些实体实例的创建:

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

我们在这里所做的也是根据单一职责原则分离关注点,每个class应该只做一件事。


现在我们有了一个模型,我们需要知道如何与之交互。在这种情况下,我们很可能会使用 JPA 将 Cars 持久化在一个名为 Car 和 Planes 的 table 中。我们将通过存储库、CarRepository 和 PlaneRespository 提供对这些持久化实体的访问。

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

void operate(int i) {..}

通过以这种方式构建您的代码,您可以将模型(实体和值对象)从它们的持久化方式(存储库)中分离出来,从对它们进行操作的服务中分离出来,如您的问题所述:

I'm looking for a design that decouples the injection from the data saving process.

如果你能改变你的流程,也许你可以做这样的事情:

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 class 当然也会失去它的 operate 方法,因为从现在开始您的服务将处理它。所以现在不是调用 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,它会定义 operate 方法或一些接口,它会做如果您不想在其中使用任何其他可靠的方法,则相同)。然而,这个答案仍然与@Justin Cooke 的答案有某种关系,在我看来,你绝对应该检查他在 post.

中提到的那些模式。