代理模式

故事

有没有男生没有玩过网游的举手?

前阵子各大院线热映的《魔兽世界》勾起了笔者的回忆,跟女友第一时间去影院看了这场期待已久的游戏改编的大电影,当看到电影中呜哩哇啦叫着的“鱼人”的时候,影院里很多人都会心的笑了。当然,只有玩过魔兽世界这款游戏的人并且从新手村一路练级的玩家才知道鱼人的叫声意味着“危险”临近了,经常是刚跑到(30级才有坐骑可以买,又贵到新手买不起)打怪地点,就被这群呜哩哇啦叫着的鱼人给群殴致死了,心里这个恨啊,只好复活,再跑一次。

游戏断断续续玩了几年,经历“70年代”,“80年代”,“90年代”,人物级别上限不断提高,却很难再有当初的激情去玩去“闯荡”世界了。

玩这个游戏的时候,是网易代理的,玩家通过买游戏点卡的方式换取游戏时间,那时候小卡15RMB,大卡30RMB,那时候玩家可以直接和其他RMB玩家用游戏内的金币交易点卡,慢慢也出现了G团,当然也少不了“代练”,虽然我觉得这个游戏真正有乐趣的地方恰恰在于升级的过程,但快餐时代,一大批新手玩家涌入到游戏中,需求催生市场,大批的代练工作室也应运而生。

作为一个程序员,我在魔兽世界的升级过程如下图所示:

Alt Text

同样,作为一个程序员,我还要用一段代码描述我在魔兽世界的辛酸的升级过程(简直罄竹难书有木有!):

    /// <summary>
    /// 游戏者接口
    /// </summary>
    public interface IWOWPlayer
    {
        void Login(string account, string password);
        void Killing();
        void Upgrade();
    }

    /// <summary>
    /// 游戏者
    /// </summary>
    public class WowPlayer : IWOWPlayer
    {
        public string Name { get; private set; }

        public WowPlayer(string userName)
        {
            this.Name = userName;
        }

        public void Login(string account, string password)
        {
            Console.WriteLine("咳咳,用户 {0} 用账号 {1} 登录了WOW", this.Name, account);
        }

        public void Killing()
        {
            Console.WriteLine("{0} 正在打怪,或者被怪打,不用你管!", this.Name);
        }

        public void Upgrade()
        {
            Console.WriteLine("哇塞,{0} 升级了也!", this.Name);
        }
    }

    /// <summary>
    /// 模拟场景
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            IWOWPlayer player = new WowPlayer("小白");
            player.Login("xiaobai", "123456");
            player.Killing();
            player.Upgrade();
        }
    }

前面提到过,游戏中后来催生了很多代练工作室,快餐时代嘛,很多新手可能不像我这样傻里傻气的把杀怪、体会游戏中的美景,有趣的故事情节当作乐趣;当然,也可能是学生借着假期来玩,时间紧,来不及体会,所以,自然而然的就找到了代练,由他们帮着练级打怪。那么,代练的工作模式是怎样的呢?

作为一个程序员,嗯,你猜对了,我又要画图了:

Alt Text

类图中,增加了一个Proxy类,用来代表代练,因为代练也是手动打怪,所以也要实现IWOWPlayer接口。

同样,作为一个程序员,哎呀,你又猜对了,我要上代码了:

/// <summary>
/// 代练者
/// </summary>
public class WOWPlayerProxy : IWOWPlayer
{
    public IWOWPlayer Player { get; private set; }

    /// <summary>
    /// 构造方法传入要代练的玩家
    /// </summary>
    /// <param name="player">玩家</param>
    public WOWPlayerProxy(IWOWPlayer player)
    {
        this.Player = player;
    }

    /// <summary>
    /// 代练用玩家账号登录
    /// </summary>
    /// <param name="account"></param>
    /// <param name="password"></param>
    public void Login(string account, string password)
    {
        this.Player.Login(account, password);
    }

    /// <summary>
    /// 代练在打怪
    /// </summary>
    public void Killing()
    {
        this.Player.Killing();
    }

    /// <summary>
    /// 代练升级了
    /// </summary>
    public void Upgrade()
    {
        this.Player.Upgrade();
    }
}

修改后的场景如下:

/// <summary>
/// 模拟场景2
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        IWOWPlayer player = new WowPlayer("小白");
        IWOWPlayer proxy = new WOWPlayerProxy(player); //实例化代练类,传入“小白”给其构造方法
        proxy.Login("xiaobai", "123456"); //代练用小白的账号登录
        proxy.Killing(); // 代练在打怪
        proxy.Upgrade(); // 代练升级了
    }
}

看,新手现在已经不需要自己打怪了,当然升级的乐趣也体会不到了,所有关于升级的一切都由代练包办了,只要把小白的账号交给代练就行了。

