You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合工厂方法与单例模式实现游戏地图单实例控制?

如何结合工厂方法模式与单例模式实现每个地图子类仅一个实例?

我目前正在开发一款包含多种类型地图的游戏:

  • 所有地图都具备共同属性与方法,因此创建了抽象类 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:11:10