【问题标题】:Implementing open close principle when interfaces are different接口不同时实现开闭原则
【发布时间】:2019-09-16 04:50:23
【问题描述】:

我有以下工厂安全系统用例。目前将系统设计为控制台应用程序。 - 机器的速度可以通过人工输入来增加或减少 - i 或 d - 当速度增加到 50 以上时,会发出警报 - 机器中有一个安全锁,可以通过人工输入再次锁定或解锁 - l 或 u - 如果机器速度增加 10 并且安全锁未锁定,则发出警报

我已经实现了该系统,但是该系统不可扩展。明天如果引入另一个安全系统,那么看来我需要回到现有的班级进行修改。

实现打开关闭原则看起来很困难,因为速度增加/减少 (ISpeedController) 和机器锁定/解锁 (ISafetyLock) 等功能不共享一个通用接口。

public interface ISpeedController
{
    void Increase();
    void Decrease();
}

public interface ISafetyLock
{
    void Lock();
    void UnLock();
}

此外,还有一个事件速度变化,只要速度改变就会触发以引发警报。这让事情变得更加复杂。

您能否帮我设计一下系统,以便我们将来可以在不影响当前代码的情况下无缝添加新的安全措施?

谢谢。

【问题讨论】:

  • 未来会有任何安全措施吗?听起来像是 YAGNI 的案例。
  • 感谢马库斯的评论。将来肯定会增加安全措施。即使我认为将来不会添加任何内容,但当前的代码看起来还是很笨拙。例如 - if(userPresses == i) ISpeedController 上的呼叫增加 else if(userPresses == d) ISpeedController 上的呼叫减少 else if(userPresses == l) ISafetyLock 上的呼叫锁定 else if(userPresses == u) ISafetyLock 上的呼叫解锁...我们能不能用开闭原则来实现代码
  • 考虑到格式化后,它易于阅读和理解,我更喜欢“笨拙的外观”而不是高度抽象的任何东西,因此可读性差,进而容易出错。特别是当 - 就像你的情况一样 - 我们正在谈论安全措施。

标签: c# solid-principles


【解决方案1】:

好吧,您只需要使用convention over configuration 方法即可。

例如,你可以定义一个带有注册的通用接口:

using System;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var handlerType = typeof(IHandleKey);
        var classes = typeof(Program).Assembly // you can get them however you want
            .GetTypes()
            .Where(p => handlerType.IsAssignableFrom(p) && p.IsClass)
            .Select(t => (IHandleKey)Activator.CreateInstance(t)) // or use IoC to resolve them...
            .ToArray();

        while(true) {
            var key = Console.ReadLine(); // or however you get your input
            var handler = classes.FirstOrDefault(x => x.Key == key);

            if (handler == null) {
                Console.WriteLine("Couldn't find a handler for " + key);
            } else {
                handler.Handle();
            }
        }
    }
}

public interface IHandleKey 
{
    String Key { get; }
    void Handle();  
}

public class Banana : IHandleKey 
{
    public String Key { get { return "u"; } }
    public void Handle() 
    {
        Console.WriteLine("I did banana work");
    }
}

这样,如果您需要开发一项新功能,您只需添加一个包含有效密钥和实现逻辑信息的类。

同样,如果您不想让实例准备好处理命令,您可以拆分它并在类型上设置一个描述键的属性,如下所示:

using System;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var handlerType = typeof(IHandleKey);
        var classes = typeof(Program).Assembly
            .GetTypes()
            .Where(p => handlerType.IsAssignableFrom(p) && p.IsClass && p.GetCustomAttributes(typeof(KeyHandlerAttribute), false).Count() > 0) // note we're checking for attribute here. This can be optimised.
            .ToArray();

        while(true) {
            var key = Console.ReadLine(); // or however you get your input
            var concreteType = classes.FirstOrDefault(x => ((KeyHandlerAttribute)(x.GetCustomAttributes(typeof(KeyHandlerAttribute), false).First())).Key == key);

            if (concreteType == null) {
                Console.WriteLine("Couldn't find a handler for " + key);
            } else {
                var handler = (IHandleKey)Activator.CreateInstance(concreteType); // or use IoC to resolve them...
                handler.Handle();
            }
        }
    }
}

