2017-09-06 74 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的答案有關,我認爲你應該檢查他在帖子中提到的那些模式。

相關問題