如何结合工厂方法与单例模式实现游戏地图单实例控制?
我目前正在开发一款包含多种类型地图的游戏:
- 所有地图都具备共同属性与方法,因此创建了抽象类
Map,并派生出SafeMap、HazardousMap、WeirdMap等子类。 - 为便于后续新增地图,我采用了工厂方法设计模式,实现了
MapCreator类,代码如下:
public class MapCreator{ public Map createMap(char mapType){ switch(mapType){ case 'S': return new SafeMap(); case 'H': return new HazardousMap(); case 'W': return new WeirdMap(); default: return null; } } }
现在我希望确保无论选择哪个子类,都仅存在一个Map实例。我了解单例设计模式的实现方式:在类中声明私有静态实例,并将构造方法设为私有,但不知道如何将其与工厂方法模式结合——因为存在多个子类,且 getInstance() 需在具体类中实现。
我已经查阅过一些类似问题,但觉得这些回答和我的情况不匹配,恳请各位提供帮助。
这其实是个很典型的「多单例+工厂」场景,我们可以从两个方向来实现,既保留工厂的扩展性,又保证每个地图子类的单例特性:
方案一:让每个Map子类自身实现单例,工厂负责调用单例获取方法
首先修改每个地图子类,把它们改成单例(以SafeMap为例,其他子类同理):
public class SafeMap extends Map { // 私有静态实例(饿汉式单例,线程安全) private static final SafeMap instance = new SafeMap(); // 私有构造方法,禁止外部实例化 private SafeMap() {} // 对外提供获取单例的方法 public static SafeMap getInstance() { return instance; } // 你的其他地图业务方法... }
然后修改MapCreator的createMap方法,不再直接new实例,而是调用子类的getInstance():
public class MapCreator{ public Map createMap(char mapType){ switch(mapType){ case 'S': return SafeMap.getInstance(); case 'H': return HazardousMap.getInstance(); case 'W': return WeirdMap.getInstance(); default: return null; } } }
这种方式的优点是职责清晰:子类自己管理单例逻辑,工厂只负责根据类型分发对应的单例实例。后续新增地图子类时,只需要让新类实现单例,再在工厂的switch里加个分支就行,符合开闭原则的基本要求。
方案二:在工厂类中统一管理所有地图单例
如果不想让每个子类都写一遍单例代码,可以把单例的管理逻辑放到工厂类里,用一个容器来缓存已经创建过的实例:
import java.util.HashMap; import java.util.Map; public class MapCreator{ // 缓存已创建的地图实例,key是地图类型标识,value是对应的Map实例 private static final Map<Character, Map> mapInstances = new HashMap<>(); public Map createMap(char mapType){ // 先看缓存里有没有,有就直接返回 if(mapInstances.containsKey(mapType)){ return mapInstances.get(mapType); } // 没有的话创建新实例并存入缓存 Map newMap = switch(mapType){ case 'S' -> new SafeMap(); case 'H' -> new HazardousMap(); case 'W' -> new WeirdMap(); default -> null; }; if(newMap != null){ mapInstances.put(mapType, newMap); } return newMap; } }
注意:这种方式需要把各个Map子类的构造方法设为包私有(如果工厂和子类在不同包,就设为protected),避免外部直接实例化子类,破坏单例特性。
这种方案的优点是单例逻辑集中,子类不需要修改,适合子类数量较多或者不想在子类里加单例代码的场景。但后续新增子类时,除了加switch分支,还要确保构造方法的访问权限正确。
额外优化建议
如果你的游戏后续可能会有大量地图类型,switch分支会越来越长,可以考虑用枚举+反射来进一步优化工厂,把地图类型和对应类的关联存在枚举里,这样新增地图时只需要加枚举项,不需要修改工厂的核心逻辑:
// 定义地图类型枚举 enum MapType { SAFE('S', SafeMap.class), HAZARDOUS('H', HazardousMap.class), WEIRD('W', WeirdMap.class); private final char code; private final Class<? extends Map> mapClass; MapType(char code, Class<? extends Map> mapClass) { this.code = code; this.mapClass = mapClass; } public static MapType fromCode(char code) { for(MapType type : values()){ if(type.code == code){ return type; } } return null; } public Class<? extends Map> getMapClass() { return mapClass; } } // 结合缓存的工厂实现 public class MapCreator{ private static final Map<Character, Map> mapInstances = new HashMap<>(); public Map createMap(char mapType){ if(mapInstances.containsKey(mapType)){ return mapInstances.get(mapType); } MapType type = MapType.fromCode(mapType); if(type == null){ return null; } try { // 通过反射创建实例,需要子类构造方法是public或者工厂有访问权限 Map newMap = type.getMapClass().getDeclaredConstructor().newInstance(); mapInstances.put(mapType, newMap); return newMap; } catch (Exception e) { // 处理反射异常,比如构造方法不存在、权限不足等 e.printStackTrace(); return null; } } }
这样后续新增地图,只需要加一个枚举项,不需要修改工厂的核心逻辑,扩展性更强。
内容的提问来源于stack exchange,提问作者Luke Collins

