10.Java设计模式 工厂模式,单例模式
Java 之工廠方法和抽象工廠模式
1. 概念
? ? 工廠方法:一抽象產(chǎn)品類派生出多個(gè)具體產(chǎn)品類;一抽象工廠類派生出多個(gè)具體工廠類;每個(gè)具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例。
? ? 即定義一個(gè)創(chuàng)建對(duì)象的接口(即抽象工廠類),讓其子類(具體工廠類)決定實(shí)例化哪一個(gè)類(具體產(chǎn)品類)。“一對(duì)一”的關(guān)系。
?
? ? 抽象工廠:多個(gè)抽象產(chǎn)品類,派生出多個(gè)具體產(chǎn)品類;一個(gè)抽象工廠類,派生出多個(gè)具體工廠類;每個(gè)具體工廠類可創(chuàng)建多個(gè)具體產(chǎn)品類的實(shí)例。
? ? 即提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無需指定他們的具體的類。“一對(duì)多”的關(guān)系。
2. UML
?工廠方法:
?
?
抽象工廠:
3. 代碼
工廠方法:
public interface Product { }public interface Creator {public Product factory(); }public class ConcreteProduct1 implements Product {public ConcreteProduct1(){System.out.println("ConcreteProduct1被創(chuàng)建");} }public class ConcreteProduct2 implements Product {public ConcreteProduct2(){System.out.println("ConcreteProduct2被創(chuàng)建");}}public class ConcreteCreator1 implements Creator {public Product factory(){return new ConcreteProduct1();} }public class ConcreteCreator2 implements Creator {public Product factory(){return new ConcreteProduct2();} }public class Client {private static Creator creator1, creator2;private static Product prod1, prod2;public static void main(String[] args){creator1 = new ConcreteCreator1();prod1 = creator1.factory();System.out.println("----------------------------");creator2 = new ConcreteCreator2();prod2 = creator2.factory();} }抽象工廠:
//定義不同的產(chǎn)品之間的一定具備的標(biāo)準(zhǔn),用interface實(shí)現(xiàn) //其中的method()方法可看作提取出不同產(chǎn)品的共性,如手機(jī)都有類似的功能 interface IProductA{ public void method(); } interface IProductB{ public void method(); } //實(shí)現(xiàn)了產(chǎn)品標(biāo)準(zhǔn)實(shí)現(xiàn)的一系列具體產(chǎn)品 //由于已經(jīng)設(shè)計(jì)好A1由廠商1生產(chǎn),故以下輸出代碼有“廠商x” class ProductA1 implements IProductA{ public void method() { System.out.println("廠商A1 生產(chǎn)ProductA1 ..."); } } class ProductA2 implements IProductA{ public void method() { System.out.println("廠商A2 生產(chǎn)ProductA2 ..."); } } class ProductB1 implements IProductB{ public void method() { System.out.println("廠商B1 生產(chǎn)ProductB1 ..."); } } class ProductB2 implements IProductB{ public void method() { System.out.println("廠商B2 生產(chǎn)ProductB2 ..."); } } //每一種牌子的產(chǎn)品生產(chǎn)工廠,即不同的廠商負(fù)責(zé)自己牌子產(chǎn)品的生產(chǎn) abstract class Factory1{ abstract IProductA getProductA1(); abstract IProductB getProductB1(); } abstract class Factory2{ abstract IProductA getProductA2(); abstract IProductB getProductB2(); } //具體的工廠用來生產(chǎn)相關(guān)的產(chǎn)品 class ConcreteFactory1 extends Factory1{ public IProductA getProductA1() { return new ProductA1(); } public IProductB getProductB1() { return new ProductB1(); } } class ConcreteFactoryB extends Factory2{ public IProductA getProductA2() { return new ProductA2(); } public IProductB getProductB2() { return new ProductB2(); } } //測試類 public class Client { public static void main(String[] args) { //廠商1負(fù)責(zé)生產(chǎn)產(chǎn)品A1、B1 Factory1 factory1 = new ConcreteFactory1(); IProductA productA1 = factory1.getProductA1(); IProductB productB1 = factory1.getProductB1(); productA1.method(); productB1.method(); //廠商2負(fù)責(zé)生產(chǎn)產(chǎn)品A2、B2 Factory2 factory2 = new ConcreteFactoryB(); IProductA productA2 = factory2.getProductA2(); IProductB productB2 = factory2.getProductB2(); productA2.method(); productB2.method(); } }?
?
?
4. 應(yīng)用場景
工廠方法:
在以下情況下,適用于工廠方法模式:
(1) 當(dāng)一個(gè)類不知道它所必須創(chuàng)建的對(duì)象的類的時(shí)候。
(2) 當(dāng)一個(gè)類希望由它的子類來指定它所創(chuàng)建的對(duì)象的時(shí)候。
(3) 當(dāng)類將創(chuàng)建對(duì)象的職責(zé)委托給多個(gè)幫助子類中的某一個(gè),并且你希望將哪一個(gè)幫助子類是代理者這一信息局部化的時(shí)候。
抽象工廠:
(1) 一個(gè)系統(tǒng)不應(yīng)當(dāng)依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、組合和表達(dá)的細(xì)節(jié),這對(duì)于所有形態(tài)的工廠模式都是重要的。
(2) 這個(gè)系統(tǒng)有多于一個(gè)的產(chǎn)品族,而系統(tǒng)只消費(fèi)其中某一產(chǎn)品族。
(3) 同屬于同一個(gè)產(chǎn)品族的產(chǎn)品是在一起使用的,這一約束必須在系統(tǒng)的設(shè)計(jì)中體現(xiàn)出來。
(4) 系統(tǒng)提供一個(gè)產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于實(shí)現(xiàn)。
?
Java 之單例模式
?
一.問題引入
偶然想想到的如果把Java的構(gòu)造方法弄成private,那里面的成員屬性是不是只有通過static來訪問呢;如果構(gòu)造方法是private的話,那么有什么好處呢;如果構(gòu)造方法是private的話,會(huì)不更好的封裝該內(nèi)呢?我主要是應(yīng)用在使用普通類模擬枚舉類型里,后來發(fā)現(xiàn)這就是傳說中的單例模式。構(gòu)造函數(shù)弄成private 就是單例模式,即不想讓別人用new 方法來創(chuàng)建多個(gè)對(duì)象,可以在類里面先生成一個(gè)對(duì)象,然后寫一個(gè)public static方法把這個(gè)對(duì)象return出去。(eg:public 類名 getInstancd(){return 你剛剛生成的那個(gè)類對(duì)象;}),用static是因?yàn)槟愕臉?gòu)造函數(shù)是私有的,不能產(chǎn)生對(duì)象,所以只能用類名調(diào)用,所有只能是靜態(tài)函數(shù)。成員變量也可以寫getter/setter供外界訪問的。
?第一個(gè)代碼不是單例模式,也就是說不一定只要構(gòu)造方法是private的就是單例模式。
class A(){private A(){}public name;pulbic static A creatInstance(){return new A();}}A a = A.createInstance(); a.name; //name 屬性 public class single{ private static final single s=new single(); private single(){ } public static single getInstance(){ return s; } }二.單例模式概念及特點(diǎn)
java中單例模式是一種常見的設(shè)計(jì)模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
單例模式有一下特點(diǎn):
1、單例類只能有一個(gè)實(shí)例。
2、單例類必須自己自己創(chuàng)建自己的唯一實(shí)例。
3、單例類必須給所有其他對(duì)象提供這一實(shí)例。
單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對(duì)象、對(duì)話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο?/span>常被設(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
正是由于這個(gè)特 點(diǎn),單例對(duì)象通常作為程序中的存放配置信息的載體,因?yàn)樗鼙WC其他對(duì)象讀到一致的信息。例如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫或 文件中,這些配置數(shù)據(jù)由某個(gè)單例對(duì)象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對(duì)象如果要獲取這些配置信息,只需訪問該單例對(duì)象即可。這種方式極大地簡化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場景的不同,也可能帶來一些同步問題。
?
public class Singleton {private static Singleton uniqueInstance = null;private Singleton() {// Exists only to defeat instantiation. }public static Singleton getInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;}// Other methods... }Singleton通過將構(gòu)造方法限定為private避免了類在外部被實(shí)例化,在同一個(gè)虛擬機(jī)范圍內(nèi),Singleton的唯一實(shí)例只能通過getInstance()方法訪問。(事實(shí)上,通過Java反射機(jī)制是能夠?qū)嵗瘶?gòu)造方法為private的類的,那基本上會(huì)使所有的Java單例實(shí)現(xiàn)失效。此問題在此處不做討論,姑且掩耳盜鈴地認(rèn)為反射機(jī)制不存在。)
但是以上實(shí)現(xiàn)沒有考慮線程安全問題。所謂線程安全是指:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。或者說:一個(gè)類或者程序所提供的接口對(duì)于線程來說是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。顯然以上實(shí)現(xiàn)并不滿足線程安全的要求,在并發(fā)環(huán)境下很可能出現(xiàn)多個(gè)Singleton實(shí)例。
public class TestStream {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;} //該類只能有一個(gè)實(shí)例private TestStream(){} //私有無參構(gòu)造方法//該類必須自行創(chuàng)建//有2種方式/*private static final TestStream ts=new TestStream();*/private static TestStream ts1=null;//這個(gè)類必須自動(dòng)向整個(gè)系統(tǒng)提供這個(gè)實(shí)例對(duì)象public static TestStream getTest(){if(ts1==null){ts1=new TestStream();}return ts1;}public void getInfo(){System.out.println("output message "+name);}} public class TestMain {public static void main(String [] args){TestStream s=TestStream.getTest();s.setName("張孝祥");System.out.println(s.getName());TestStream s1=TestStream.getTest();s1.setName("張孝祥");System.out.println(s1.getName());s.getInfo();s1.getInfo();if(s==s1){System.out.println("創(chuàng)建的是同一個(gè)實(shí)例");}else if(s!=s1){System.out.println("創(chuàng)建的不是同一個(gè)實(shí)例");}else{System.out.println("application error");}}}結(jié)論:由結(jié)果可以得知單例模式為一個(gè)面向?qū)ο蟮膽?yīng)用程序提供了對(duì)象惟一的訪問點(diǎn),不管它實(shí)現(xiàn)何種功能,整個(gè)應(yīng)用程序都會(huì)同享一個(gè)實(shí)例對(duì)象。
其次,下面是單例的三種實(shí)現(xiàn)。
1.餓漢式單例類
下面這個(gè)可以不加final,因?yàn)殪o態(tài)方法只在編譯期間執(zhí)行一次初始化,也就是只會(huì)有一個(gè)對(duì)象。
//餓漢式單例類.在類初始化時(shí),已經(jīng)自行實(shí)例化 public class Singleton1 {//私有的默認(rèn)構(gòu)造子private Singleton1() {}//已經(jīng)自行實(shí)例化 private static final Singleton1 single = new Singleton1();//靜態(tài)工廠方法 public static Singleton1 getInstance() {return single;}}2.懶漢式單例類
那個(gè)if判斷確保對(duì)象只創(chuàng)建一次。
//懶漢式單例類.在第一次調(diào)用的時(shí)候?qū)嵗?public class Singleton2 {//私有的默認(rèn)構(gòu)造子private Singleton2() {}//注意,這里沒有final private static Singleton2 single=null;//靜態(tài)工廠方法 public synchronized static Singleton2 getInstance() {if (single == null) { single = new Singleton2();} return single;}}3.登記式單例類
import java.util.HashMap; import java.util.Map;//登記式單例類.//類似Spring里面的方法,將類名注冊(cè),下次從里面直接獲取。public class Singleton3 {private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();static{Singleton3 single = new Singleton3();map.put(single.getClass().getName(), single);}//保護(hù)的默認(rèn)構(gòu)造子protected Singleton3(){}//靜態(tài)工廠方法,返還此類惟一的實(shí)例public static Singleton3 getInstance(String name) {if(name == null) {name = Singleton3.class.getName();System.out.println("name == null"+"--->name="+name);}if(map.get(name) == null) {try {map.put(name, (Singleton3) Class.forName(name).newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}return map.get(name);}//一個(gè)示意性的商業(yè)方法public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) {Singleton3 single3 = Singleton3.getInstance(null);System.out.println(single3.about());}}四.單例對(duì)象作配置信息管理時(shí)可能會(huì)帶來的幾個(gè)同步問題
1.在多線程環(huán)境下,單例對(duì)象的同步問題主要體現(xiàn)在兩個(gè)方面,單例對(duì)象的初始化和單例對(duì)象的屬性更新。
本文描述的方法有如下假設(shè):
a. 單例對(duì)象的屬性(或成員變量)的獲取,是通過單例對(duì)象的初始化實(shí)現(xiàn)的。也就是說,在單例對(duì)象初始化時(shí),會(huì)從文件或數(shù)據(jù)庫中讀取最新的配置信息。
b. 其他對(duì)象不能直接改變單例對(duì)象的屬性,單例對(duì)象屬性的變化來源于配置文件或配置數(shù)據(jù)庫數(shù)據(jù)的變化。
1.1單例對(duì)象的初始化
首先,討論一下單例對(duì)象的初始化同步。單例模式的通常處理方式是,在對(duì)象中有一個(gè)靜態(tài)成員變量,其類型就是單例類型本身;如果該變量為null,則創(chuàng)建該單例類型的對(duì)象,并將該變量指向這個(gè)對(duì)象;如果該變量不為null,則直接使用該變量。
這種處理方式在單線程的模式下可以很好的運(yùn)行;但是在多線程模式下,可能產(chǎn)生問題。如果第一個(gè)線程發(fā)現(xiàn)成員變量為null,準(zhǔn)備創(chuàng)建對(duì)象;這是第二 個(gè)線程同時(shí)也發(fā)現(xiàn)成員變量為null,也會(huì)創(chuàng)建新對(duì)象。這就會(huì)造成在一個(gè)JVM中有多個(gè)單例類型的實(shí)例。如果這個(gè)單例類型的成員變量在運(yùn)行過程中變化,會(huì) 造成多個(gè)單例類型實(shí)例的不一致,產(chǎn)生一些很奇怪的現(xiàn)象。例如,某服務(wù)進(jìn)程通過檢查單例對(duì)象的某個(gè)屬性來停止多個(gè)線程服務(wù),如果存在多個(gè)單例對(duì)象的實(shí)例,就 會(huì)造成部分線程服務(wù)停止,部分線程服務(wù)不能停止的情況。
1.2單例對(duì)象的屬性更新
通常,為了實(shí)現(xiàn)配置信息的實(shí)時(shí)更新,會(huì)有一個(gè)線程不停檢測配置文件或配置數(shù)據(jù)庫的內(nèi)容,一旦發(fā)現(xiàn)變化,就更新到單例對(duì)象的屬性中。在更新這些信 息的時(shí)候,很可能還會(huì)有其他線程正在讀取這些信息,造成意想不到的后果。還是以通過單例對(duì)象屬性停止線程服務(wù)為例,如果更新屬性時(shí)讀寫不同步,可能訪問該 屬性時(shí)這個(gè)屬性正好為空(null),程序就會(huì)拋出異常。
下面是解決方法
?
//單例對(duì)象的初始化同步 public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance == null) {syncInit();}return instance;}public Vector getProperties() {return properties;}}這種處理方式雖然引入了同步代碼,但是因?yàn)檫@段同步代碼只會(huì)在最開始的時(shí)候執(zhí)行一次或多次,所以對(duì)整個(gè)系統(tǒng)的性能不會(huì)有影響。
單例對(duì)象的屬性更新同步。
參照讀者/寫者的處理方式,設(shè)置一個(gè)讀計(jì)數(shù)器,每次讀取配置信息前,將計(jì)數(shù)器加1,讀完后將計(jì)數(shù)器減1.只有在讀計(jì)數(shù)器為0時(shí),才能更新數(shù)據(jù),同時(shí)要阻塞所有讀屬性的調(diào)用。
代碼如下:
public class GlobalConfig {private static GlobalConfig instance;private Vector properties = null;private boolean isUpdating = false;private int readCount = 0;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance == null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance==null) {syncInit();}return instance;}public synchronized void update(String p_data) {syncUpdateIn();//Update properties }private synchronized void syncUpdateIn() {while (readCount > 0) {try {wait();} catch (Exception e) {}}}private synchronized void syncReadIn() {readCount++;}private synchronized void syncReadOut() {readCount--;notifyAll();}public Vector getProperties() {syncReadIn();//Process data syncReadOut();return properties;}}采用"影子實(shí)例"的辦法具體說,就是在更新屬性時(shí),直接生成另一個(gè)單例對(duì)象實(shí)例,這個(gè)新生成的單例對(duì)象實(shí)例將從數(shù)據(jù)庫或文件中讀取最新的配置信息;然后將這些配置信息直接賦值給舊單例對(duì)象的屬性。
public class GlobalConfig {private static GlobalConfig instance = null;private Vector properties = null;private GlobalConfig() {//Load configuration information from DB or file//Set values for properties }private static synchronized void syncInit() {if (instance = null) {instance = new GlobalConfig();}}public static GlobalConfig getInstance() {if (instance = null) {syncInit();}return instance;}public Vector getProperties() {return properties;}public void updateProperties() {//Load updated configuration information by new a GlobalConfig objectGlobalConfig shadow = new GlobalConfig();properties = shadow.getProperties();}}注意:在更新方法中,通過生成新的GlobalConfig的實(shí)例,從文件或數(shù)據(jù)庫中得到最新配置信息,并存放到properties屬性中。上面兩個(gè)方法比較起來,第二個(gè)方法更好,首先,編程更簡單;其次,沒有那么多的同步操作,對(duì)性能的影響也不大。
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/tingbogiu/p/5852903.html
總結(jié)
以上是生活随笔為你收集整理的10.Java设计模式 工厂模式,单例模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Job 逻辑执行图
- 下一篇: Java基础-绘图技术