小言_互联网的博客

Android 架构:设计单例,使单例对象面向接口编程

318人阅读  评论(0)

我们都知道面向接口编程的主要优点有: 提高编程的灵活性模块解耦,降低维护成本

但是,对于平时我们使用的单例来说,我们是无法做到上面的事情的。如果,我们想让单例对象也可以面向对象编程的话。
我们就需要在其他的地方来保证对象的唯一性(单例),不能通过传统的方式,在对象内部保证对象的唯一性。

通过下面的目录,我们来实现一个可扩展的单例。

普通单例

使用以前的方法,我们创建一个单例。比如,我们创建一个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.");
    }
  }
}

通过上面的修改,我们反射获取类对象就算完成啦。整个单例的设计也就完成了。

通过上面的实现,我们既实现了对象的单例,又通过接口控制了调用者的使用范围,并且具有了扩展性。


转载:https://blog.csdn.net/ecliujianbo/article/details/103571620
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场