Spring中如何使用工廠模式實現程序解耦?

@java

一、 啥是耦合、解耦?

既然是程序解耦,那咱們必需要先知道啥是耦合,耦合簡單來講就是程序的依賴關係,而依賴關係則主要包括mysql

一、 類之間的依賴
二、 方法間的依賴spring

好比下面這段代碼:sql

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類類型不可的情形。設計模式

從某一意義上來說使用類的向上轉型或接口回調的方式進行解耦都是利用多態的思想!安全

固然解耦的方式還有不少,從根本意義上講實現低耦合就是對兩類之間進行解耦,解除類之間的直接關係,將直接關係轉換成間接關係,從而也有不少設計模式也對程序進行解耦,好比:適配器模式、觀察者模式、工廠模式....總之,必須明確一點:耦合性強的程序獨立性不好!併發

二、 jdbc程序進行解耦

先來看一段代碼:

//一、註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //若是把jdbc的MySQLjar包依賴去除直接編譯失敗提示沒有mysql    
//二、獲取鏈接
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/ufida","root","root");
//三、獲取操做數據庫的預處理對象
PreparedStatement pstm=conn.prepareStatement("select * from client");
//四、執行SQL,獲得結果集
ResultSet rs=pstm.executeQuery();
//5\遍歷結果集
while(rs.next()){
    System.out.println(rs.getString("name"));
}
//六、釋放資源
rs.close();
pstm.close();
conn.close();

等等等等,好熟悉好懷念的代碼.....

沒錯就是jdbc的代碼,不是用來懷舊的,而是若是這樣設計,你會以爲這樣的程序耦合性如何?又如何進行解耦?先仔細思考一番。

一分鐘過去了.....
兩分鐘過去了.....

好了,咱們都知道jdbc鏈接MySQL須要一個mysql-connector的jar包,若是咱們把這個jar包依賴或者這個jar包給去掉,顯然上面的這個程序會編譯報錯,以下圖
在這裏插入圖片描述
顯然這樣的程序耦合性太高!因而咱們能夠這樣設計,將第一步的註冊驅動代碼new的方式改爲反射的方式以下:

//一、new的方式註冊驅動
DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //若是把jdbc的MySQLjar包依賴去除直接編譯失敗提示沒有mysql相關的jar包

改成以下方式

 //二、反射的方式註冊驅動
Class.forName("com.mysql.jdbc.Driver"); //改用這種方式註冊驅動會發現不會編譯失敗,相比上面的方式相對解耦,可是依然存在缺陷:若鏈接改成Oracle數據庫,這裏的字符串又要進行改動!

正如註釋的解釋同樣,又一個缺陷就浮現了:若鏈接改成Oracle數據庫,這裏的字符串又要進行改動!

因而對於這個jdbc程序來講就有這樣的一個解耦思路:

第一步:經過反射來建立對象,儘可能避免使用new關鍵字
第二步:經過讀取配置文件來獲取建立的對象全限定類名

三、傳統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....太糟糕了

那麼對此,你有何解耦思路?

四、使用工廠模式實現解耦

別想了,工廠模式實現程序解耦你值得擁有!順着jdbc程序的解耦思路:

一、經過讀取配置文件來獲取建立的對象全限定類名
二、經過反射來建立對象,儘可能避免使用new關鍵字

首先在resources目錄下中寫一個bean.properties配置類,具體內容以下

accountServiceOld=com.factory.service.impl.AccountServiceOldImpl

接着使用工廠方法代碼:

/**
 * 一個建立Bean對象的工廠
 *
 *    一、須要一個配置文件來配置咱們的service和dao    配置文件的內容:惟一標識=全限定類名(key-value)
 *    二、經過讀取配置文件中配置的內容,反射建立對象
 *
 *    場景:主要是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建立一次實例就很完美了,這也是我爲啥要在servicecontroller中都添加一個Old關鍵字的緣由了,接下來咱們來看看工廠是如何改進的!

五、工廠模式改進

爲了避免被搞暈,咱們從新寫代碼,也就是重頭開始寫代碼~其實就是把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中正是使用工廠模式來實現程序解耦的,spring是一個大工廠, 或許你並無察覺哈哈哈哈.....

若是本文對你有一點點幫助,那麼請點個讚唄,你的贊同是我最大的動力,謝謝~

最後,如有不足或者不正之處,歡迎指正批評,感激涕零!若是有疑問歡迎留言,絕對第一時間回覆!

歡迎各位關注個人公衆號,裏面有一些java學習資料和一大波java電子書籍,好比說周志明老師的深刻java虛擬機、java編程思想、核心技術卷、大話設計模式、java併發編程實戰.....都是java的聖經,不說了快上Tomcat車,咋們走!最主要的是一塊兒探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

在這裏插入圖片描述

相關文章
相關標籤/搜索