#c# #solid-principles
#c# #принципы solid
Вопрос:
В настоящее время я новичок в теме чистого кода и принципов SOLID.
Мы написали a 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, решение / класс / метод должны быть открыты для расширения, закрыты для изменений (принцип Open / Closed). Наша проблема сейчас в том, что мы создали неспецифический, общий 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 вместо электронной почты. Если я реализую свойство ISendMessage
в UseCase
, у меня нет параметра subject
.
Я имею в виду эту строку в UseCase
:
_MessageSender.SendMessage('Message','for@bar.com');
Поскольку мы указали общий интерфейс ISendMessage
, мы не можем предоставить a subject
в методе. Если мы изменим ISendMessage
на ISendEmail
, мы больше не открыты, не так ли?
Как это можно сделать или мы неправильно поняли что-то совершенно неправильное?
Обновить
Прежде всего, спасибо за ваши комментарии и решения. Я думаю, это именно та проблема, которую мы, как СОЛИДНЫЕ новички, должны понимать, когда нужно быть конкретным, а не пытаться создать программу, которая может делать что угодно в любом случае. Конечно, SOLID не означает, что код вообще не изменяется, но вопрос в том, где код нужно изменить.
Я думаю, что во всех предоставленных решениях проблема только смещается. В любом случае мне нужно указать, какой именно тип я использую в UseCase
. Если бы я использовал решение @Martin Verjans, я мог бы использовать тип IMessage
, но тогда я должен быть конкретен в параметрах. При использовании generics
у меня такая же проблема. Решение @SomeBody не соответствует принципу Open / Closed.
Наша цель — добиться того, чтобы быть неспецифичным в UseCase
, чтобы нам не нужно было изменять UseCase
само, если мы решим отправлять сообщения WhatsApp вместо электронной почты. И на самом деле я не вижу возможности для этого.
Комментарии:
1. ИМХО: все принципы просто абстрактны, они не относятся ни к какому языку, поэтому вы должны найти способ их реализации. В этом случае у вас есть 2 параметра, КТО и ЧТО. Это может быть не просто строка, это может быть сложный объект или идентификаторы. Но каждый наследник будет решать, какие поля использовать.
2. @EgorPopov Да, я знаю, что
SOLID
это не относится ни к одному языку, и я также знаю, что я тоже могу передавать сложные параметры. Но я не думаю, что это помогает в этой проблеме. Если вам интересно, пожалуйста, прочтите мое обновление в исходном сообщении.
Ответ №1:
Я думаю, первый вопрос, который нужно задать: «Какова реальная цель интерфейса ISendMessage ?»
Если нужно иметь возможность отправлять любое сообщение любым допустимым способом, возможно, просто иметь сообщение и получателя недостаточно. В этом случае вы могли бы добавить тему в общий интерфейс.
Если у вас есть много разных способов отправки этого сообщения, и некоторые из них не требуют subject, возможно, роль класса, который реализует ISendEMail, заключается в создании subject .
Если вы действительно хотите оставаться общим, возможно, вы можете связать этот интерфейс с фабрикой, которая будет генерировать правильный интерфейс в зависимости от заданных параметров.
Другим вариантом было бы иметь интерфейс 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, если ваша программа на данный момент отправляет только электронные письма и всегда требует тему, поместите это в свой общий интерфейс.
Последнее слово: открыть для расширения, закрыть для изменений не означает никогда не изменять исходный код. На самом деле это означает, что после создания метода вы не будете изменять этот метод. Но вы можете добавить метод в сторону, который немного отличается или добавляет новую функцию.
Обновить
После прочтения вашего обновления я думаю, что лучшим решением будет включить тему как часть интерфейса ISendMessage. Причина в том, что любое отправленное вами сообщение имеет тему. Это относится и к обычной жизни, есть тема, которую вы хотите затронуть всякий раз, когда разговариваете с кем-то…
Поэтому разработчики будут нести ответственность за то, чтобы относиться к предмету так, как они хотят :
- Отправитель электронной почты поместит его в поле тема
- Отправитель What’Sapp может поместить его в качестве первой строки сообщения
- Отправитель SMS может просто проигнорировать это
- Каждый должен относиться к своему способу отправки сообщения с темой.
Комментарии:
1. Большое спасибо за ваше подробное объяснение! Я думаю, вы столкнулись с нашей проблемой. Пожалуйста, посмотрите Мое обновление в исходном сообщении.
Ответ №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.
Комментарии:
1. если добавить еще один метод — исходный код будет изменен, так что это НЕ принцип Open closed.
Ответ №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>
{
}
Комментарии:
1. Большое спасибо за ваше объяснение! Пожалуйста, посмотрите Мое обновление в исходном сообщении.