Java最佳實踐和建議:設計模式
設計模式是軟體開發過程中經常出現的問題的常見解決方案。這些解決方案提供了優雅且在大多數情況下解決與物件建立,資源分配,簡化程式碼等相關的不同問題的最有效方法。需要維護它們的上下文,而解決方案本身需要定製,根據業務邏輯。
設計模式分為三類:
- 創造性,提供解決方案來解決在物件建立過程中發生的不同問題
- 結構化,通過找到如何在更大的結構中組合類的方法,為例項化問題提供解決方案
- 行為,為程式碼的不同部分之間的通訊中發生的問題提供解決方案。
DAO模式
在架構設計過程中,一些設計模式實際上可以用作指導,就像DAO設計模式的情況一樣。軟體體系結構通常有三層:應用程式的端點,服務層,即業務邏輯和資料層。
資料層是使用DAO設計模式(資料訪問物件)實現的,該模式將與資料庫通訊的部分與應用程式的其餘部分分開。DAO模式定義了所有實體的CRUD(建立,讀取,更新,刪除)操作。通過新增將經常用於實體本身的命名/本機查詢,可以完全分離持久層。
<b>public</b> <b>interface</b> DAO<T,E <b>extends</b> Serializable>{ <b>public</b> T save(T object); <b>public</b> Boolean delete(T object); <b>public</b> T update(T object); <b>public</b> T find(E id); }
DAO的介面本身僅定義了需要在實現中指定的操作。實現本身使用提供的實體管理器的泛型型別。實體管理器是一個負責應用程式中所有永續性操作的類,可以使用應用程式上下文獲取。
<b>public</b> <b>abstract</b> <b>class</b> GenericDAO<T,E> implements DAO<T,E>{ @PersistenceContext <b>private</b> EntityManager entityManager; <b>public</b> T save(T object){ <b>return</b> entityManager.persist(object); } <b>public</b> T find(E id){ <b>return</b> entityManager.find(T.<b>class</b>,id); } <b>public</b> Boolean delete(T object){ <b>return</b> entityManager.remove(object); } <b>public</b> T update(T object){ <b>return</b> entityManager.merge(object); } }
提供的示例需要基本瞭解Hibernate和Java的永續性。Hibernate是一個ORM工具(物件關係對映),它從java程式碼建立表,並使用HQL(休眠查詢語言)進行查詢輸入和執行。
@Entity @Table(name=<font>"person"</font><font>) @NamedQueries ( { @NamedQuery(name=Person.GET_PERSON_BY_AGE,query=</font><font>"Select * from User u where u.age>:age</font><font>") } ) <b>public</b> <b>class</b> Person{ <b>public</b> <b>static</b> <b>final</b> String GET_PERSON_BY_AGE = </font><font>"Person.getPersonByAge"</font><font>; @Id @GeneratedValue( strategy = GenerationType.IDENTITY) @Column(name=</font><font>"id"</font><font>,unique=</font><font>"true"</font><font>) <b>public</b> <b>int</b> id; @Column(name=</font><font>"name"</font><font>) <b>public</b> String name; <b>public</b> Person(String name){ <b>this</b>.name=name; } </font><font><i>//getters and setters...</i></font><font> } </font>
將用於實體的DAO類擴充套件了通用DAO,其中實現了基本的CRUD操作,因此我們只需要新增將要使用的特定查詢。
<b>public</b> PersonDAO <b>extends</b> GenericDAO<Person,Integer>{ <b>public</b> List<Person> getPersonByAge(<b>int</b> age){ Query q=entityManager.createNamedQuery(Person.GET_PERSON_BY_AGE, Person.<b>class</b>); q.setParameter(<font>"age"</font><font>,5); <b>return</b> (List<Person>)q.getResultList(); } } </font>
優點:
- 提供程式碼與業務邏輯的邏輯和物理分離,易於實現;
- 可以使用快取策略輕鬆擴充套件DAO類,可以在方法中實現;
- 如果將DAO類宣告為EJB,則每個方法都可以指定事務屬性,以便控制底層事務的範圍;
缺點:
- 它會在與資料庫的連線中產生開銷,因為DAO物件通常會處理整個物件。當涉及到儲存操作時,這是一個優點,因為整個物件一次儲存但是讀取可能是昂貴的操作;
- 為了避免這種情況,可以使用本機或命名查詢,以便根據業務需要檢索物件的較小部分;
- DAO模式不應該在小型應用程式中使用,因為它的優點很小,而且程式碼會變得更復雜;
工廠模式
設計模式通常用於簡化大塊程式碼,甚至可以隱藏應用程式流中的特定實現。這類問題的完美示例是工廠設計模式,它是一種創造性設計模式,無需指定物件的確切類別即可提供物件建立。它建議使用從超類繼承的超類和多個子類。在執行期間,僅使用超類,其值因工廠類而異。
<b>public</b> <b>class</b> Car{ <b>private</b> String model; <b>private</b> <b>int</b> numberOfDoors; <b>public</b> Car(){ } <b>public</b> String getModel(){ <b>return</b> <b>this</b>.model; } <b>public</b> <b>int</b> getNumberOfDoors(){ <b>return</b> <b>this</b>.numberOfDoors; } <b>public</b> <b>void</b> setModel(String model){ <b>this</b>.model = model; } <b>public</b> <b>void</b> setNumberOfDoors(<b>int</b> n){ <b>this</b>.numberOfDoors = n; } } <b>public</b> <b>class</b> Jeep <b>extends</b> Car{ <b>private</b> <b>boolean</b> land; <b>public</b> Jeep(){ } <b>public</b> <b>void</b> setLand(<b>boolean</b> land){ <b>this</b>.land=land; } <b>public</b> <b>boolean</b> getLand(){ <b>return</b> <b>this</b>.land; } } <b>public</b> <b>class</b> Truck <b>extends</b> Car{ <b>private</b> <b>float</b> capacity; <b>public</b> Truck(){ } <b>public</b> <b>void</b> setCapacity(<b>float</b> capacity){ <b>this</b>.capacity=capacity; } <b>public</b> <b>float</b> getCapacity(){ <b>return</b> <b>this</b>.capacity; } }
為了使用這種模式,我們需要實現一個工廠類,它將為給定的輸入返回正確的子類。上面的java類指定了一個超類(Car.java)和兩個子類(Truck.java和Jeep.java)。在我們的實現中,我們例項化Car類的一個物件,並且根據引數,工廠類將決定它是Jeep還是Truck。
<b>public</b> <b>class</b> CarFactory{ <b>public</b> Car getCarType(<b>int</b> numberOfDoors, String model,Float capacity, Boolean land){ Car car=<b>null</b>; <b>if</b>(capacity!=<b>null</b>){ car=<b>new</b> Jeep(); <font><i>//implement setters</i></font><font> }<b>else</b>{ car=<b>new</b> Truck(); </font><font><i>//implement setters</i></font><font> } <b>return</b> car; } } </font>
在執行時,工廠類考慮輸入例項化正確的子類。
<b>public</b> <b>static</b> <b>void</b> main(String [] args){ Car c = <b>null</b>; CarFactory carFactory = <b>new</b> CarFactory(); c = carFactory.getCarType(2,“BMW”,<b>null</b>,<b>true</b>); }
抽象工廠
抽象工廠設計模式以相同的方式工作,但父類不是常規類,而是一個抽象類。抽象類通常更快,更容易例項化,因為它們基本上是空的。實現是相同的,只有父類被宣告為抽象及其所有方法,並且子類需要實現抽象類中宣告的方法的行為。
Abstract工廠的示例是使用介面建立的。通過簡單地用抽象類替換介面可以完成同樣的操作,而不是實現介面,子類將擴充套件抽象類。
<b>public</b> <b>interface</b> Car { <b>public</b> String getModel(); <b>public</b> Integer getNumberOfDoors(); <b>public</b> String getType(); } <b>public</b> <b>class</b> Jeep implements Car{ <b>private</b> String model; <b>private</b> Integer numberOfDoors; <b>private</b> Boolean isLand; <b>public</b> Jeep() {} <b>public</b> Jeep(String model, Integer numberOfDoors, Boolean isLand){ <b>this</b>.model = model; <b>this</b>.numberOfDoors = numberOfDoors; <b>this</b>.isLand = isLand; } <b>public</b> String getModel(){ <b>return</b> model; } <b>public</b> Integer getNumberOfDoors() { <b>return</b> numberOfDoors; } <b>public</b> Boolean isLand() { <b>return</b> isLand; } <b>public</b> <b>void</b> setLand(Boolean isLand) { <b>this</b>.isLand = isLand; } <b>public</b> <b>void</b> setModel(String model) { <b>this</b>.model = model; } <b>public</b> <b>void</b> setNumberOfDoors(Integer numberOfDoors){ <b>this</b>.numberOfDoors = numberOfDoors; } <b>public</b> String getType(){ <b>return</b> <font>"jeep"</font><font>; } <b>public</b> <b>class</b> Truck implements Car{ <b>private</b> String model; <b>private</b> Integer numberOfDoors; <b>private</b> Integer numberOfWheels; <b>public</b> Truck(String model, Integer numberOfDoors, Integer numberOfWheels) { <b>this</b>.model = model; <b>this</b>.numberOfDoors = numberOfDoors; <b>this</b>.numberOfWheels = numberOfWheels; } <b>public</b> Truck() {} <b>public</b> String getModel() { <b>return</b> model; } <b>public</b> Integer getNumberOfDoors() { <b>return</b> numberOfDoors; } <b>public</b> Integer getNumberOfWheels() { <b>return</b> numberOfWheels; } <b>public</b> <b>void</b> setNumberOfWheels(Integer numberOfWheels) { <b>this</b>.numberOfWheels = numberOfWheels; } <b>public</b> <b>void</b> setModel(String model) { <b>this</b>.model = model; } <b>public</b> <b>void</b> setNumberOfDoors(Integer numberOfDoors) { <b>this</b>.numberOfDoors = numberOfDoors; } <b>public</b> String getType(){ <b>return</b> </font><font>"truck"</font><font>; } } <b>public</b> <b>class</b> CarFactory { <b>public</b> CarFactory(){} <b>public</b> Car getCarType(String model,Integer numberOfDoors, Integer numberOfWheels, Boolean isLand){ <b>if</b>(numberOfWheels==<b>null</b>){ <b>return</b> <b>new</b> Jeep(model,numberOfDoors,isLand); }<b>else</b>{ <b>return</b> <b>new</b> Truck(model,numberOfDoors,numberOfWheels); } } } </font>
唯一的區別是抽象類中宣告的方法必須在每個子類中實現。在這兩種情況下,工廠和主要方法都保持不變。
<b>public</b> <b>class</b> CarMain { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { Car car=<b>null</b>; CarFactory carFactory=<b>new</b> CarFactory(); car=carFactory.getCarType(<font>"Ford"</font><font>, <b>new</b> Integer(4), <b>null</b>, <b>new</b> Boolean(<b>true</b>)); System.out.println(car.getType()); } } </font>
優點:
- 它允許鬆散耦合和更高級別的抽象;
- 它是可擴充套件的,可用於將某些實現與應用程式分開;
- 通過簡單地新增適當的例項化邏輯,可以在層次結構中建立新類之後重用工廠類,並且程式碼仍然可以工作。
- 單元測試,因為使用超類可以很容易地覆蓋所有場景;
缺點:
- 它往往太抽象,難以理解;
- 瞭解何時實現工廠設計模式非常重要,因為在小型應用程式中,它只會在物件建立期間建立開銷(更多程式碼);
- 工廠設計模式必須保持其上下文,即只有從同一父類繼承或實現相同介面的類才適用於工廠設計模式。
singleton單例模式
這個設計模式是最有名的和有爭議的造物設計模式之一。單例類是一個類,它將在應用程式的生命週期中僅例項化一次,即只有一個物件共享所有資源。單例方法是執行緒安全的,並且可以由應用程式的多個部分同時使用,即使它們訪問Singleton類中的共享資源也是如此。關於何時使用單例類的完美示例是記錄器實現,其中所有資源都在同一日誌檔案中寫入並且是執行緒安全的。其他示例包括資料庫連線和共享網路資源。
此外,每當應用程式需要從伺服器讀取檔案時,使用Singleton類就很方便,因為在這種情況下,只有應用程式的一個物件才能訪問儲存在伺服器上的檔案。除了記錄器實現之外,配置檔案是使用單例類有效的另一個示例。
在java中,singleton是一個帶有私有建構函式的類。單例類使用類本身的例項保留一個欄位。該物件是使用get方法建立的,如果尚未啟動例項,則呼叫建構函式。早些時候,我們提到過這種模式最具爭議性,因為例項生成的多個實現。它必須是執行緒安全的,但它也必須是高效的。在示例中,我們有兩個解決方案。
<b>import</b> java.nio.file.Files; <b>import</b> java.nio.file.Paths; <b>public</b> <b>class</b> LoggerSingleton{ <b>private</b> <b>static</b> Logger logger; <b>private</b> String logFileLocation=<font>"log.txt"</font><font>; <b>private</b> PrintWriter pw; <b>private</b> FileWriter fw; <b>private</b> BufferedWriter bw; <b>private</b> Logger(){ fw = <b>new</b> FileWriter(logFileLocation, <b>true</b>); bw = <b>new</b> BufferedWriter(fw) <b>this</b>.pw = <b>new</b> PrintWriter(bw); } <b>public</b> <b>static</b> synchronised Logger getLogger(){ <b>if</b>(<b>this</b>.logger==<b>null</b>){ logger=<b>new</b> Logger(); } <b>return</b> <b>this</b>.logger; } <b>public</b> <b>void</b> write(String txt){ pw.println(txt); } } </font>
因為將經常訪問日誌檔案。使用緩衝寫入器的列印編寫器確保檔案不會多次開啟和關閉。
第二個實現包括一個私有類,它包含Singleton類例項的靜態欄位。私有類只能在單例類中訪問,即只能從get方法訪問。
<b>public</b> <b>class</b> Logger{ <b>private</b> <b>static</b> <b>class</b> LoggerHolder(){ <b>public</b> <b>static</b> Singleton instance=<b>new</b> Singleton(); } <b>private</b> Logger(){ <font><i>// init</i></font><font> } <b>public</b> <b>static</b> Logger getInstance(){ <b>return</b> LoggerHolder.instance; } } </font>
然後可以從app中的任何其他類使用單例類:
Logger log=Logger.getInstance(); log.write(<font>"something"</font><font>); </font>
優點:
- 單例類只在應用程式的生命週期中例項化一次,並且可以多次使用;
- singleton類允許執行緒安全訪問共享資源;
- 單例類不能擴充套件,如果正確實現,即get方法應該是同步和靜態的,它是執行緒安全的;
- 建議首先建立一個介面,然後設計單例類本身,因為它更容易測試介面;
缺點:
- 測試期間的問題,當單例類訪問共享資源並且測試的執行很重要時;
- 單例類還隱藏了程式碼中的一些依賴項,即建立未明確建立的依賴項;
- 使用沒有工廠模式的單例的問題在於它打破了單一責任原則,因為類正在管理自己的生命週期;
Builder模式
生成器模式也是建立模式,它允許對複雜物件的增量建立。當欄位設定需要複雜操作或僅僅欄位列表太長時,建議使用此模式。該類的所有欄位都儲存在私有內部類中
<b>public</b> <b>class</b> Example{ <b>private</b> String txt; <b>private</b> <b>int</b> num; <b>public</b> <b>static</b> <b>class</b> ExampleBuilder{ <b>private</b> String txt; <b>private</b> <b>int</b> num; <b>public</b> ExampleBuilder(<b>int</b> num){ <b>this</b>.num=num; } <b>public</b> ExampleBuilder withTxt(String txt){ <b>this</b>.txt=txt; <b>return</b> <b>this</b>; } <b>public</b> Example build(){ <b>return</b> <b>new</b> Example(num,txt); } } <b>private</b> Example(<b>int</b> num,String txt){ <b>this</b>.num=num; <b>this</b>.txt=txt; } }
在實際情況中,引數列表將更長,並且可以基於其他輸入計算類的一些引數。構建器類與類本身具有相同的欄位,並且必須將其宣告為靜態才能訪問,而無需例項化持有者類的物件(在本例中為Example.java)。上面給出的實現是執行緒安全的,可以通過以下方式使用:
Example example=<b>new</b> ExampleBuilder(10).withTxt(<font>"yes"</font><font>).build(); </font>
優點:
- 如果類中的引數數量大於6或7,則程式碼更加整潔和可重用;
- 在設定所有需要的欄位之後建立物件,並且只有完全建立的物件可用;
- 構建器模式隱藏構建器類中的一些複雜計算,並將其與應用程式流分離;
缺點:
- 構建器類必須包含原始類中的所有欄位,因此與單獨使用類相比,可能需要更多的時間來開發;
觀察模式
觀察 設計模式是一種行為設計模式,它通過將某些實體傳播到應用程式的相關部分來觀察某些實體並處理這些更改。每個容器可以為不同的設計模式提供不同的實現,並且觀察者模式在java中使用介面Observer來實現,該介面將受到觀察者類中的更改的影響。另一方面,觀察到的類需要實現Observable介面。Observer介面只有update方法,但在Java 9中已棄用,因為它的簡單性不建議使用它。它沒有提供有關更改內容的詳細資訊,只是在較大的物件中查詢更改可能是一項代價高昂的操作。