【问题标题】:SOLID principles in C# - How to implement more specific interfaceC# 中的 SOLID 原则 - 如何实现更具体的接口
【发布时间】:2020-10-25 02:12:31
【问题描述】:

我目前不熟悉清洁代码和 SOLID 原则这一主题。

我们为特定任务写了UseCase

using BusinessLogic.Classes.BusinessLogic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BusinessLogic.UseCases
{
    public class DoASpecificTask<T> : Interfaces.IUseCase
        where T : Interfaces.Connections.IQuery
    {
        T _Query;
        Interfaces.Connections.IConnection<T> _Connection;
        object _Parameters;
        string _ServiceName;
        Interfaces.ISendMessage _MessageSender;

        public DoASpecificTask(T query, Interfaces.Connections.IConnection<T> connection, object parameters, string serviceName, Interfaces.ISendMessage messageSender)
        {
            _Query = query;
            _Connection = connection;
            _Parameters = parameters;
            _ServiceName = serviceName;
            _MessageSender = messageSender;
        }

        public void Execute()
        {
            Interfaces.Connections.IQueryResultList resultList = ExecuteReadDataOnConnection<T>.GetList(_Query, _Connection, _Parameters);
            if (!ValidateQueryResultListItems.Validate(resultList))
            {
                EndImp3Service.EndService(_ServiceName);
                _MessageSender.SendMessage('Message','for@bar.com');
            }
            
        }
    }
}

据我了解 SOLID,解决方案/类/方法应该对扩展开放,对更改关闭(开放/封闭原则)。我们现在的问题是,我们创建了一个不具体的通用interface 用于发送消息

Interfaces.ISendMessage:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BusinessLogic.Interfaces
{
    public interface ISendMessage
    {
        void SendMessage(string message, string recipient);
    }
}

实际上,我们想通过电子邮件发送消息,因此我们创建了另一个接口ISendEmail,它实现了ISendMessage。邮件还有一个参数subject,在通用接口ISendMessage中没有实现(方法SendMessage())。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BusinessLogic.Interfaces
{
    public interface ISendEmail : ISendMessage
    {
        void SendMessage(string message, string recipient, string subject);
    }
}

现在我的问题是,我们如何才能在 UseCase 中不明确以保持对扩展的开放?可能是,在某些时候我们想要发送 WhatsApp 或 Telegram 消息而不是电子邮件。如果我在UseCase 中实现ISendMessage 的属性,我就没有subject 的参数。

我的意思是UseCase中的这一行:

_MessageSender.SendMessage('Message','for@bar.com');

因为我们指定了通用接口ISendMessage,所以我们不能在方法中提供subject。如果我们将ISendMessage 更改为ISendEmail,我们就不再营业了,不是吗?

这是怎么做到的,还是我们误解了一些完全错误的东西?


更新

首先,感谢您的 cmets 和解决方案。我认为,这正是我们作为 SOLID 初学者需要了解的问题,什么时候要具体化,而不是试图制作一个在任何情况下都可以做任何事情的程序。当然,SOLID 并不是说​​完全不改代码,问题是哪里需要改代码。

我认为,在所有提供的解决方案中,问题只是转移了。无论如何,我需要指定我在UseCase 中使用的确切类型。如果我要使用@Martin Verjans 的解决方案,我可以使用IMessage 类型,但是我必须在参数中具体说明。通过使用generics,我遇到了同样的问题。 @SomeBody 的解决方案,不符合 Open/Closed 原则。

我们的目标是在UseCase 中实现不特定,这样如果我们决定发送 WhatsApp 消息而不是电子邮件,我们就不需要更改 UseCase 本身。实际上我没有看到这个选项。

