【问题标题】:Standardization with Interface in .Net.Net 中的接口标准化
【发布时间】:2014-09-29 13:54:12
【问题描述】:

我有一个关于 OOP 和接口的奇怪问题,在尝试寻找最佳设计时让我一头雾水。

有两个不同的类在不同的环境中进行相同的工作(例如发送消息)。这两种环境使用不同的参数来定义接收者;一种是使用邮件地址,另一种是使用用户名。 所以这两个类都有相同但略有不同的消息发送方法。

MessageSenderViaMailManager.cs

public bool SendMessage(string recipientMailAddress, string message) {
..
}

MessageSenderViaUsernameManager.cs

public bool SendMessage(string recipientUserName, string message) {
..
}

两个类中还有其他类似的方法,负责相同的工作,但可能需要不同的参数。 为了使这些具有界面的管理器可用,我创建了一个名称为 IMessageSenderManager 并包含这样的定义的管理器。

public bool SendMessage(string recipientUserName, string recipientMailAddress, string message);

因此,我的课程中的两个 SendMessage 方法都更改为:

public bool SendMessage(string recipientUserName,string recipientMailAddress, string message) {
 ..
}

使用这个新的 SendMessage 方法,我可以使用适当的参数作为收件人(邮件地址或用户名)。这看起来不错,但实施看起来很奇怪。因为我必须发送所有参数,而不知道在编码时将在运行时使用哪个参数。例如:

// Sending message via username implementation
string userName = GetUserNameFromSomeWhere();
string mailAddress = GetUserMailFromSomeWhere();
IMessageSenderManager manager = MessageSenderFactory();
manager.SendManager(userName, mailAddress, "This messaged sent by your user name");

和上面的代码一样,通过邮件地址发送消息看起来很相似。

在我看来,这不是一个好的设计,所以我开始考虑更好的解决方案。因为如果我想实现另一个 MessageSender 提供者,它使用不同的接收者描述符,我必须向我的接口添加另一个参数,所以对所有类。 我想,我可以用一个通用收件人参数更改两个收件人参数,并为上下文发送适当的值。但我试图在动态环境中使用它,并且方式(通过用户名或邮件)将在运行时确定,所以我不能使用它。

我打算制作一个灵活的库,可以解耦使用或对其他开发人员友好地进行单元测试,我不想将它们与无意义的参数或糟糕的设计混淆。

对于这种情况,有没有更好的设计?

编辑:

实际上是我的错误,我忘记了我的问题中非常重要的部分,对此我深表歉意。 正如您从答案中看到的那样,有一些替代方法可以解决我上面描述的第一个问题。但是之后? 我在代码中提到,接口是从 MessageSenderFactory() 方法返回的,但我不知道返回的是哪个消息发送管理器。所以我有两个选择,

  1. 编写 if 条件以检查从方法返回的管理器,并为该管理器发送具有适当值的强制参数,并将其他参数发送为空。
  2. 无论经理是谁,都发送具有正确值的所有参数,因此它们都可以正常工作。但将来,如果添加了另一个经理,我每次都需要为该经理发送额外的参数。
  3. 还有其他我想不到的方法吗?

除了 SendMessage 之外的其他方法可能需要根据管理器的不同参数,这在运行时是未知的。例如:

MessageSenderViaMailManagerAddContact 方法可能需要以下参数:

  • 联系人姓名
  • 联系邮箱
  • 联系电话
  • 联系邮件类型(丰富、普通)

MessageSenderViaUserNameManagerAddContact方法可能需要以下参数:

  • 联系人姓名
  • 联系邮箱
  • 联系人用户名
  • 联系消息平台(Twitter、facebook、vs)
  • 联系人发件人姓名

所以这让一切变得非常复杂。 我的 IMessageSenderManger 的 AddMethod 应该如何?它应该包含所有参数吗?我应该超载吗?或者我应该将公共参数放在方法中,并使其他参数因经理而异(如 MVC 中的 HtmlHelper)

我知道这个问题不是很扎实,而且我不擅长用英语描述。

GitHub 编辑:

我创建了一个小例子并上传到github,我希望这可以帮助我更好地解释我的问题 https://github.com/bahadirarslan/InterfaceDesign

【问题讨论】:

  • User 对象传递给SendMessage 方法怎么样?
  • SendMessage 的实现如何?
  • 对于初学者来说,既然你问“什么是最好的”,你的问题是“基于意见的”,所以我投票结束,我没有添加答案。此外,这是一个非常本地化的问题,它对未来的读者几乎没有帮助,因为即使是标题也太模糊,无法帮助任何人通过搜索查询甚至谷歌找到它。这就是近距离投票的原因。不过,我可以看到你的问题,我会尝试在 cmets 中回复。
  • @quetzalcoatl 我不同意,这是如何基于意见的? OP 在这里有一个特定的问题要解决。虽然我同意这个问题的标题写得不好,如果改变的话。它肯定会对未来的读者有所帮助。
  • 实际上,我只是注意到我想写的所有内容都可以压缩为 KlausByskovPedersen 已经说过的内容:更改 string email/string username 以便 SendMessage 将 User 对象作为目标,并且底层的 SendMessage 实现将能够从 User 对象中选择它需要的任何内容。如果您没有 User 之类的东西,请传递 UserID(假设您的用户有它)..

