I’m currently new to the topic of Clean Code and SOLID principles.
We have written a UseCase
for a specific task.
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','[email protected]');
}
}
}
}
As far as I understood SOLID, a solution / class / method should be open for expansion, closed for changes (Open/Closed Principle). Our issue now is, that we have created an unspecific, general interface
for sending out a message
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);
}
}
Actually, we want to send out a message by email, so we created another interface ISendEmail
, which implements ISendMessage
. An email has also a parameter subject
, which is not implemented in the general interface ISendMessage
(method 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);
}
}
Now my question is, how can we be unspecific in the UseCase
to stay open for expansion? It could be, that at some point we want to send out WhatsApp or Telegram messages instead of an email. If I implement a property of ISendMessage
in the UseCase
, I do not have the parameter subject
.
I mean this line in the UseCase
:
_MessageSender.SendMessage('Message','[email protected]');
Because we specified the general interface ISendMessage
, we cannot provide a subject
in the method. If we change ISendMessage
to ISendEmail
, we are no longer open, aren’t we?
How can this be done or do we have misunderstood something completely wrong?
Update
First of all, thanks for your comments and solutions. I think, it is exactly the issue, that we as SOLID beginners, need to understand, when to be specific and not trying to make a program that can do anything in any case. Of course, SOLID does not mean, that no code is changed at all, but the question is where the code needs to be changed.
I think, in all provided solutions the problem is only shifted. In any case, I need to specify which exact type I use in the UseCase
. If I would use the solution of @Martin Verjans, I can use the type IMessage
, but then I have to be specific in the parameters. By using generics
, I have the same issue. The solution of @SomeBody, does not fulfill Open/Closed Principle.
Our goal is to achieve to be unspecific in the UseCase
, so that we don’t need to change the UseCase
itself, if we decide to send WhatsApp messages instead of email. And actually I don’t see an option for this.
3
Answers
You could modify your interface
ISendMessage
in a way that it knows whether it supports a subject or not:You would call it like that:
This approach is also used in the base class library. For example, the
Stream
class has a property which is calledCanSeek
. Of course, this becomes unpractical, if you have several of such optional parameters in your implementations of the interface, because this would result in a lot of if checks.I guess the first question to ask is : "What is the real purpose of the ISendMessage interface ?"
If it is to be able to send any message through any valid way, maybe just having a message and a recipient is not enough. In that case you could add the subject in the general interface.
If you have many different ways of sending this message and some of them do not require a subject, maybe it would be the role of the class that implements ISendEMail to generate a subject.
If you really want to stay general, maybe you can couple this interface with a factory that will generate the proper interface depending on the given parameters.
Another option would be to have a IMessageParameters interface that you would pass to the ISendMessage, like so :
then for a ISendEmail interface, you would require a specific IMessageParameters :
This all depend on what you really want to achieve here. Although SOLID principles help you greatly consolidate your code, do not try to create a program that can do anything in any case.
Remember also KISS principle, if your program for now only sends e-mails and always require a subject, put that in your general interface.
One last word : Open for extension, close to modifications does NOT mean to never change your source code. It actually means that once a method is built, you will not change that method. But you can add a method aside that does things a little different or adds a new feature.
UPDATE
After reading your update, I think the best solution is to include the subject as part of the ISendMessage interface. The reason would be that any message you sent has a subject. That goes in general life as well, there is a subject you want to come up with whenever you talk to someone…
So it will be the responsibility of implementers to treat the subject however they want :
What about using generics?