【问题讨论】:

  • 恕我直言:所有原则都只是抽象的,它们与任何语言都没有关系,所以你应该找到一种方法来实现它们。在这种情况下,您有 2 个参数,WHOM 和 WHAT。它可能不仅仅是字符串,它可能是复杂的对象或标识符。但是每个继承者将决定使用哪些字段。
  • @EgorPopov 是的,我知道,SOLID 并不特定于任何语言,而且我也知道,我也可以传递复杂的参数。但我不认为,这有助于解决这个问题。如果您有兴趣,请阅读我在原帖中的更新。

标签: c# solid-principles


【解决方案1】:

我猜要问的第一个问题是:“ISendMessage 接口的真正目的是什么?”

如果要能够通过任何有效的方式发送任何消息,也许只有一个消息和一个收件人是不够的。在这种情况下,您可以在通用界面中添加主题。

如果您有许多不同的方式来发送此消息,并且其中一些不需要主题,那么可能是实现 ISendEMail 的类的角色来生成主题。

如果你真的想保持通用,也许你可以将此接口与一个工厂结合起来,该工厂将根据给定的参数生成适当的接口。

另一种选择是拥有一个 IMessageParameters 接口,您可以将其传递给 ISendMessage,如下所示:

public interface IMessageParameters
{
}

public interface ISendMessage
{
    void SetParameters(IMessageParameters);
    void SendMessage(string message);
}

那么对于 ISendEmail 接口,您将需要特定的 IMessageParameters :

public interface ISendEmailParameters : IMessageParameters
{
    string recipient { get; set;}
    string subject { get; set;}
}

这一切都取决于您在这里真正想要实现的目标。虽然 SOLID 原则可以帮助您极大地整合您的代码,但不要试图创建一个在任何情况下都可以做任何事情的程序。

还要记住KISS principle,如果您的程序现在只发送电子邮件并且总是需要主题,请将其放在您的通用界面中。

最后一句话:对扩展开放,接近修改并不意味着永远不会更改您的源代码。这实际上意味着一旦构建了一个方法,您将不会更改该方法。但是您可以添加一个方法来做一些不同的事情或添加一个新功能。

更新

阅读您的更新后,我认为最好的解决方案是将主题作为 ISendMessage 接口的一部分。原因是您发送的任何消息都有主题。在一般生活中也是如此,每当您与某人交谈时,您都会想提出一个主题...

因此,实施者有责任按照自己的意愿处理主题:

  • 电子邮件发件人会将其放在主题字段中
  • What'sApp 发件人可以将其作为消息的第一行
  • 短信发送者可以忽略它
  • 任何人都必须以自己的方式发送带有主题的消息。

【讨论】:

  • 非常感谢您的详细解释!我想,你已经解决了我们的问题。请在原帖中查看我的更新。
【解决方案2】:

你可以修改你的界面ISendMessage,让它知道它是否支持一个主题:

public interface ISendMessage
    {
        bool SupportsSubject { get; }
        void SendMessage(string message, string recipient);
        void SendMessage(string message, string recipient, string subject);
    }

你可以这样称呼它:

if(_MessageSender.SupportsSubject)
 {
 _MessageSender.SendMessage("Message","for@bar.com", "the subject"); 
 }
else
 {
 _MessageSender.SendMessage("Message","for@bar.com");
 }

这种方法也用在基类库中。例如,Stream 类有一个名为CanSeek 的属性。当然,如果您在接口的实现中有几个这样的可选参数,这将变得不切实际,因为这会导致大量的 if 检查。

【讨论】:

  • 如果再增加一个方法-源代码会改变,所以不是开放\封闭原则。
【解决方案3】:

使用泛型怎么样?

public interface IMessageSender<TMessage>
{
    void SendMessage(TMessage message);
}

public class Message {
  string recipient { get; set; }
  string content { get; set; }  
}

public class EmailMessage : Message {
  string subject { get; set; }
}

public interface IEmailMessageSender : IMessageSender<EmailMessage>
{
}

public interface ISmsMessageSender : IMessageSender<Message>
{
}

【讨论】:

  • 非常感谢您的解释!请在原帖中查看我的更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多