Search code examples
c#oopdesign-patternsliskov-substitution-principle

How can i avoid breaking LSP in this example? C#


I have a base class called Message like this:

public abstract class Message 
{
    protected int m_id;
    protected bool m_localized;
    protected string m_metaData;

    public int GetID() { return m_id; }
    public bool GetLocalized() { return m_localized; }
    public string GetMetadata() { return m_metaData; }
}

Then, i have two more classes that inherit from Message for example:

public class ClassicMessage : Message
{
     private string m_title;
     private string m_content;

     public void SetTitle(string title) { m_title = title; }
     public void SetContent(string content) { m_content = content; }
     public string GetTitle() { return m_title; }
     public string GetContent() { return m_content; }
}

public class MessageWithCustomContent : Message
{
     private List<CustomContent> m_content;

     public MessageWithCustomContent() 
     {
          m_content = new List<CustomContent>();
     }

     public List<CustomContent> GetContent()
     {
          return m_content;
     }

     public CustomContent GetContentEntry(int id) 
     {
          return m_content.find(x => x.ID.Equals(id));
     }
}

public class CustomContent
{
     private int m_id;
     public int ID { get; set { m_id = value; } }
     private string m_body;
     public string Body { get { return m_body; } set { m_body = value; }
     private Image m_image; 
     public Image Image { get { return m_image; } set { m_image = value; } }
}

In a case like this, how can i unify the app interface if the derived classes has similar methods but these methods have different return types? (even when the methods try to do the same)

I know that with the example i'm breaking the Liskov Substitution Principle and the Open/Closed principle, what's the best approach to get around with that?

Thanks for your help!

Edit:

For more clarity, what i'm trying to achieve is to create a common interface to manage all the possible messages as the base "Message", because i want to avoid using typeof in the consumer class.

for example:

if(message is MessageWithCustomContent) 
{
         // do something with the contents.
}
else if(message is MessageWithCustomContent) 
{
       // do another thing with the contents.
}
etc...

Solution

  • You could change Message to be generic, and the T would specify the Content return type. See example below.

    Edit You could use a "IMessage" and a "Message: IMessage" as base. You would then be able to create a IMessage list like so

    var messages = new List<IMessage>
    {
        new ClassicMessage(),
        new MessageWithCustomContent()
    };
    foreach (var message in messages)
    {
        message.GetContent();
    }
    

    Below is how the implementation of IMessagecould be done.

    public interface IMessage
    {
        int GetID();
        bool GetLocalized();
        string GetMetadata();
        object GetContent();
    }
    
    public abstract class Message<T> : IMessage
    {
        protected int m_id;
        protected bool m_localized;
        protected string m_metaData;
    
        public int GetID() { return m_id; }
        public bool GetLocalized() { return m_localized; }
        public string GetMetadata() { return m_metaData; }
        object IMessage.GetContent()
        {
            return GetContent();
        }
        public abstract T GetContent();
    }
    
    public class ClassicMessage : Message<string>
    {
        private string m_title;
        private string m_content;
    
        public void SetTitle(string title) { m_title = title; }
        public void SetContent(string content) { m_content = content; }
        public string GetTitle() { return m_title; }
        public override string GetContent()
        {
            return m_content;
        }
    }
    
    public class MessageWithCustomContent : Message<List<CustomContent>>
    {
        private List<CustomContent> m_content;
    
        public MessageWithCustomContent()
        {
            m_content = new List<CustomContent>();
        }
    
        public CustomContent GetCustomContent(int id)
        {
            return null;
        }
    
        public override List<CustomContent> GetContent()
        {
            return m_content;
        }
    }
    
    public class CustomContent
    {
        private int m_id;
        public int ID { get; set; }
        private string m_body;
    
        public string Body
        {
            get { return m_body; }
            set { m_body = value; }
        }
    }