Unity设计模式—服务定位器模式

Posted NickPansh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity设计模式—服务定位器模式相关的知识,希望对你有一定的参考价值。

服务定位器模式

概要

为某服务提供一个全局访问入口来避免使用者与该服务具体实现类耦合

游戏编程中,某些对象或者系统几乎出现在程序的每个角落。如音频,UI管理等。

直接引用对象肯定最差,用单例也不好,更合适的方案是引入服务定位器。

它使用起来像一个更灵活,更可配置的单例模式。

类结构

一个服务类为一系列操作定义了一个抽象的接口。

若干个具体服务提供器实现这个接口。

一个单独的服务定位器通过查找合适的提供器来提供这个服务的访问。

实现

jarjin/LuaFramework_UGUI的Facade就是用了服务定位器模式。

我在它的基础上结合《游戏编程模式》的服务定位模式设计了新的服务定位器,引入了空对象服务,让整个系统更易于使用。

Locator

// 服务定位器
public class Locator

    private static Dictionary<string, IManager> _managers = new Dictionary<string, IManager>();
    private static Dictionary<string, IManager> _nullManageCache = new Dictionary<string, IManager>()
    
        typeof(AudioManager).Name, new NullAudioManager() ,
        typeof(NetworkManager).Name, new NullNetworkManager() ,
    ;

    static Locator()
    

    
    /// <summary>
    /// 获得管理器
    /// </summary>
    /// <typeparam name="T">IManager</typeparam>
    /// <returns></returns>
    public static T GetManager<T>() where T : class, IManager
    
        string mgrName = typeof(T).Name;
        // 若字典中没有对应的管理器,则返回对应的空管理器
        if (!_managers.ContainsKey(mgrName))
        

            if (_nullManageCache.TryGetValue(mgrName, out IManager nullMgr))
            
                return nullMgr as T;
            
            else
            
                return default(T);
            
        
        return (T)_managers[mgrName];
    
    /// <summary>
    /// 添加管理器
    /// </summary>
    /// <typeparam name="T">IManager</typeparam>
    /// <param name="mgr">instance of T</param>
    /// <returns></returns>
    public static T AddManager<T>(T mgr) where T : class, IManager
    
        string mgrName = typeof(T).Name;
        if (_managers.ContainsKey(mgrName))
        
            _managers[mgrName] = mgr;
        
        else
        
            _managers.Add(mgrName, mgr);
        
        return mgr;
    

    public static void RemoveManager<T>() where T : class, IManager
    
        string mgrName = typeof(T).Name;
        if (_managers.ContainsKey(mgrName))
        
            _managers.Remove(mgrName);
        
    

AudioManager&NullAudioManager

public  class AudioManager : IManager

    public virtual void PlaySound(string soundName)
    
        Debug.Log("PlaySound:" + soundName);
    


// 空管理器
// q:为什么它继承自AudioManager而不是声明IAudioManager后NullAudioManager和AudioManager都实现IAudioManager呢?
// a:希望Locator在AddManager<T>()时,传的是AudioManager而不是IAudioManager,所以NullAudioManager必须继承自AudioManager
// 否则 return nullMgr as T;会返回空。比起空Manager持有变量占用内存的缺点而言,更看重Locator调用时方便易懂。
public class NullAudioManager : AudioManager

    public override void PlaySound(string soundName)
    
        // Do nothing
        Debug.Log("Do nothing:");
    

调用代码:

// 注册服务
// Register the service
AudioManager audioMgr = new AudioManager();
Locator.AddManager<AudioManager>(audioMgr);
NetworkManager networkMgr = new NetworkManager();
Locator.AddManager<NetworkManager>(networkMgr);

// 调用管理器
Locator.GetManager<AudioManager>().PlaySound("Sound1");

// 移除管理器
Locator.RemoveManager<NetworkManager>();

我见

服务定位器模式比单例模式要好得多,它比起单例模式好拓展的多。

  • 我们可以通过装饰器模式来装饰一个原有的服务,来轻易地拓展原有的服务。

  • 现在我们也不用担心单例的一些初始化步骤在哪里进行—只要用空对象模式即可。

  • 我们可以在运行时更换服务。

源码

完整代码已上传至nickpansh/Unity-Design-Pattern | GitHub

其他设计模式

专题 | Unity3D游戏开发中的设计模式 | 问渠 (wenqu.site)

设计模式-服务定位器模式

上文(设计模式-拦截过滤器模式):


背景