public interface IHandleKey 
{
    void Handle();  
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class KeyHandlerAttribute: Attribute 
{
    public String Key { get; set; }
}

[KeyHandler(Key = "u")]
public class Banana : IHandleKey 
{
    public void Handle() 
    {
        Console.WriteLine("I did banana work");
    }
}

更新这是使用第二种变体并实现共享状态和基于事件的通信的更新程序列表。

老实说,我觉得这很琐碎,所以请随时提出任何问题,因为我不确定什么会显得更令人困惑,什么不会...

using System;
using System.Linq;
using System.Collections.Generic;

namespace Whatever
{
    public class Program
    {
        public static void Main()
        {
            // This part belongs to IoC as a Singleton
            var state = new State();

            // This part belongs to IoC as scoped services
            var handlerType = typeof(IHandleKey);
            var dict = new Dictionary<String, Object>();

            foreach (var type in typeof(Program).Assembly
                .GetTypes()
                .Where(p => handlerType.IsAssignableFrom(p) && p.IsClass))
            {
                var attributes = type.GetCustomAttributes(typeof(KeyHandlerAttribute), false);
                if (attributes.Any())
                {
                    var attribute = (KeyHandlerAttribute)attributes.First();
                    var handlr = (IHandleKey)Activator.CreateInstance(type);
                    handlr.RegisterEvent(state);
                    dict.Add(attribute.Key, handlr);
                }
            }

            // Main routine here
            while (true)
            {
                var key = Console.ReadLine(); // or however you get your input
                var handler = dict.ContainsKey(key) ? (IHandleKey)dict[key] : null;

                if (handler == null)
                {
                    Console.WriteLine("Couldn't find a handler for " + key);
                }
                else
                {
                    handler.Handle();
                }
            }
        }
    }

    // This class allows us to share state.
    public class State : ISharedState
    {
        // As required by the question, this is an event.
        public event EventHandler StateChanged;
        public void RaiseStateChange(object sender)
        {
            this.StateChanged.Invoke(sender, new EventArgs());
        }
    }

    // This makes our Handlers unit testable.
    public interface ISharedState
    {
        event EventHandler StateChanged;
        void RaiseStateChange(object sender);
    }

    // Familiar interface -> note how we have a 'register event' method now.
    // We could instead just use a constructor on the HandlerBase. This is really dealer's choice now.
    public interface IHandleKey
    {
        void Handle();
        void RegisterEvent(ISharedState state);
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class KeyHandlerAttribute : Attribute
    {
        public String Key { get; set; }
    }

    // To avoid boilerplate in our handlers for registering/unregistering events we have a base handler class now.
    public abstract class HandlerBase: IHandleKey
    {
        protected ISharedState _state;
        public abstract void Handle();
        public void RegisterEvent(ISharedState state)
        {
            this._state = state;
            this._state.StateChanged += OnStateChanged;
        }

        public abstract void OnStateChanged(object sender, EventArgs e);

        ~HandlerBase()
        {
            this._state.StateChanged -= OnStateChanged;
        }
    }

    // Actual handlers...
    [KeyHandler(Key = "u")]
    public class Banana : HandlerBase
    {
        public override void Handle()
        {
            Console.WriteLine("I did banana work");
            this._state.RaiseStateChange(this);
        }

        public override void OnStateChanged(object sender, EventArgs e)
        {
            if (sender != this) // optional, in case we don't want to do this for self-raised changes
            {
                Console.WriteLine("State changed inside Banana handler");
            }
        }
    }

    [KeyHandler(Key = "c")]
    public class Cheese : HandlerBase
    {
        public override void Handle()
        {
            Console.WriteLine("I did cheese work");
            this._state.RaiseStateChange(this);
        }

        public override void OnStateChanged(object sender, EventArgs e)
        {
            if (sender != this) // optional, in case we don't want to do this for self-raised changes
            {
                Console.WriteLine("State changed inside cheese handler");
            }
        }
    }
}

【讨论】:

  • 在我的场景中,IHandleKey - SpeedController 和 SafetyLock 类的实现必须引发一个事件以在速度改变或 SafetyLock 被锁定或解锁时相互关联。这样,这些类将知道何时发出警报。您知道我们如何处理这种情况吗?感谢您详尽的回答。
  • @App 查看更新 - 现在列表有点长,但它可以满足您的要求。注意:我可能不会为事件而烦恼,只是推出了我自己的Task.Run(),但这真的取决于你的工作流程。
猜你喜欢
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 2021-10-16
  • 2020-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多