java設計模式學習筆記(一)--- 建立型模式
文章目錄
簡介
建立型模式,主要用於輔助建立物件。有人說建立物件很簡單,只要new一下就好了,但是實際情況往往非常複雜。比如所建立的物件要經常修改、建立過程中有複雜的動態操作、對所建立的物件有特殊要求等等。這時就需要用到各種各樣的建立型設計模式。
設計模式所遵循的幾個原則
- 單一原則:一個物件只負責完成一個職責;高內聚,低耦合;
- 開閉原則:對擴充套件開放,對修改關閉;對類的改動通過增加程式碼實現,而不是通過修改程式碼;
- 里氏替換原則:任何父類物件都可使用子類進行替換;
- 依賴注入原則:依賴於抽象,不依賴於具體實現(面向介面程式設計)
- 介面分離原則:一個介面不要提供過多的行為
- 迪米特原則:一個物件對其他物件儘可能少的依賴(降低耦合)
- 多用組合,少用繼承原則:父類的任何改變可能直接影響子類的行為。
這是網上隨處可見的答案,也正是設計模式存在的原因。
並不需要特意去記這些,好用才是真理,不符合實際情況的遵守規則反而會寫出更糟糕的程式碼。
當然,是否好用,如何認清實際情況才是最難的,它需要非常多的程式碼量與思考才能真正掌握。
在學會這些之後,會發現,所謂規則不過是把自己一直在做的東西做了一個總結罷了,它不是一個指導規則,而是一個習慣。
一、工廠方法模式
出於方便檢視的考慮,我的程式碼會全部寫到一個java檔案中,並且會盡量刪除多餘的程式碼。
首先準備一下工廠所創建出的類。
interface IProduct{} //產品類介面
class Product1 implements IProduct{} //產品1
class Product2 implements IProduct{} //產品2
class Product3 implements IProduct{} //產品3
public class BlobFactoryTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
IProduct aa = new Product1();
System.out.println(aa.getClass().getSimpleName());
}
}
簡單工廠模式
public class BlobFactoryTest {
public static void main(String[] args) {
//IProduct aa = new Product1();
IProduct aa = SimpleFactory.getProduct("p1");
System.out.println(aa.getClass().getSimpleName());
}
}
class SimpleFactory{
public static IProduct getProduct(String type){
switch (type) {
case "p1":
return new Product1();
case "p2":
return new Product2();
case "p3":
return new Product3();
default:
return null;
}
}
}
簡單工廠模式有最差的規範性,分支多了後呼叫所需的字串很難記,還有寫錯的風險。
但是這是最簡單易懂的寫法。小功能,或者幾乎不需要擴充套件的功能可以一用。
工廠方法模式
在簡單工廠模式中,主要存在的問題有兩個
- 擴充套件時需要修改原先寫好的方法。(記住:任何程式碼在直接修改後都可能出現新的bug)
- 如果呼叫時,傳入的字串寫錯,會使程式出錯。
工廠方法模式解決了這兩個問題。
直接上程式碼
public class BlobFactoryTest {
public static void main(String[] args) {
//IProduct aa = new Product1();
// IProduct aa = SimpleFactory.getProduct("p1");
// System.out.println(aa.getClass().getSimpleName());
System.out.println(MethodFatory.getProduct1().getClass().getSimpleName());
System.out.println(MethodFatory.getProduct2().getClass().getSimpleName());
System.out.println(MethodFatory.getProduct3().getClass().getSimpleName());
}
}
class MethodFatory{
public static IProduct getProduct1(){
return new Product1();
}
public static IProduct getProduct2(){
return new Product2();
}
public static IProduct getProduct3(){
return new Product3();
}
}
輸出
Product1
Product2
Product3
現在,每一個產品類都由單獨的方法建立。擴充套件的時候無需修改原來的方法,只要繼續新增方法就好了。並且不會出現拼寫錯誤的情況。MethodFatory.getProduct123()
這種程式碼根本過不了編輯器的校驗。
工廠方法模式應該是最常用的建立方法。程式碼簡單,並且能應對大多數情況。
抽象工廠模式
名字挺唬人的,其實就是在工廠類上做了個多型的處理。
在工廠方法模式中,擴充套件時雖然不需要修改原來的方法,但是需要在這個工廠類中新增新的方法。這樣就是對這個工廠類產生了修改。嚴格來說,這也是不符合規範的。
在抽象工廠模式中,每新增一套新的產品類,就新建一個工廠類去處理。
public class BlobFactoryTest {
public static void main(String[] args) {
//IProduct aa = new Product1();
// IProduct aa = SimpleFactory.getProduct("p1");
// System.out.println(aa.getClass().getSimpleName());
// System.out.println(MethodFatory.getProduct1().getClass().getSimpleName());
// System.out.println(MethodFatory.getProduct2().getClass().getSimpleName());
// System.out.println(MethodFatory.getProduct3().getClass().getSimpleName());
IFactory factory;
factory = new Product1AbsFactory();
System.out.println(factory.getProduct().getClass().getSimpleName());
factory = new Product2AbsFactory();
System.out.println(factory.getProduct().getClass().getSimpleName());
factory = new Product3AbsFactory();
System.out.println(factory.getProduct().getClass().getSimpleName());
}
}
interface IFactory{ public IProduct getProduct();}
class Product1AbsFactory implements IFactory{
public IProduct getProduct() {
return new Product1();
}
}
class Product2AbsFactory implements IFactory{
public IProduct getProduct() {
return new Product2();
}
}
class Product3AbsFactory implements IFactory{
public IProduct getProduct() {
return new Product3();
}
}
輸出
Product1
Product2
Product3
使用抽象工廠模式可以避免對原有的程式碼進行修改,這是更符合規範的寫法,但是程式碼讀起來比較累,對於複雜一些的抽象工廠類,修改起來很不方便。當在一些小功能上用時,更是有種殺雞用牛刀的感覺。
工廠模式小結
簡單工廠模式、工廠方法模式、抽象工廠模式,這三種模式都是為了建立物件而存在的。在規範性上,依次遞增。但這並不能說明後面的就比前面的更好,需要根據實際情況來選擇使用哪種模式。
單例模式
有時候,對於一些類,我們只想讓它建立一次。產生這種情況的原因很多,比如這個類的例項化所消耗的資源很大,或者這個類在邏輯上就應該只例項化一次。
通過程式組內的口頭約定可以基本達成這樣的目的,但是這並不保險,正確的做法是在程式碼上杜絕這樣的可能性。
一個單例類的基本模式就是私有化構造方法,並提供一個返回自身例項的方法。
一種簡單的實現方法
package blog.java.pattern.creater;
public class Singleton_1 {
private static Singleton_1 obj = new Singleton_1();
private Singleton_1(){}
private static Singleton_1 getInstance(){
return obj;
}
}
其中可以根據情況將new Singleton_1();
放到靜態塊中。
這是最簡單的方法,不過有一個小缺陷,那就是在載入這個類的同時,obj = new Singleton_1();
就會被載入。有時需要做一個延遲載入的機制,可以用下面的方法實現。
public class Singleton_2{
private Singleton_2(){}
private static class SingletonBuild{
private static Singleton_2 in_obj = new Singleton_2();
}
private static Singleton_2 getInstance(){
return SingletonBuild.in_obj;
}
public static void main(String[] args) {
System.out.println(Singleton_2.getInstance());
}
}
在java中,類中的靜態物件會在類被載入後加載,而類會在第一次呼叫後加載。這一點對於內部靜態類也是一樣的。在類Singleton_2
被載入後,這句話private static Singleton_2 in_obj = new Singleton_2();
並不會被立即載入。它會在方法getInstance
被呼叫的時候再載入。這就起到了一個延遲載入的效果。
寫一個例子來驗證這一點
package blog.java.pattern.creater;
public class Singleton_2{
static{System.out.println("外部類載入");}
private Singleton_2(){}
private static class SingletonBuild{
static{System.out.println("內部類載入");}
private static Singleton_2 in_obj = new Singleton_2();
}
private static Singleton_2 getInstance(){
return SingletonBuild.in_obj;
}
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("blog.java.pattern.creater.Singleton_2");
Singleton_2.getInstance();
}
}
第三種寫法
public class Singleton_2{
static{System.out.println("外部類載入");}
private Singleton_2(){}
private static class SingletonBuild{
static{System.out.println("內部類載入");}
private static Singleton_2 in_obj = new Singleton_2();
}
private static Singleton_2 getInstance(){
return SingletonBuild.in_obj;
}
public static void main(String[] args) throws ClassNotFoundException {
// Class.forName("blog.java.pattern.creater.Singleton_2");
// Singleton_2.getInstance();
SingletonEnumTest.INSTANCE.aaa();
}
}
enum SingletonEnumTest{
INSTANCE;
public void aaa(){System.out.println("aaa");}
}
利用了enum 。這是很好地寫法,不過從來沒見過這麼寫的。
最後一種寫法:利用同步鎖來實現。不過我不想介紹這種寫法。它最難寫,最容易寫錯,所實現的功能卻是一樣的,我為什麼要用它?
單例模式小結
單例模式是目的性最強的一個設計模式,它通過java語言本身的一些特性來實現這個需求。我個人覺得,與其說它是一個模式,不如說它是一種技巧更容易讓人理解。
建造者模式
如果物件在建立的過程中存在多種複雜的操作,並且每種操作中存在著共性,可以考慮使用建造者模式。
它的實現方式簡單描述一下就是:客戶程式設計師通過一個導演類來建立物件,導演類需要外部接收建立規則。建立中的複雜操作全部在導演類中完成。也就是說,不管這個物件的建立有多複雜,我都可以不去理會。
package blog.java.pattern.creater.builder;
public class BuilderTest {
public static void main(String[] args) {
System.out.println(new Director(new Builder1()).getProduct());
}
}
interface IProduct{} //產品類介面
class Product1 implements IProduct{} //產品類
//規則介面
interface IBuilder{
public void doSomeThing1();
public void doSomeThing2();
}
//建立規則1
class Builder1 implements IBuilder{
public void doSomeThing1() {
System.out.println("Builder1 doSomeThing1");
}
public void doSomeThing2() {
System.out.println("Builder1 doSomeThing2");
}
}
//建立規則2
class Builder2 implements IBuilder{
public void doSomeThing1() {
System.out.println("Builder2 doSomeThing1");
}
public void doSomeThing2() {
System.out.println("Builder2 doSomeThing2");
}
}
//導演類
class Director{
IBuilder builder;
public Director(IBuilder builder){
this.builder = builder;
}
public IProduct getProduct(){
this.builder.doSomeThing1();
this.builder.doSomeThing2();
return new Product1();
}
}
原型模式
簡單來說,就是將一個物件複製。然後,沒了…
程式碼
public class Product implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
如果說單例模式是一連串的語法組成的技巧,那原型模式就只是一個語法。
在克隆物件的時候還是要考慮一下淺拷貝之類的問題的,不過這不在設計模式的討論範圍之內。
原型模式的使用範圍
基本就是在需要拷貝的時候用。
- 建立物件需要消耗許多額外的資源(比如需要查詢一個耗資源的sql)。
- 物件需要在多執行緒中使用。
- 這個物件在經過一連串的操作後,需要複製一份到別處使用,畢竟不可能重新模擬一遍已經做過的一連串操作吧。