解决库设计 - 集中配置参考

时间:2012-08-22 13:46:24

标签: c# design-patterns dll circular-dependency

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

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());
        }
    }
}

问题

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

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

如果我给Actor()一个内部构造函数并使Stage成为工厂, 然后我失去了一些用户做继承Actor的灵活性。

如果我将Stage设为单身,那么我只能拥有一套SharedSetting。 如果用户在Stage中想要多个AppExe,则无法完成。

有没有重新设计架构以避免上述问题?

3 个答案:

答案 0 :(得分:1)

如果您的功能不受在actor之间共享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代码来实现。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

我会使用Func而不是将Stage传递给Actor来解决此问题。像这样:

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);
        }
    }
}

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

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