Skip to content

Instantly share code, notes, and snippets.

@jaquiel
Created December 5, 2025 11:06
Show Gist options
  • Select an option

  • Save jaquiel/c30bc132307d8f687fb577dccbcddf58 to your computer and use it in GitHub Desktop.

Select an option

Save jaquiel/c30bc132307d8f687fb577dccbcddf58 to your computer and use it in GitHub Desktop.
@jaquiel
Copy link
Author

jaquiel commented Dec 5, 2025

Answer: The quality of this code is low.

Issues that cause low code quality:

If in the future I need to introduce new Message classes, like MessageD, the if/else if block will continue to grow, and I will need to add new cases, increasing the risk of introducing bugs.

Multiple Type Casting/Checking:

  • (message is MessageA) and other similar: There are 2 problems here, repeated type checks and hard casting
  • (messageA?.): repeatedly null checks for each specific type

Maintainability

The message processing logic is centralised; if this feature grows larger, it will become harder to manage, as more message types and methods are added.

Design Patterns and Design Principles:

The use of a large sequence of if statements is a code smell and could be classified as a Switch Statements Anti-Pattern
The code violates the Open/Closed Principle (OCP) principle

My alternative suggestion

Code Refactoring, implementing Polymorphism and Strategy Pattern

Step 1: Defining an IMessage Interface, because all message types have a common action

public interface IMessage
{
    // The method that implements the common actions between the message types
    void Process();
}

Step 2: Now, each message class implements the IMessage interface and moves its specific logic into the Process() method

public class MessageA : IMessage
{
    public void Process()
    {
        // Equivalent to: messageA?.MyCustomMethodOnA();
        MyCustomMethodOnA();
    }

    public void MyCustomMethodOnA() { /* ... */ }
}

public class MessageB : IMessage
{
    public void Process()
    {
        // Equivalent to: messageB?.MyCustomMethodOnB();
        // Equivalent to: messageB?.SomeAdditionalMethodOnB();
        MyCustomMethodOnB();
        SomeAdditionalMethodOnB();
    }

    public void MyCustomMethodOnB() { /* ... */ }
    public void SomeAdditionalMethodOnB() { /* ... */ }
}

public class MessageC : IMessage
{
    public void Process()
    {
        // Equivalent to: messageC?.MyCustomMethodOnC();
        MyCustomMethodOnC();
    }

    public void MyCustomMethodOnC() { /* ... */ }
}

Step 3: Now, independently of the number of existing type messages, the code doesn’t change and provides a solution in just one line

public void HandleMessage(IMessage message) 
{ 
     message.Process(); 
}

Bonus: Or, it could also be used like this

public class SendMessage
{
    private readonly IMessage _message();
    
    public SendMessage(IMessage message)
    {
         _message = message;	
    } 	
     
    public void HandleMessage()
    {
         /* ... */
         _message.Process()
        /* ... */
     }
}

//
_sendMessage(new MessageA())

//
_sendMessage(new MessageB())

//
_sendMessage(new MessageC())

Improvements (Conclusion)

Now, with these changes, we have implemented the OCP Principle:
If I need to add a new MessageD or more, I can just create a class that implements IMessage and put its logic in Process() without changing the HandleMessage.

In addition to the OCP Principle, we are following the Single Responsibility Principle, because each message class implements its own responsibility.

The code is now clean, decoupling, easy to maintain, and testable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment