在此示例中如何避免破坏 LSP? C#

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

我有一个名为 Message 的基础 class,如下所示:

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; }
}

然后,我还有两个 class 继承自 Message 例如:

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; } }
}

在这种情况下,如果派生的 classes 具有相似的方法但这些方法具有不同的 return 类型,我该如何统一应用程序界面? (即使方法试图做同样的事情)

我知道这个例子违反了里氏替换原则和 Open/Closed 原则,解决这个问题的最佳方法是什么?

感谢您的帮助!

编辑:

为了更清楚,我想要实现的是创建一个通用接口来管理所有可能的消息作为基础 "Message",因为我想避免在消费者中使用 typeof class.

例如:

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

您可以将 Message 更改为通用的,T 将指定 Content return 类型。请参阅下面的示例。

编辑 您可以使用 "IMessage" 和 "Message: IMessage" 作为基础。 然后您就可以像这样创建一个 IMessage 列表

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

下面是如何实现 IMessage 的。

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; }
    }
}

我将在下面解释你如何破坏 LSP,但在我这样做之前,你并没有真正进行任何继承。是的,您是在声明您的 class 继承,但您 并没有真正 继承任何东西。所以在学习 LSP 之前,或许你需要先了解一下继承。


我怎么知道我是否破坏了 LSP?

为了避免你的 Message class 是这样,请注意 virtualabstract 方法:

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

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

像这样创建一个列表:

var messages = new List<Message>();

然后将具体类型添加到所有继承类型的列表中。然后这样做:

foreach(var thisMessage in messages)
{
    var id = thisMessage.GetID();
    var loc = GetLocalized();
    var meta = GetMetadata();
}

如果您没有因为其中一个继承 classes 决定它不需要这些方法之一而抛出异常,那么您没有破坏 LSP。这个想法是,如果某些东西正在继承 Message,那么它应该继承一切。否则,我们无法安全而自信地用继承的 替代父代。

这个原则很重要的原因是因为可能存在使用 Message 的现有代码,如上面的 foreach 所示,它处理所有类型 多态性 开发人员决定像这样继承它:

public abstract class BadMessage 
{    
    public override int GetID() 
    { 
        throw new InvalidOperationException
           ("This method is not needed for BadMessage and should not be called"); 
    }
    public override bool GetLocalized() { ... }
    public override string GetMetadata() { ... }
}

你看这会破坏现有代码。最糟糕的是,编译器甚至无法捕捉到它,直到它像生产中的丑陋错误一样出现。

好吧,您缺少基础 class 中的接口方法。抽象函数,在派生 classes 中实现。如果你收到一条消息,不知道它是什么类型,你将如何请求它的内容? 您可以将 derivative-specific 方法添加到您的基础中,但您必须在基础 class 的虚拟实现中实现一个 not_implemented 异常,以补偿所有未实现它的派生,并添加异常处理。但是你应该问问自己:“这个 class 真的是一个导数吗?我想达到什么目的。”