目录
今天来继续介绍单例模式相关的两个东西,第一个是单例模式的登记式模式,可以被继承,第二个是实现固定数量的实例。
登记式模式
package singleton;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 登记式模式父类
*/
public class RegisterPattern {
private static Map<String,RegisterPattern> map = new HashMap<>();
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
RegisterPattern registerPattern1 = getInstance(null);
System.out.println("registerPattern1:"+registerPattern1);
RegisterPattern registerPattern2 = getInstance(null);
System.out.println("registerPattern2:"+registerPattern2);
if(registerPattern1 == registerPattern2){
System.out.println("两次获取的实例化对象相等!");
}else{
System.out.println("两次获取的实例化对象不相等!");
}
}
/**
* 这里必须要设置为protected以上级别的构造函数,否则子类无法访问构造函数,详细原因如下:
* 因为创建子类对象调用子类的构造方法的时候会先调用父类的构造方法(需要设置为protected级别以上),在子类的构造方法中调用父类的构造方法是用super(),
* 如果没有写super(),则默认调用父类的无参构造方法。
*/
protected RegisterPattern(){}
public static RegisterPattern getInstance(String name) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if(name == null){
name = RegisterPattern.class.getName();
System.out.println("当前name:"+name);
}
// key对应的实例为null
if(!Optional.ofNullable(map.get(name)).isPresent()){
synchronized (RegisterPattern.class){
if(!Optional.ofNullable(map.get(name)).isPresent()){
// 通过反射方式可以实例化子类
map.put(name,(RegisterPattern)Class.forName(name).newInstance());
}
}
}
return map.get(name);
}
public Map<String,RegisterPattern> getMap(){
return map;
}
}
这里代码展示的是父类的单例模式写法,我们可以看到实例化后的对象是通过map进行登记起来,其中构造方法用到了protected的访问级别,而不是private,原因在于我们造的单例模式需要满足被继承实现,所以只能用protected级别以上的,才能够被访问,那么可能这时候就会提出一个问题了,那不是会有可能被同包下其它的类给new新对象了,对的,这是这种模式的一个缺陷,避免的方法:将单例类放到一个外在的包中,以便在其它包中的类(包括缺省的包)无法实例化一个单例类。
顺便科普下java的访问级别:
- private(当前类访问级别):如果类的成员被private访问控制符来修饰,则这个成员只能被该类的其他成员访问,其他类无法直接访问。类的良好封装就是通过private关键字来实现的。
- default(包访问级别):如果一个类或者类的成员不使用任何访问控制符修饰,则称它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问。
- protected(子类访问级别):如果一个类的成员被protected访问控制符修饰,那么这个成员既能被同一包下的其他类访问,也能被不同包下该类的子类访问。
- public(公共访问级别):这是一个最宽松的访问控制级别,如果一个类或者类的成员被public访问控制符修饰,那么这个类或者类的成员能被所有的类访问,不管访问类与被访问类是否在同一个包中。
package singleton;
/**
* 登记式模式子类
*/
public class RegisterChildrenAPattern extends RegisterPattern{
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
RegisterPattern registerPattern1 = getInstance(null);
System.out.println("registerPattern1:"+registerPattern1);
RegisterPattern registerPattern2 = getInstance(null);
System.out.println("registerPattern2:"+registerPattern2);
RegisterChildrenAPattern registerChildrenAPattern1 = getInstance();
System.out.println("registerChildrenAPattern1:"+registerChildrenAPattern1);
RegisterChildrenAPattern registerChildrenAPattern2 = getInstance();
System.out.println("registerChildrenAPattern2:"+registerChildrenAPattern2);
System.out.println(registerPattern1.getMap());
}
public static RegisterChildrenAPattern getInstance() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
return (RegisterChildrenAPattern)RegisterChildrenAPattern.getInstance("singleton.RegisterChildrenAPattern");
}
}
这里展示的是子类继承父类后直接定义一个getInstance()方法,调用父类的getInstance方法即可实例化子类的单例,且同时登记到map里。运行后输出的结果如下:
可以看到父类和子类的两次对象的获取,分别为同一个对象,且都登记到map中,以上就是登记式模式,父类可以被继承,且登记不同的实例对象。
利用单例实现固定数量实例
package singleton;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Teacher {
public static void main(String[] args){
int studentCount = 5;
while (studentCount > 0){
System.out.println("第"+studentCount+"个学生报名分配的老师是:"+getInstance().getName());
studentCount -- ;
}
}
// 定义实例数量
private static int count = 2;
private String name;
// 定义一个列表,存储固定实例的教师
private static List<Teacher> list = new ArrayList<>();
static{
// 初始化对象
for (int i=0; i < count; i++) {
Teacher teacher = new Teacher("老师"+i);
list.add(teacher);
}
}
// 私有构造函数,初始化变量
private Teacher(String name){
this.name = name;
System.out.println("i am is "+name);
}
public String getName(){
return this.name;
}
/**
* 随机从固定实例中获取一个
* @return
*/
public static Teacher getInstance(){
Random random = new Random();
return list.get(random.nextInt(count));
}
}
以上代码通过模拟一个场景,一个学校有n个老师,将会有一堆学生来报名,报名成功后分配的老师将是随机的。
从代码上可以看出首先定义一个Teacher类,通过静态初始化固定实例teacher对象,并且对外提供一个getInstance()方法,随机获取一个teacher对象实例。
而在main方法中模拟有5个学生将报名参加学习,每个学生将随机分配一个老师,运行的结果如下:
以上就是单例模式的拓展去实现一个固定数量的实例的模拟场景应用。