1. 程式人生 > >java設計模式學習筆記(一)--- 建立型模式

java設計模式學習筆記(一)--- 建立型模式

文章目錄

簡介

建立型模式,主要用於輔助建立物件。有人說建立物件很簡單,只要new一下就好了,但是實際情況往往非常複雜。比如所建立的物件要經常修改、建立過程中有複雜的動態操作、對所建立的物件有特殊要求等等。這時就需要用到各種各樣的建立型設計模式。

設計模式所遵循的幾個原則

  1. 單一原則:一個物件只負責完成一個職責;高內聚,低耦合;
  2. 開閉原則:對擴充套件開放,對修改關閉;對類的改動通過增加程式碼實現,而不是通過修改程式碼;
  3. 里氏替換原則:任何父類物件都可使用子類進行替換;
  4. 依賴注入原則:依賴於抽象,不依賴於具體實現(面向介面程式設計)
  5. 介面分離原則:一個介面不要提供過多的行為
  6. 迪米特原則:一個物件對其他物件儘可能少的依賴(降低耦合)
  7. 多用組合,少用繼承原則:父類的任何改變可能直接影響子類的行為。

這是網上隨處可見的答案,也正是設計模式存在的原因。
並不需要特意去記這些,好用才是真理,不符合實際情況的遵守規則反而會寫出更糟糕的程式碼。
當然,是否好用,如何認清實際情況才是最難的,它需要非常多的程式碼量與思考才能真正掌握。
在學會這些之後,會發現,所謂規則不過是把自己一直在做的東西做了一個總結罷了,它不是一個指導規則,而是一個習慣。

一、工廠方法模式

出於方便檢視的考慮,我的程式碼會全部寫到一個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;
		}
	}
}

簡單工廠模式有最差的規範性,分支多了後呼叫所需的字串很難記,還有寫錯的風險。
但是這是最簡單易懂的寫法。小功能,或者幾乎不需要擴充套件的功能可以一用。

工廠方法模式

在簡單工廠模式中,主要存在的問題有兩個

  1. 擴充套件時需要修改原先寫好的方法。(記住:任何程式碼在直接修改後都可能出現新的bug)
  2. 如果呼叫時,傳入的字串寫錯,會使程式出錯。

工廠方法模式解決了這兩個問題。
直接上程式碼

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

}

如果說單例模式是一連串的語法組成的技巧,那原型模式就只是一個語法。
在克隆物件的時候還是要考慮一下淺拷貝之類的問題的,不過這不在設計模式的討論範圍之內。

原型模式的使用範圍

基本就是在需要拷貝的時候用。

  1. 建立物件需要消耗許多額外的資源(比如需要查詢一個耗資源的sql)。
  2. 物件需要在多執行緒中使用。
  3. 這個物件在經過一連串的操作後,需要複製一份到別處使用,畢竟不可能重新模擬一遍已經做過的一連串操作吧。