【问题标题】:C#, separating message display code from the business logicC#,将消息显示代码与业务逻辑分离
【发布时间】:2017-07-08 11:45:54
【问题描述】:

我有一个 winforms 应用程序,但我没有遵循任何类型的设计模式。我的问题是,我有这些包含我所有业务逻辑的基类。当发生异常或需要向用户显示对话框时,我已将代码直接写入需要它的基类中。

我知道我需要分离我的业务逻辑和显示逻辑,所以我编写了一个静态类,其中包含显示消息所需的方法。

我的问题是,有没有更简单的方法可以将业务逻辑与显示分开?

我的静态方法是这样的,

public static void DisplayMessage(string message) 
    {
        MessageBox.Show(message);
    }

 public static bool DisplayDialogBox(string message,string caption ) 
    {
        DialogResult newresult = new DialogResult();

        newresult = MessageBox.Show(message,caption,MessageBoxButtons.OKCancel);

        if (newresult == DialogResult.OK)
        {
            return true;
        }
        else 
        {
            return false;
        }  

所以我会从基类调用这些方法,比如

MsgDisplay.DisplayMessage(e.Message);

这种方法是一种好的做法吗?

【问题讨论】:

    标签: c# winforms


    【解决方案1】:

    不,这种方法不是一个好习惯。您的类和 MessageBox 类之间没有任何区别。
    看,和你的班级一起:

    MsgDisplay.DisplayMessage(e.Message);
    

    只使用 MessageBox

    MessageBox.Show(e.Message);
    

    此包装器不提供任何附加功能。
    如果你想把业务逻辑和用户界面分开,那么你必须分解你的方法并只在用户界面层显示消息。
    而且,小点。而不是:

    if (newresult == DialogResult.OK)
        {
            return true;
        }
        else 
        {
            return false;
        }
    

    只需输入:

    return newresult==DialogResult.OK
    

    更新: 如果您只想显示异常消息,那么您应该捕获异常并在 UI 层上显示消息。因此,在您的商务舱中而不是显示消息:

    void foo() {
       try {
           //some code here
       }
       catch(FooException fe) {
           MessageBox.Show(fe.Message);
       }
    }
    

    向ui层抛出异常:

    void foo() {
       try {
           //...
       }
       catch(FooException fe) {
           //throw new BarException("Error occured: "+fe.Message); //if you want to customize error message.
           throw; //If you don't need to change the message consider to not catch exception at all
       }
    }
    

    然后在业务逻辑之外显示消息:

    void fooButton_Click(object sender, EventArgs args) {
       try {
            fooBusinessClass.foo();
       } catch(FooException fe) {
            //if(MessageBox.Show(fe.Message)==DialogResult.OK) fooBusinessClass.continuefoo(); //if you have several options
            MessageBox.Show(fe.Message);
       }
    }
    

    【讨论】:

    • 确实如此。这就是我问这个问题的原因。您能否举例说明如何分解方法?例如,如果我有我的 mainForm 和一个类,它的方法可以在未来返回一个日期,可以说。并且此方法可能会引发异常。我将如何分解显示消息的方法?对不起,这可能是一件非常简单的事情,这是我构建的第一个应用程序,非常感谢您的帮助。
    • 没有代码很难说什么。你可能想试试codereview.stackexchange.com
    • 我最终破坏了您在基类中建议的方法。并将异常抛出到 ui 层。现在逻辑流程由 ui 层控制。谢谢!
    【解决方案2】:

    我通常会创建一个IView 接口,然后将其注入到业务逻辑类中。如果逻辑“有问题”需要获取用户输入,那么它会像这样:

    interface IView
    {
         bool AskUser(string question);
    }
    
    class SomeClass
    {
         IView _View;
    
         public SomeClass(IView view)
         {
            _View = view;
         }
    
         public void SomeLogic()
         {
              if (someCondition && !_View.AskUser("continue?"))
                   return;
         }
    }
    

    然后您的表单可以实现IView 通过消息框提示询问用户。对于单元测试,您可以模拟出您在静态设计案例中无法真正做到的视图。

    【讨论】:

      【解决方案3】:

      在 WinForms 中实现简单的 MVC 式设计模式比您想象的要容易,而且您可以将其绑定到现有代码中而无需进行重大修改。让您的表单或控件实现视图接口,并将视图传递给实现您的业务逻辑的类:

      public interface IPersonDetailsView
      {
          bool PromptUser(string message, string caption);
      }
      // Your form:
      public partial class PersonDetailsForm : Form, IPersonDetailsView
      {
          //...
          public bool PromptUser(string message, string caption) {
              var result = MessageBox.Show(message, caption, MessageBoxButtons.OkCancel);
              return result == DialogResult.Ok;
          }
      }
      // Your business logic:
      public class PersonDetailsController {
          public IPersonDetailsView View { get; set; }
      
          public void DoingSomething() {
              // ...
              if (this.View.PromptUser(message, caption)) { ...
              }
          }
      }
      

      在创建表单时将PersonDetailsController.View 设置为表单。如果您需要表单能够与控制器对话,您只需添加 PersonDetailsForm.Controller 并让表单调用控制器上的公共方法。

      我会使用 BDD 方法,而不是仅仅使用表单作为 WinForms 调用的代理,因此我会选择 View.AskUserIfTheyWantToDeleteThePerson() 之类的东西(不带参数),而不是 View.ShowPrompt("Do you want to delete this person?", "Deleting person")。这是一个很长的方法名称,但它非常明确,将实现和消息留给视图,从长远来看可以使事情更清晰。

      【讨论】:

        【解决方案4】:

        一般来说,业务层会返回一个错误字符串。字符串由 GUI 显示在 MessageBox 或状态栏中。

        顺便说一句,你可以从一个消息框得到一个结果(你不需要一个对话框):

        MessageBoxResult MBR = MessageBox.Show("Click Me", "Title", MessageBoxButton.YesNoCancel);
        
        MessageBox.Show("You selected: " + MBR.ToString());
        

        【讨论】:

        • 我不知道!我一直在使用对话结果,谢谢。关于向 GUI 返回一个错误字符串,如果我有另一个需要从该方法返回的参数怎么办?如果我的方法需要返回一个 int 值怎么办?这将如何运作?
        • 可以使用返回值返回一个参数。其他参数可以使用 out 关键字返回:dotnetperls.com/out
        【解决方案5】:

        我会采取一种使用事件来呈现这种消息显示的方法。然后,您可以通过订阅事件轻松决定是否要记录。

        我会这样做:

        首先为您的消息方法定义几个委托:

        public delegate void DisplayMessage(string message);
        public delegate bool DisplayDialogBox(string message, string caption);
        

        这些可以用作业务逻辑类中的事件:

        public class BusinessLogic
        {
            public event DisplayMessage DisplayMessage;
            public event DisplayDialogBox DisplayDialogBox;
        
            protected void OnDisplayMessage(string message)
            {
                var dm = this.DisplayMessage;
                if (dm != null)
                {
                    dm(message);
                }
            }
        
            protected bool OnDisplayDialogBox(string message, string caption)
            {
                var ddb = this.DisplayDialogBox;
                if (ddb != null)
                {
                    return ddb(message, caption);
                }
                return false;
            }
        
            public void SomeMethod()
            {
                this.OnDisplayMessage("Hello, World!");
                var result = this.OnDisplayDialogBox("Yes or No?", "Choose");
                this.OnDisplayMessage(result.ToString());
            }
        }
        

        现在调用代码如下所示:

        var bl = new BusinessLogic();
        
        bl.DisplayMessage += MsgDisplay.DisplayMessage;
        bl.DisplayDialogBox += MsgDisplay.DisplayDialogBox;
        
        bl.SomeMethod();
        

        这在我的测试中效果很好。

        现在,一个警告 - DisplayDialogBox 委托返回一个 bool,因此当它用作事件处理程序时,您可以让多个订阅者附加到事件,然后只返回最后一个返回值,但所有订阅者将处理该事件。您可能会弹出对话框,用户说“否”,但下一个处理程序返回“是”,这就是返回的内容。

        有一个相对简单的解决方法。将return ddb(message, caption); 行替换为:

                    return ddb
                        .GetInvocationList()
                        .Cast<DisplayDialogBox>()
                        .Select(d => d(message, caption))
                        .Aggregate((b1, b2) => b1 || b2);
        

        只要您选择适当的聚合函数 - ||&amp;&amp; - 或按 bool 分组并选择计数最高的一个 - 那么它会很好用。

        如果这有帮助,请告诉我。

        【讨论】:

        • 我也考虑过使用事件来执行此操作,但决定分解我的基类中的方法,以便我的 UI 可以控制逻辑流,并在必要时提示用户。谢谢你的回答!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-12-15
        • 1970-01-01
        • 2011-07-03
        • 2011-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多