Android 架構:設計單例,使單例對象面向接口編程

我們都知道面向接口編程的主要優點有: 提高編程的靈活性模塊解耦,降低維護成本

但是,對於平時我們使用的單例來說,我們是無法做到上面的事情的。如果,我們想讓單例對象也可以面向對象編程的話。
我們就需要在其他的地方來保證對象的唯一性(單例),不能通過傳統的方式,在對象內部保證對象的唯一性。

通過下面的目錄,我們來實現一個可擴展的單例。

普通單例

使用以前的方法,我們創建一個單例。比如,我們創建一個UserManager單例。

public class UserManager {

  private static final UserManager manager = new UserManager();

  public static UserManager getInstance() {
    return manager;
  }

  private UserManager() {
  }
}

優點:

  • 內存中只有一個對象,節約系統資源。
  • 對唯一實例的訪問控制。

缺點:

  • 單例沒有抽象層,基本沒有擴展能力。
  • 生命週期長,容易引發內存泄漏。
  • 每個調用者獲取到單例對象,對可以對其內部進行修改。

普通的單例,不管是餓漢式還是懶漢式,我們都是通過在對象內部的方式,私有構造器,來保證唯一性的,這樣也就不能滿足我們的擴展性,並且每個調用者,都可以修改對象內部的邏輯。

設計新單例

我們設計單例的目的,就是爲了解決上面的問題:

  • 沒有擴展能力
  • 控制調用者對單例對象的功能限制。

1,我們先創建一個反射工具類

這個類主要是通過反射的方法來創建實例對象

public class ProtoFactory {

  /**
   * 不帶參數的構造方法
   */
  public static <T> T newInstance(Class<T> clazz) {
    try {
      return clazz.newInstance();
    } catch (IllegalAccessException e) {
		//這裏是自定義的異常處理
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    } catch (InstantiationException e) {
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    }
  }

  /**
   * 帶參數的構造方法
   */
  public static <T> T newInstance(Class<T> clazz, Class<?>[] paramTypes, Object[] paramValues) {
    try {
      Constructor<T> constructor = clazz.getConstructor(paramTypes);
      T result = constructor.newInstance(paramValues);
      return result;
    } catch (InstantiationException e) {
		//這裏是自定義的異常處理
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    } catch (Exception e) {
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    }
  }
}

這個類,主要是通過反射來創建一個類對象。

2,我們創建一個單例的工廠

public class SingletonFactory {
	//單例對象的緩存集合。這裏是線程安全的
  private static Map<String, Object> objectMap = Collections
      .synchronizedMap(new HashMap<String, Object>());
	//創建對象
  public static <T> T getSingleton(Class<T> tClass) {
    T result;
    synchronized (SingletonFactory.class) {
      if (objectMap == null) {
        objectMap = Collections.synchronizedMap(new HashMap<String, Object>());
      }
    }
    synchronized (objectMap) {
      result = (T) objectMap.get(tClass.getName());
      if (result == null) {
        result = ProtoFactory.newInstance(tClass);
      }
      objectMap.put(tClass.getName(), result);
    }
    return result;
  }

}

這個類的主要作用:

  • 創建一個線程安全的緩存集合
  • 創建對象。首先從緩存中檢查,有就返回;沒有,就通過反射創建。
  • 因爲集合是線程安全的,這裏就保證了對象的唯一性。

目的只有一個就是確保對象的唯一性。

3,創建一個IUserManager的接口

創建接口。提供UserManager的操作方法

public interface IUserManager {

  User getCurUser();

  void updateUser(User user);
}

4,改造UserManager

普通的單例,幾乎沒有擴展性。我們現在,通過SingletonFactory就可以實現單例,並且不用限制UserManager對象。

現在,我們的UserManager就不需要使用單例的方式了,我們改造下。

public class UserManager implements IUserManager {

  public UserManager() {

  }
  @Override
  public User getCurUser() {
    //TODO: 寫獲取User的邏輯
    User use = new User();
    use.name = "liu";
    return null;
  }

