Android Architecture: Design Singletons to Make Singleton Object-Oriented Interface Programming

We all know that the main advantages of interface-oriented programming are: improving programming flexibility and module decoupling, and reducing maintenance costs .

However, for the singletons we usually use, we cannot do the above things. If we want singleton objects to also be object-oriented programming.
We need to ensure the uniqueness of the object (singleton) in other places. We cannot guarantee the uniqueness of the object inside the object in the traditional way.

Through the following directory, let's implement an extensible singleton.

common singleton

Using the previous method, we create a singleton. For example, we create a UserManager singleton.

public class UserManager {

  private static final UserManager manager = new UserManager();

  public static UserManager getInstance() {
    return manager;
  }

  private UserManager() {
  }
}

advantage:

  • There is only one object in memory, saving system resources.
  • Access control to unique instances.

shortcoming:

  • Singletons have no abstraction layer and basically have no scalability.
  • The long life cycle can easily lead to memory leaks.
  • Each caller obtains a singleton object, which can be modified internally.

Ordinary singletons, whether they are hungry or lazy, we use the method inside the object, the private constructor, to ensure uniqueness, which cannot satisfy our scalability, and each caller can modify the logic inside the object.

Design a new singleton

The purpose of our singleton design is to solve the above problems:

  • no scalability
  • Controls the caller's functional restrictions on the singleton object.

1, we first create a reflection tool class

This class mainly creates instance objects through reflection methods

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.");
    }
  }
}

This class mainly creates a class object through reflection.

2. We create a singleton factory

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;
  }

}

The main role of this class:

  • Create a thread-safe cache collection
  • Create objects. First check from the cache, and return if there is one; if not, create it through reflection.
  • Because the collection is thread-safe, the uniqueness of the object is guaranteed here.

The only purpose is to ensure the uniqueness of the object.

3. Create an IUserManager interface

Create an interface. Provide the operation method of UserManager

public interface IUserManager {

  User getCurUser();

  void updateUser(User user);
}

4. Transform UserManager

Ordinary singleton, almost no scalability. We can now implement singletons through SingletonFactory without restricting the UserManager object.

Now, our UserManager does not need to use a singleton, let's transform it.

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的逻辑
  }
}

Here, mainly

  • Remove the normal singleton pattern of the UserManager object.
  • Let it implement the interface, and we use this interface to limit the scope that the caller can operate.

5. Test

After the modification, let's see how to call it.

public class Test {

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

}

As you can see here, we can call it by declaring the interface. In this way, we can code in an interface-oriented way.

The benefits of this are:

  • Others can only operate through the methods provided by our interface.
  • If what we expose to the outside world is a specific object, then others can modify this object.
  • The caller does not know your specific object, internal logic.

In fact, the above words, when others call, still need to pass in the object of the operation.
The disadvantages of this are:

  • If there are too many places to use, if we need to replace the class, there are too many places involved.

6. Create a Factory class corresponding to the function

For each class that needs to be operated, create a Factory class. In this way, we will not expose the specific operations to the outside world, and it is convenient to modify.

public class UserFactory {

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

In this way, when calling from the outside, just call the getUserManager() method directly, without referencing the UserManager class, which is convenient for modification, and does not expose specific operation classes.

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

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

The benefits of doing this:

  • Complete decoupling, external calls do not need to introduce the UserManager object
  • For external callers, the internal logic is completely isolated

Is that the end?
If the caller finds it troublesome to operate directly through the new object, how do we control it. . . . . .

7. Use of access control characters

The above external control has been done. But, as said above, I want to use it in a different position... such as

  public static void main(String[] args) {

    UserManager userManager1 = new UserManager();
  }

In order to control such behavior. We need to set an appropriate access control character for its constructor.

public class UserManager implements IUserManager {

  protected UserManager() {

  }
}

Here, we use the protected control constructor to only make it visible in subclasses and under the same package. In this way, if other people want to use it, they can only use it according to the rules we provide.


At this point, are we done?

Not yet, but it's just a step away. . . . .

Because after we set the access control character restriction for the constructor, our reflection class ProtoFactory also needs to be modified accordingly.

Because the class that obtains the non-public modified constructor through class.newInstance cannot be instantiated. If you call it like this again, an error will be reported, because our constructor is no longer public.

Next we modify the ProtoFactory class

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.");
    }
  }
}

Through the above modification, we can complete the acquisition of the class object through reflection. The design of the entire singleton is completed.

Through the above implementation, we not only realize the singleton of the object, but also control the scope of use of the caller through the interface, and have scalability.

Guess you like

Origin blog.csdn.net/ecliujianbo/article/details/103571620