skip to Main Content

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


  1. You could modify your interface ISendMessage in a way that it knows whether it supports a subject or not:

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

    You would call it like that:

    if(_MessageSender.SupportsSubject)
     {
     _MessageSender.SendMessage("Message","[email protected]", "the subject"); 
     }
    else
     {
     _MessageSender.SendMessage("Message","[email protected]");
     }
    

    This approach is also used in the base class library. For example, the Stream class has a property which is called CanSeek. 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.

    Login or Signup to reply.
  2. 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 :

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

    then for a ISendEmail interface, you would require a specific IMessageParameters :

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

    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 :

    • An e-mail sender will put it in the subject field
    • A What’sApp sender could put it as the message first line
    • A SMS sender could just ignore it
    • Anyone has to treat their way of sending a message with a subject.
    Login or Signup to reply.
  3. What about using generics?

    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>
    {
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search