  @Override
  public void updateUser(User user) {
    //TODO: 更新User的邏輯
  }
}

這裏,主要是

  • 去掉UserManager對象的普通單例模式。
  • 讓它實現接口,我們通過這個接口來限制調用者可以操作的範圍。

5,測試

改造完了以後,我們看下怎麼調用。

public class Test {

  public static void main(String[] args) {
    IUserManager userManager = SingletonFactory.getSingleton(UserManager.class);
  }

}

這裏可以看到,我們可以聲明接口的方式來調用了。這樣,我們就可以通過面向接口的方式來進行編碼。

這樣的好處有:

  • 別人只能通過我們接口提供的方法操作。
  • 如果,我們對外暴露的是具體的對象的話,那麼別人也就可以對這個對象進行修改了。
  • 調用者不知道你具體的對象,內部邏輯。

其實,上面的話,別人調用的時候,還是需要傳入操作的對象。
這樣的壞處是:

  • 如果使用的地方特別多,如果,我們需要替換類的話,涉及的地方太多。

6,創建一個對應功能的Factory類

給每一個需要操作的類,創建一個Factory類。這樣,我們對外就不會暴露具體的做操作,並且方便修改。

public class UserFactory {

  public static IUserManager getUserManager() {
    return SingletonFactory.getSingleton(UserManager.class);
  }
}

這樣,外面調用的時候,直接調用getUserManager()方法就可以了,不會引用UserManager類,方便修改,也不會暴露具體的操作類。

   public static void main(String[] args) {
//    IUserManager userManager = SingletonFactory.getSingleton(UserManager.class);

    //新的調用方式
    IUserManager userManager = UserFactory.getUserManager();
  }

這樣做的好處:

  • 徹底解耦,外部調用不需要引入UserManager對象
  • 對外部調用者來說,內部的邏輯是完全隔離的

這樣就完了嗎?
如果調用者嫌麻煩,直接通過new 對象來操作呢,我們怎麼控制。。。。。。

7,訪問控制符的使用

上面的對外控制已經做到了。但是,就像上面說的,我想換個姿勢來使用呢…比如

  public static void main(String[] args) {

    UserManager userManager1 = new UserManager();
  }

我們爲了控制這樣的行爲。我們就需要給它的構造器,設置一個合適的訪問控制符了。

public class UserManager implements IUserManager {

  protected UserManager() {

  }
}

這裏,我們使用protected控制構造器,只讓其在子類及相同包下可見。這樣,其他人如果想使用,只能按照我們提供的規則來使用啦。


到這裏,我們是不是就完成了呢?

還沒有,不過,只差臨門一腳了。。。。。

因爲我們給構造器設置了訪問控制符限制以後,我們的反射類ProtoFactory也需要對應的修改一下纔可以。

因爲通過class.newInstance來獲取非public修飾的構造器的類是實例化不了的。再這樣調用,就會報錯了,因爲我們的構造器已經不是public的了。

下面我們修改ProtoFactory類

public class ProtoFactory {

  /**
   * 不帶參數的構造方法
   */
  public static <T> T newInstance(Class<T> clazz) {
    try {
      Constructor<T> constructor = clazz.getDeclaredConstructor();
      constructor.setAccessible(true);
      return constructor.newInstance();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 帶參數的構造方法
   */
  public static <T> T newInstance(Class<T> clazz, Class<?>[] paramTypes, Object[] paramValues) {
    try {
      Constructor<T> constructor = clazz.getDeclaredConstructor(paramTypes);
      constructor.setAccessible(true);
      T result = constructor.newInstance(paramValues);
      return result;
    } catch (InstantiationException e) {
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    } catch (Exception e) {
      throw new ServiceException(ServiceExceptionCode.instantiationException,
          "create " + clazz.getName() + " failed.");
    }
  }
}

通過上面的修改,我們反射獲取類對象就算完成啦。整個單例的設計也就完成了。

通過上面的實現,我們既實現了對象的單例,又通過接口控制了調用者的使用範圍,並且具有了擴展性。

發佈了84 篇原創文章 · 獲贊 27 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章