我们都知道面向接口编程的主要优点有: 提高编程的灵活性和模块解耦,降低维护成本。
但是,对于平时我们使用的单例来说,我们是无法做到上面的事情的。如果,我们想让单例对象也可以面向对象编程的话。
我们就需要在其他的地方来保证对象的唯一性(单例),不能通过传统的方式,在对象内部保证对象的唯一性。
通过下面的目录,我们来实现一个可扩展的单例。
普通单例
使用以前的方法,我们创建一个单例。比如,我们创建一个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