记得刚入行的时候,有一个需求通过用户每次来获取一个固定商品的信息,而我的实现是直接从库中查出再转成对象,然后返回给用户,突然某一天用户量增加了好几百倍,直接把库拉挂了...后面直接用上了缓存,解决了该问题...

服务定位器模式是什么?

服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。
角色:
服务(Service):实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。
Context / 初始的 Context :JNDI Context 带有对要查找的服务的引用。
服务定位器(Service Locator):服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。
缓存(Cache):缓存存储服务的引用,以便复用它们。
客户端(Client):Client 是通过 ServiceLocator 调用服务的对象。
优点:
提升系统性能:由于该模式可以将每次需要返回的固定数据放到缓存中可以减少每次创建对象的开销,很大程度上提升了系统的性能;
缺点:
缓存的实现使系统会比较复杂,如果控制不好会导致内存溢出。

服务定位器模式可以干嘛?

服务定位器模式主要解决一个相同或者公共的数据放到缓存中减少内存开销,并且在一定程序上提升了系统的性能。

个人理解:

比如数据库连接池,不需要每次去连接数据库的时候去创建连接,而是由池去保持一些连接,当需要连接数据库的时候直接获取池中的连接就可以了。

服务定位器模式类图

源码下载:https://gitee.com/hong99/design-model/issues/I1IMES

实现代码

/** * @Auther: csh * @Date: 2020/7/5 11:44 * @Description:抽象的服务接口 */public interface IUserInfo { public String getName(); public void printUserInfo();}
/** * @Auther: csh * @Date: 2020/7/5 11:45 * @Description:用户信息(Service) */public class User implements IUserInfo { private String userName;

public void setUserName(String userName) { this.userName = userName; }
private User() { }


public User(String usreName) { this.userName = usreName; }
@Override public String getName() { return userName; }
@Override public void printUserInfo() { System.out.println("用户信息:"+this.getName()); }
@Override public String toString() { return "User{" + "userName='" + userName + '\'' + '}'; }}
/** * @Auther: csh * @Date: 2020/7/5 11:48 * @Description:创建对象(Context ) */public class InitialContext { private static InitialContext initialContext = new InitialContext();
private InitialContext(){
}
public static InitialContext getInstance(){ return initialContext; }
public Object lookup(String username){ return new User(username); }}
/** * @Auther: csh * @Date: 2020/7/5 11:51 * @Description:用户缓存 */public class UserCache { private List<IUserInfo> userInfos;
public UserCache() { userInfos = new ArrayList <IUserInfo>(); }
public IUserInfo getUserInfo(String userName){ for (IUserInfo userInfo : userInfos) { if(userInfo.getName().equalsIgnoreCase(userName)){ System.out.println("从缓存中获取到用户信息:"+userInfo.toString()); return userInfo; } } return null; }
public void addUserInfo(IUserInfo userInfo){ boolean exist = false; for (IUserInfo info : userInfos) { if(info.getName().equalsIgnoreCase(userInfo.getName())){ exist = true; } } if(!exist){ userInfos.add(userInfo); } }}
/** * @Auther: csh * @Date: 2020/7/5 12:06 * @Description:服务定位器(Service Locator) */public class UserInfoLocator { private static UserCache userCache = new UserCache();
public static IUserInfo getUserInfo(String userName){ IUserInfo userInfo = userCache.getUserInfo(userName); if(userInfo!=null){ return userInfo; } InitialContext instance = InitialContext.getInstance(); IUserInfo user = (IUserInfo)instance.lookup(userName); userCache.addUserInfo(user); return user; }}
/** * @Auther: csh * @Date: 2020/7/5 12:51 * @Description:服务定位器模式 */public class Client { public static void main(String[] args) { IUserInfo userInfo = UserInfoLocator.getUserInfo("user1"); userInfo.printUserInfo(); userInfo = UserInfoLocator.getUserInfo("user2"); userInfo.printUserInfo(); userInfo = UserInfoLocator.getUserInfo("user1"); userInfo.printUserInfo(); }}

结果

用户信息:user1用户信息:user2从缓存中获取到用户信息:User{userName='user1'}用户信息:user1

源码下载:https://gitee.com/hong99/design-model/issues/I1IMES

最后

参考文章:

https://www.oracle.com/technetwork/java/servicelocator-137181.html

http://gameprogrammingpatterns.com/service-locator.html

以上是关于Unity设计模式—服务定位器模式的主要内容,如果未能解决你的问题,请参考以下文章

unity script 精准定位访问

Unity设计模式:命令模式

Unity设计模式:命令模式

Unity---游戏设计模式之单例模式

Unity开发常用设计模式(初级)

[Unity 设计模式]桥接模式(BridgePattern)