当当当! 代理模式 隆重登场!

概念

Provider a surrogate or placeholder for another object to control access to it.
代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。

代理模式通用类图如下:

Alt Text

  • Subject 抽象主题角色,类或接口
  • RealSubject 真实主题角色,被委托的角色,被代理的角色,业务逻辑的具体执行者
  • Proxy 代理,委托、代理角色,,负责对真实角色的应用

代理模式还有个小名,叫“委托模式”,是不是很贴切?

代理模式是一项基本设计技巧。许多其他的模式,如状态模式,策略模式,访问者模式本质上实在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。(出自《设计模式之禅》)

根据通用类图实现的通用代码如下:

public interface ISubject
{
    void Request();
}

public class RealSubject : ISubject
{
    public void Request()
    {
        throw new NotImplementedException();
    }
}

public class ProxySubject : ISubject
{
    private ISubject Subject = null;

    public ProxySubject(ISubject subject)
    {
        this.Subject = subject;
    }

    public ProxySubject()
    {
        this.Subject = new RealSubject();
    }

    public void Request()
    {
        this.Before();
        this.Subject.Request();
        this.After();
    }

    private void Before()
    {
        // 预处理
    }

    private void After()
    {
        //善后处理
    }
}

通用实现代码中,我加入了Before和After方法,代理类可以在其中实现自己的逻辑如权限控制,引用计数等。这里可以引入另外一个话题“面向方面的编程(AOP)”,有兴趣的同学可以搜索下,也可以关注我的博客,以后另开篇幅详说。

一个代理类可以代理多个被委托者或被代理者。因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。当然,最简单的情况就是一个主题类和一个代理类,这是最简洁的代理模式。--出自《设计模式之禅》

代理模式的应用场景

  • 远程代理
    比如网络服务,当我们在项目中引入一个Web引用(如天气预报),我们可以使用代理模式隐藏掉远程网络服务的一些细节,无论它在哪里,对于客户来说,就像是在操作本地对象一样;
  • 虚拟代理
    比如网页加载,网页中包含了大量的文字和图片,我们可以很快的看到文字,但图片资源需要一张一张的下载后才能看到,那些未打开的图片框,就是通过虚拟代理替换掉了真是的图片,此时代理中存储了真实图片的地址和尺寸;
  • 安全代理
    控制真实对象的访问权限,多用于对象应该有不同的访问权限的时候;
  • 指针引用
    是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;
  • 延迟加载
    用代理模式实现延迟加载的一个例子就是应用启动时的数据加载,当加载的数据很大时,势必会影响用户体验,比如界面卡死,出现大片空白等。使用代理模式,可以将应用启动时不需要用到的加载项延迟到使用时加载,从而减少资源占用,提升系统效率和用户体验。

延迟加载

以一个简单的示例来阐述使用代理模式实现延迟加载的方法及其意义。假设有一个操作数据库的应用,该应用启动时,需要初始化系统的所有类,其中包括尝试建立数据库连接。当然系统中还有其他耗时操作,比如操作XML文件的文件解析类等。所有这些初始化的操作叠加起来会使得应用的启动变得非常缓慢,可能用户还没有等你的应用初始化完成,就已经不耐烦的关闭了应用。这个时候,代理模式就派上用场了。使用代理模式封装对数据库查询中的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理类直到真正的数据操作之前是什么都不会做的。因此其构造非常迅速。

public interface IDbHelper
{
    string Request();
}

/// <summary>
/// 真实的数据库操作类
/// </summary>
public class MySqlHelper : IDbHelper
{
    public MySqlHelper()
    {
        //假设耗时的连接操作
        System.Threading.Thread.Sleep(1000);
    }

    public string Request()
    {
        return "user request";
    }
}

public class MySqlHelperProxy : IDbHelper
{
    public IDbHelper DbHelper { get; private set; } = null;

    public string Request()
    {
        //只有在需要的时候才创建对象
        //这里注意在多线程的环境下要考虑使用单例模式
        if (null == this.DbHelper)
        {
            DbHelper = new MySqlHelper();
        }
        return this.DbHelper.Request();
    }
}

/// <summary>
/// 模拟场景
/// </summary>
class ProxyPatternDemo
{
    static void Main(string[] args)
    {
        IDbHelper dbHelper = new MySqlHelperProxy(); //使用代理
        dbHelper.Request(); //在真正使用时才创建真实的对象
    }
}

结束语

代理模式的应用很广泛,是我们最常用的设计模式之一,本文所述的几种应用只是很简单的几种,希望能够帮助大家认识这个模式。


参考:

posted @ 2017-10-20 08:45  DebugLife  阅读(557)  评论(1编辑  收藏  举报