标签: c# oop interface


【解决方案1】:

更新: 为清楚起见,对以前的答案进行了编辑。

我已更改代码以利用您的结构。

public static class MessageSenderManagerFactory
{
  public static IMessageSenderManager Create(IRecipient recipient)
  {
    return new MessageSenderManager { Recipient = recipient };
  }
}

public interface IMessageSenderManager
{
  public IRecipient Recipient { get; set; }
  bool SendMessage(string message);
}

public class MessageSenderManager : IMessageSenderManager
{
  public IRecipient Recipient { get; set; }

  public bool SendMessage(string message)
  {
    // At this point you construct the actual message and sending mechanism.
    // You'll have all the information you need in the TheUser property of Recipient.
    // The following is an example how this can be implemented but since you have not
    // provided what information you need to send or HOW you send the message I can't
    // be more specific.

    var messageToSend = new Message(message);
    messageToSend.Address = Recipient.GetRecipientAddress();

    messageToSend.Send();
  }
}

public interface IRecipient
{
  public string GetRecipientAddress();
}

public abstract class RecipientBase
{
  public User TheUser { get; set; }
  private RecipientBase() { }
  protected RecipientBase(string userId) { TheUser = FindUserById(userId); }
}

public class MailRecipient : RecipientBase, IRecipient
{
  public MailRecipient(string userId) : base(userId) { }
  public string GetRecipientAddress() { return TheUser.Mail; }
}

public class UserNameRecipient : RecipientBase, IRecipient
{
  public UserNameRecipient(string userId) : base(userId) { }
  public string GetRecipientAddress() { return TheUser.UserName; }
}

因此,当您拥有用户 ID 时,您可以使用 一个 以下行,具体取决于收件人的类型(就像您使用 switch-您的 git 示例中的情况):

var manager = MessageSenderManagerFactory.Create(new MailRecipient(userId));
var manager = MessageSenderManagerFactory.Create(new UserNameRecipient(userId));

使用哪种类型的Recipient 的逻辑不应基于用户id。数据库或User 对象中应该有一个标志或设置来指定它。

然后发送消息:

manager.SendMessage(message);

免责声明: 代码未经测试。

【讨论】:

  • 我写的是完全一样的东西+1
  • 感谢您的建议,这种方法似乎也不错。
  • @SriramSakthivel 我更新了问题,你能再看一遍吗?
  • @SriramSakthivel:如果您查看我的示例,您会发现只有一种 SendMessage 方法。在那里,您可以检查实现接口的实际类。您可以使用关键字is 来完成。 IE。 if (Recipient is EmailRecipient) ...
  • @SriramSakthivel:再说一遍:我们没有足够的信息给你一个具体的例子。您必须提供更具体的信息。您说您从数据库中获取值。什么价值观?电子邮件?用户名?如果从数据库中获取值必须知道它是什么。对吗?
【解决方案2】:

为什么不让你的界面如下:

public bool SendMessage(string recipient, string message) {
..
}

然后在每个类的逻辑中添加一些代码来验证收件人信息,例如,您需要一个电子邮件地址的实现,添加验证以检查您传递的是一个有效的电子邮件地址。 然后在您需要用户名的实现中,验证您是否可以找到该用户。

您始终可以在您的工厂中实现该检查,以便它知道要创建哪个类。

【讨论】:

  • 感谢您的回答,但我不能接受。因为这并不能解决任何问题;此外,如果需要添加另一种发送消息的方式,将来会导致更多问题。我也不想检查字符串值是电子邮件还是其他。
【解决方案3】:

我将创建一个包含收件人属性的class MailMessage。然后在那个MailMessage 类上有两个AddRecipient 方法的重载。一种用于添加收件人的每种方式。

【讨论】:

  • 可以考虑这个解决方案。谢谢建议
【解决方案4】:

设计始终是一组特定要求的结果。 OO 也没有什么不同:几乎有无数种方法可以实现可以在各种情况下发送电子邮件的库。哪种设计/实现好完全取决于您的情况。因此,除非您透露有关您的要求的更多细节,否则询问“什么设计是最好的”是没有意义的:

为什么你想要/需要重新设计你已经实现的东西?

尽管如此,我同意您选择的抽象(一种具有多个参数的方法,这些参数是可选的或互斥的,具体取决于客户拥有的信息),看起来很糟糕。通常,最好将方法和类设计得简单明了,这样每个参数都有意义。换句话说:恕我直言,您的第一个具有两个不同类的设计比您的第二个更好,因为它更符合Single Responsibility Principle

【讨论】:

    猜你喜欢
    • 2014-12-09
    • 1970-01-01
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 2010-10-14
    • 2010-10-15
    相关资源
    最近更新 更多