您好,登錄后才能下訂單哦!
這篇文章主要介紹基于Spring使用工廠模式實現程序解耦的示例,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
1、 啥是耦合、解耦?
既然是程序解耦,那我們必須要先知道啥是耦合,耦合簡單來說就是程序的依賴關系,而依賴關系則主要包括
1、 類之間的依賴
2、 方法間的依賴
比如下面這段代碼:
public class A{ public int i; } public class B{ public void put(A a){ System.out.println(a.i); } }
上面這個例子中A類和B類之間存在一種強耦合關系,B類直接依賴A類,B類的put方法非A類類型不可,我們把這種情況叫做強耦合關系。
實際開發中應該做到:編譯期不依賴,運行時才依賴。怎么理解呢?我們很容易想到多態向上轉型,是的,編譯時不確定,運行時才確定,當然接觸面更廣一點的童鞋會想到接口回調,是的接口回調方式也能有效的解耦!如下代碼:
//一個接口叫做Inter,里面定義了一個happy()方法,有兩個類A、B實現了這個接口 interface Inter{ void happy(); } class A implements Inter{ @Override public void happy() { System.out.println("happy...A"); } } class B implements Inter{ @Override public void happy() { System.out.println("happy...B"); } } public class Test{ public void happys(Inter inter){ inter.happy(); } }
是的,如上代碼正是典型的接口回調,Test類中的happys方法參數變的相對靈活起來,代碼中Test類與A類、B類之間就存在一種弱耦合關系,Test類的happys方法的參數可以使A類類型也可以是B類類型,不像強耦合關系中非A類類型不可的情形。
從某一意義上來講使用類的向上轉型或接口回調的方式進行解耦都是利用多態的思想!
當然解耦的方式還有很多,從根本意義上講實現低耦合就是對兩類之間進行解耦,解除類之間的直接關系,將直接關系轉換成間接關系,從而也有很多設計模式也對程序進行解耦,比如:適配器模式、觀察者模式、工廠模式....總之,必須明確一點:耦合性強的程序獨立性很差!
2、 jdbc程序進行解耦
先來看一段代碼:
//1、注冊驅動 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //如果把jdbc的MySQLjar包依賴去除直接編譯失敗提示沒有mysql //2、獲取連接 Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/ufida","root","root"); //3、獲取操作數據庫的預處理對象 PreparedStatement pstm=conn.prepareStatement("select * from client"); //4、執行SQL,得到結果集 ResultSet rs=pstm.executeQuery(); //5\遍歷結果集 while(rs.next()){ System.out.println(rs.getString("name")); } //6、釋放資源 rs.close(); pstm.close(); conn.close();
等等等等,好熟悉好懷念的代碼.....
沒錯就是jdbc的代碼,不是用來懷舊的,而是如果這樣設計,你會覺得這樣的程序耦合性如何?又如何進行解耦?先仔細思考一番。
一分鐘過去了.....
兩分鐘過去了.....
好了,我們都知道jdbc連接MySQL需要一個mysql-connector的jar包,如果我們把這個jar包依賴或者這個jar包給去掉,顯然上面的這個程序會編譯報錯,如下圖
顯然這樣的程序耦合性過高!于是我們可以這樣設計,將第一步的注冊驅動代碼new的方式改成反射的方式如下:
//1、new的方式注冊驅動 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //如果把jdbc的MySQLjar包依賴去除直接編譯失敗提示沒有mysql相關的jar包 改為如下方式 //2、反射的方式注冊驅動 Class.forName("com.mysql.jdbc.Driver"); //改用這種方式注冊驅動會發現不會編譯失敗,相比上面的方式相對解耦,但是依然存在缺陷:若連接改為Oracle數據庫,這里的字符串又要進行改動!
正如注釋的解釋一樣,又一個缺陷就浮現了:若連接改為Oracle數據庫,這里的字符串又要進行改動!
于是對于這個jdbc程序來說就有這樣的一個解耦思路:
第一步:通過反射來創建對象,盡量避免使用new關鍵字
第二步:通過讀取配置文件來獲取創建的對象全限定類名
3、傳統dao、service、controller的程序耦合性
順著jdbc程序的解耦思路,我們再來看看傳統dao、service、controller的程序耦合性分析
由于只是一個demo,省去dao層的操作.....
定義一個Service接口
public interface IAccountOldService{ public void save(); }
Service接口實現類
public class AccountServiceOldImpl implements IAccountOldService{ @Override public void save() { System.out.println("save成功一個賬戶...."); } }
controller代碼:
public class AccountCencollertOld { public static void main(String[] args) { IAccountOldService iaccount=new AccountServiceOldImpl (); iaccount.save(); //運行結果:save成功一個賬戶.... } }
到這里,有何想法?表面上來看是沒有一點問題的,So Beautiful,但仔細的看。表現層與業務層、業務層與持久層緊緊的互相依賴關聯,這與我們開發程序的高內聚低耦合原則相違背,哦My God,So Bad!我們順著jdbc程序的解耦思路,我們應該盡量避免使用new關鍵字,我們發現這些層里面service層new 持久層dao,controller表現層new 業務層service....太糟糕了
那么對此,你有何解耦思路?
4、使用工廠模式實現解耦
別想了,工廠模式實現程序解耦你值得擁有!順著jdbc程序的解耦思路:
1、通過讀取配置文件來獲取創建的對象全限定類名
2、通過反射來創建對象,盡量避免使用new關鍵字
首先在resources目錄下中寫一個bean.properties配置類,具體內容如下
accountServiceOld=com.factory.service.impl.AccountServiceOldImpl
接著使用工廠方法代碼:
/** * 一個創建Bean對象的工廠 * * 1、需要一個配置文件來配置我們的service和dao 配置文件的內容:唯一標識=全限定類名(key-value) * 2、通過讀取配置文件中配置的內容,反射創建對象 * * 場景:主要是service調用dao,controller調用service的程序。這里面耦合性非常的高,互相new互相依賴 * * 為了解耦,利用工廠模式進行 */ public class BeanFactoryOld { private static Properties props; static{ try { //實例化對象 props = new Properties(); //獲取properties文件的流對象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in);//加載其對應路徑下的配置文件 }catch (Exception e){ throw new ExceptionInInitializerError("初始化properties失敗!"); } } //根據bean的名稱獲取bean對象 public static Object getBean(String beanName){ Object bean=null; try { String beanPath= props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); //這里的newInstance創建實例(默認無參構造器)每次執行都需要創建一次 } catch (Exception e) { e.printStackTrace(); } return bean; } }
此時,controller的代碼就可以編寫為
/** * 這里模擬一個controller調用service * */ public class AccountCencollertOld { public static void main(String[] args) { // IAccountOldService iaccount=new AccountServiceOldImpl (); //使用工廠方法不再通過new方式 IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld"); iaccount.save(); //運行結果:save成功一個賬戶.... 說明成功調用了service } }
通過運行結果,屬實沒毛病,成功降低程序耦合!So Beautiful!先高興一會吧,因為馬上出現.....但是,隨之而來的問題又出現了,我們對這個controller進行一下改寫
for(int i=0;i<5;i++){ IAccountOldService iaccount= (IAccountOldService) BeanFactoryOld.getBean("accountServiceOld"); iaccount.save(); }
打印結果:
com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... com.factory.service.impl.AccountServiceImpl@677327b6 save成功一個賬戶.... com.factory.service.impl.AccountServiceImpl@14ae5a5 save成功一個賬戶.... com.factory.service.impl.AccountServiceImpl@7f31245a save成功一個賬戶.... com.factory.service.impl.AccountServiceImpl@6d6f6e28 save成功一個賬戶....
打印的是五個不同的對象,說明是多例的,每次調用getBean的時候都會newInstance出一個新對象,如下
多例每次都要創建對象,資源浪費、效率低下
針對單例多例情況,我們再對service業務層代碼進行修改:
public class AccountServiceImpl implements IAccountService { //定義類成員 private int i=1; @Override public void save() { System.out.println("save成功一個賬戶...."); System.out.println(i); i++; } }
運行controller代碼,運行結果
save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1
why?多例因為每次都是新的對象,上面也驗證過了,因此每次創建新對象都會初始化一次,重新賦值,所以都是1,如果我們把類成員改為局部成員變量如下
public class AccountServiceOldImpl implements IAccountOldService { // private int i=1; @Override public void save() { int i=1; //改為局部變量 System.out.println("save成功一個賬戶...."); System.out.println(i); i++; } }
不用猜,運行結果同樣是1。算了還是運行一下吧哈哈哈
save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1 save成功一個賬戶.... 1
說了這么多,通過觀察service和dao之間單不單例好像無所謂,因為他們之間并沒有業務方法中改變的類成員,所以并不需要多例來保證線程安全。那說這些有何意義?不要忘了,由于使用了工廠改進如下中的.newInstance創建實例(默認無參構造器)每次執行都需要創建一次,這樣就不好了(浪費資源),因此我們要設計出只newInstance創建一次實例就很完美了,這也是我為啥要在service和controller中都添加一個Old關鍵字的原因了,接下來我們來看看工廠是如何改進的!
5、工廠模式改進
為了不被搞暈,我們重新寫代碼,也就是重頭開始寫代碼~其實就是把Old去掉~
由于只是一個demo,省去dao層的操作.....
定義一個Service接口
public interface IAccountService { public void save(); }
Service接口實現類
public class AccountServiceImpl implements IAccountService{ @Override public void save() { System.out.println("save成功一個賬戶...."); } }
controller代碼:
/** * 這里模擬一個controller調用service * */ public class AccountCencollert { public static void main(String[] args) { // IAccountService iaccount=new AccountServiceImpl(); IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService"); iaccount.save(); //運行結果:save成功一個賬戶.... 說明了成功調用了service } }
改進的工廠方法代碼:
public class BeanFactory { private static Properties props; //定義一個map容器,用于存放創建的對象 private static Map<String,Object> beans; //改進的代碼============ static{ try { //實例化對象 props = new Properties(); //獲取properties文件的流對象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in);//加載其對應路徑下的配置文件 ////////////////////以下是改進的代碼======================= //實例化容器 beans=new HashMap<String,Object>(); //取出配置文件中所有的key值 Enumeration<Object> keys = props.keys(); //遍歷枚舉 while(keys.hasMoreElements()){ //取出每個key String key = keys.nextElement().toString(); //根據key取出對應的value (這里因為每個value值對應著類路徑) String beanPath = props.getProperty(key); //反射創建對象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key,value); } }catch (Exception e){ throw new ExceptionInInitializerError("初始化properties失敗!"); } } //隨著代碼的改進,我們就可以簡化下面的獲取bean對象的方法,如下代碼 /** * 根據bean的名稱獲取對象(單例) */ public static Object getBean(String beanName){ //通過Map容器對應key來獲取對應對象 return beans.get(beanName); //這里通過Map容器中獲取,這樣就不會每次都創建一次實例! } //不再使用下面的方法 /* //根據bean的名稱獲取bean對象 public static Object getBean(String beanName){ Object bean=null; try { String beanPath= props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); //這里的newInstance創建實例(默認無參構造器)每次執行都需要創建一次,這樣就不好了 } catch (Exception e) { e.printStackTrace(); } return bean; }*/ }
從上面改進的工廠代碼,我們可以發現一開始就定義一個Map容器,用于存放創建的對象,為啥要先定義一個Map容器呢?用一個容器將這個實例裝起來,這是由于不把這個對象裝存起來的話,這個對象不使用很容易被GC掉,何況我們現在只使用這一個對象!
定義一個Map容器存放配置好的文件中的每個對象,之后我們就直接提供一個根據Map的key來取value的getBean方法,這樣不僅僅擴展了程序的配置文件的靈活性而且還保證了只產生一個對象,保證資源不浪費,So Beautiful !
那如何證明已經是單例的模式了呢?很簡單,如下設計一下service業務層、controller表現層代碼即可:
service業務層:添加一個類成員屬性,并在方法內部 i++;
public class AccountServiceImpl implements IAccountService { private int i=1; //類成員屬性 @Override public void save() { System.out.println("save成功一個賬戶...."); System.out.println(i); i++;//二次改革代碼 } }
controller表現層: 創建調用工廠5次創建對象的方法
/** * 這里模擬一個controller調用service * */ public class AccountCencollert { public static void main(String[] args) { for(int i=0;i<5;i++){ IAccountService iaccount= (IAccountService) BeanFactory.getBean("accountService"); System.out.println(iaccount); //打印的是五個不同的對象,說明是多例的 iaccount.save(); //會發現打印的i值都是1,并沒有自增成功 } }
運行代碼結果:
com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 2 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 3 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 4 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 5
發現,確實5個對象都是同一個,并且出現了改變類成員屬性的現象。
如果我們把類成員屬性改為局部成員屬性呢?
public class AccountServiceImpl implements IAccountService { @Override public void save() { int i=1; //局部成員屬性 System.out.println("save成功一個賬戶...."); System.out.println(i); i++; } }
運行結果
com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1 com.factory.service.impl.AccountServiceImpl@1540e19d save成功一個賬戶.... 1
看到這個結果,我們就能聯想到,之前為什么servlet中為啥要避免定義類成員,原因就在這里!多例情況下,就不會出現這種情況!!!!
以上是“基于Spring使用工廠模式實現程序解耦的示例”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。