首页 > 代码库 > 以引用对象取代单例模式

以引用对象取代单例模式

介绍:

系统中存在单例的全局访问点,你希望将对单例的访问通过对象引用来实现。往往是将对单例的依赖关系转换为关联关系。

动机:

在系统中引入单例模式往往并没有起到明显的效果却增加了系统的复杂性。不能仅仅因为某个类只需要一个实例而采用单例模式,这些完全可以用引用对象取代。
通过全局访问点使用单例对象往往造成依赖不清、可读性差等问题,我们完全可以通过显式的关联引用来做到在子系统中共享同一个实例,并且只对需要这个实例的对象注入依赖。将对全局变量的依赖转变为对成员对象的依赖使类更易于理解。
当设计需要中不需要强调系统的任何时刻都只需要同一实例的时候,单例模式最大的缺点在于没有优点。单例模式并不是常用的设计模式。 

重构:

1. 修改单例类,将构造函数设为public,取消访问方法和相关属性。
2. 查找类的所有引用点,添加对单例类的引用字段并将全局访问改为对引用对象的请求。
3. 修改相关类的构造函数,将引用对象作为参数传递进来。
4. 查找相关类的引用点,增加构造函数的调用参数。

范例:

假设有这样一个类:它封装了输入,外观层修改类的状态,并使具体类可以不同步地访问此类。这样的封装降低了外观类与具体类的耦合。
由于输入类只需要一个实例,可以采用单例模式实现这种设计(不考虑线程安全):
    /// <summary>
    /// 封装用户输入
    /// </summary>
    public class Input
    {
        private Input() { }

        /// <summary>
        /// 用户输入实例
        /// </summary>
        private static Input _instance;

        /// <summary>
        /// 获取封装用户输入的唯一实例
        /// </summary>
        public static Input Instance
        {
            get 
            {
                if (_instance == null)
                {
                    _instance = new Input();
                }
                return _instance;
            }
        }

        /// <summary>
        /// 绑定到窗体
        /// </summary>
        /// <param name="window">相应用户输入的窗体</param>
        public void BindingToForm(Form window)
        {
            KeyBoard = new KeyBoard(window);
        }

        /// <summary>
        /// 获取或设置键盘输入状态
        /// </summary>
        public KeyBoard KeyBoard { get; private set; }

        /// <summary>
        /// 更新输入状态
        /// </summary>
        public void Update()
        {
            KeyBoard.Process();
        }
    }

通过窗体事件获取输入数据,并直接反应在这个唯一实例中。

现在用引用对象取代单例模式,则Input类改为:
    /// <summary>
    /// 封装用户输入
    /// </summary>
    public class Input
    {
        /// <summary>
        /// 绑定到窗体
        /// </summary>
        /// <param name="window">相应用户输入的窗体</param>
        public void BindingToForm(Form window)
        {
            KeyBoard = new KeyBoard(window);
        }

        /// <summary>
        /// 获取或设置键盘输入状态
        /// </summary>
        public KeyBoard KeyBoard { get; private set; }

        /// <summary>
        /// 更新输入状态
        /// </summary>
        public void Update()
        {
            KeyBoard.Process();
        }
    }

具体类拥有对Input实例的引用,通过异步的方式与外观类共同使用Input对象.
    class SpaceShip
    {
        Input _input;

        public SpaceShip(Input input)
        {
            this._input = input;
        }

        public Vector MoveDirection { get; set; }

        private Vector _posation;

        public void Move()
        {
            _posation = _posation + MoveDirection;
        }

        public void Update()
        {
            Vector amount = Vector.Zero;

            if (_input.KeyBoard.IsKeyHeld(Keys.Left))
            {
                amount.X = -1;
            }
            if (_input.KeyBoard.IsKeyHeld(Keys.Right))
            {
                amount.X = 1;
            }
            if (_input.KeyBoard.IsKeyHeld(Keys.Up))
            {
                amount.Y = -1;
            }
            if (_input.KeyBoard.IsKeyHeld(Keys.Down))
            {
                amount.Y = 1;
            }
            MoveDirection = amount;
        }
    }

外观类创建并使用Input对象,并将此对象注入各个需要的类中。在winfrom程序中体现为Form子类组合Input的一个实例,通过用户界面事件改变Input对象状态,具体类可以通过引用异步的获取这些状态。