C#,将消息显示代码与业务逻辑分离

3
我是一名有用的助手,可以为您进行文本翻译。以下是需要翻译的内容:

我有一个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);

这种方法是好的实践吗?
5个回答

2
不,这种方法不是一个好的实践。你的类和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);
   }
}

这就是为什么我问这个问题的原因。你能否举个例子来说明如何分解一个方法呢?例如,如果我有一个主窗体和一个类,其中包含一个返回未来日期的方法,假设该方法可能会抛出异常。我应该如何分解这个方法以显示消息?很抱歉,这可能是一件非常简单的事情,这是我建立的第一个应用程序,感谢您的帮助。 - shani
没有代码很难说什么。你可以尝试访问http://codereview.stackexchange.com/。 - default locale
我按照你建议的方法在基类中进行了修改,将方法分解并将异常抛到UI层。现在逻辑流由UI层控制。谢谢! - shani

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,通过消息框提示用户。在单元测试中,您可以模拟无法在静态设计案例中执行的视图。


2

在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并让表单调用控制器上的公共方法即可。

与其仅使用表单作为WinForms调用的代理,我更喜欢采用BDD方法,因此不要使用View.ShowPrompt("Do you want to delete this person?", "Deleting person"),而应选择类似于View.AskUserIfTheyWantToDeleteThePerson()(没有参数)。这是一个很长的方法名,但它非常明确,将实现和消息留给视图,并且可以在长期运行中使事情更清晰。


1

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

下面是我会做的步骤:

首先,为你的消息方法定义一些委托:

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

只要您选择适当的聚合函数 - ||&& - 或按bool分组并选择计数最高的一个,那么它就会很好地工作。
如果这有帮助,请告诉我。

我曾考虑过使用事件来完成这个任务,但最终决定将基类中的方法拆分开来,以便我的用户界面(UI)可以控制逻辑流程,并在必要时提示用户。感谢你的回答! - shani

1
通常,业务层会返回一个错误字符串。该字符串将由GUI在MessageBox或状态栏中显示。
顺便说一下,您可以从消息框中获取结果(无需对话框):
MessageBoxResult MBR = MessageBox.Show("Click Me", "Title", MessageBoxButton.YesNoCancel);

MessageBox.Show("You selected: " + MBR.ToString());

我不知道!我一直使用对话框结果,谢谢。关于将错误字符串返回到GUI,如果我的方法还需要返回另一个参数怎么办?如果我的方法需要返回一个int值,这该怎么办? - shani
您可以使用返回值来返回一个参数。其他参数可以使用out关键字返回:http://www.dotnetperls.com/out - Steve Wellens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接