0

考虑下面的示例代码,其中包含一个类库设计和一个使用该库的可执行程序。

namespace AppLib
{
    /// <summary>
    /// Entry point for library. Stage manages all the actors in the logic.
    /// </summary>
    class StageApp
    {
        /// <summary>
        /// Setting that is looked up by different actors
        /// </summary>
        public int SharedSetting { get; set; }

        /// <summary>
        /// Stage managing actors with app logic
        /// </summary>
        public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }

        private List<Actor> m_actors = new List<Actor>();
    }

    /// <summary>
    /// An object on the stage. Refers to stage (shared)settings and execute depending on the settings.
    /// Hence actor should have reference to stage
    /// </summary>
    class Actor
    {
        private StageApp m_StageApp;

        private int m_Property;

        /// <summary>
        /// An actor that needs to refer to stage to know what behavior to execute
        /// </summary>
        /// <param name="stage"></param>
        public Actor(StageApp stage)
        {
            m_StageApp = stage;
            m_Property = new Random().Next();
        }

        /// <summary>
        /// Execute according to stage settings
        /// </summary>
        /// <returns></returns>
        public int Execute()
        {
            return m_StageApp.SharedSetting * m_Property;
        }
    }
}

namespace AppExe
{
    using AppLib;

    class Program
    {
        static void Main(string[] args)
        {
            StageApp app = new StageApp();
            app.SharedSetting = 5;

            // Question: How to add actor to stage?

            foreach (var actor in app.Actors)
                Console.WriteLine(actor.Execute());
        }
    }
}

问题

Stage并且Actor有循环依赖,对我来说似乎很糟糕。例如,我们应该如何将演员添加到舞台?

如果我让用户自己创建新Actor()的,那么他们必须继续提供Stage.

如果我给Actor()一个内部构造函数并创建Stage一个工厂,那么我会失去一些让用户进行继承Actor的灵活性。

如果我做Stage一个单例,那么我只能有一组SharedSetting. Stage如果用户在他的 中想要多个AppExe,则无法完成。

无论如何要重新设计架构以避免上述问题?

4

3 回答 3

1

如果您的功能不受在演员之间共享 StageApp 设置的限制,还会有一些其他逻辑。例如,当您需要从 Actor 了解父 StageApp 时,反之亦然。我更喜欢以这种方式实现它:

namespace AppLib
{
    /// <summary> 
    /// Entry point for library. Stage manages all the actors in the logic. 
    /// </summary> 
    class StageApp
    {
        /// <summary> 
        /// Setting that is looked up by different actors 
        /// </summary> 
        public int SharedSetting { get; set; }

        /// <summary> 
        /// Stage managing actors with app logic 
        /// </summary> 
        public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }

        private List<Actor> m_actors = new List<Actor>();

        public int TotalActorsCount
        {
            get
            {
                return m_actors.Count;
            }
        }

        public void AddActor(Actor actor)
        {
            if (actor == null)
                throw new ArgumentNullException("actor");

            if (m_actors.Contains(actor))
                return; // or throw an exception

            m_actors.Add(actor);
            if (actor.Stage != this)
            {
                actor.Stage = this;
            }
        }

        // we are hiding this method, to avoid because we can change Stage only to another non null value
        // so calling this method directly is not allowed
        internal void RemoveActor(Actor actor)
        {
            if (actor == null)
                throw new ArgumentNullException("actor");

            if (!m_actors.Contains(actor))
                return; // or throuw exception

            m_actors.Remove(actor);
        }
    }

    /// <summary> 
    /// An object on the stage. Refers to stage (shared)settings and execute depending on the settings. 
    /// Hence actor should have reference to stage 
    /// </summary> 
    class Actor
    {
        private StageApp m_StageApp;

        private int m_Property;

        public StageApp Stage
        {
            get
            {
                return m_StageApp;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                if (m_StageApp != value)
                {
                    if (m_StageApp != null) // not a call from ctor
                    {
                        m_StageApp.RemoveActor(this);
                    }
                    m_StageApp = value;
                    m_StageApp.AddActor(this);
                }
            }
        }

        /// <summary> 
        /// An actor that needs to refer to stage to know what behavior to execute 
        /// </summary> 
        /// <param name="stage"></param> 
        public Actor(StageApp stage)
        {
            Stage = stage;
            m_Property = new Random().Next();
        }

        /// <summary> 
        /// Execute according to stage settings 
        /// </summary> 
        /// <returns></returns> 
        public int Execute()
        {
            return m_StageApp.SharedSetting * m_Property;
        }
    }
}

namespace AppExe
{
    using AppLib;

    class Program
    {
        static void Main(string[] args)
        {
            StageApp app = new StageApp();
            app.SharedSetting = 5;

            StageApp anotherApp = new StageApp();
            anotherApp.SharedSetting = 6;

            // actor is added to the stage automatically after instantiation
            Actor a1 = new Actor(app);
            Actor a2 = new Actor(app);
            Actor a3 = new Actor(anotherApp);

            Console.WriteLine("Actors in anotherApp before moving actor:");
            Console.WriteLine(anotherApp.TotalActorsCount);

            // or by calling method from StageApp class
            anotherApp.AddActor(a1);

            Console.WriteLine("Actors in anotherApp after calling method (should be 2):");
            Console.WriteLine(anotherApp.TotalActorsCount);

            // or by setting Stage through property
            a2.Stage = anotherApp;

            Console.WriteLine("Actors in anotherApp after setting property of Actor instance (should be 3):");
            Console.WriteLine(anotherApp.TotalActorsCount);

            Console.WriteLine("Actors count in app (should be empty):");
            Console.WriteLine(app.TotalActorsCount);
        }
    }
}

它允许您透明地操作对象关系,但需要一点 mor 代码来实现。

于 2012-08-22T16:04:09.443 回答
0

添加一个新类“ActorRole”如何定义每个阶段中演员的行为。它使您可以将 Actor 和 Stage 彼此分离,因此您可以独立地实例化两者(例如通过工厂),然后将它们组合起来,创建配置您的 Stage 的 ActorRole 对象。如果需要,可以使用 Builder 模式进行这种组合。

如果您需要动态更改您的演员行为,您可以使用基于 ActorRole 类的策略模式,因此您可以根据舞台为演员分配其行为的不同具体实现。

于 2012-08-22T14:04:12.210 回答
0

我会通过使用Func而不是传递StageActor. 像这样:

namespace AppExe
{
    using AppLib;
class Program
    {
        static void Main(string[] args)
        {
            StageApp app = new StageApp();
            app.CreateActor();
            app.SharedSetting = 5;
            foreach (var actor in app.Actors)
                Console.WriteLine(actor.Execute());
        }
    }
}

namespace AppLib
{
    class StageApp
    {
        public int SharedSetting { get; set; }
        public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
        private List<Actor> m_actors = new List<Actor>();
        public void CreateActor()
        {
            m_actors.Add(new Actor(Executed));
        }
        private int Executed(int arg)
        {
            return SharedSetting * arg;
        }
    }

    class Actor
    {
        private int m_Property;
        private Func<int, int> m_executed;

        public Actor(Func<int, int> executed)
        {
            m_executed = executed;
            m_Property = new Random().Next();
        }

        public int Execute()
        {
            return m_executed(m_Property);
        }
    }
}

我完全同意你的观点,循环引用并不好玩:)。

你也可以使用事件来解决这个问题,但我喜欢传递回调等函数。

于 2012-08-22T14:34:12.303 回答