Как я могу избежать взлома LSP в этом примере? C#
У меня есть базовый класс под названием сообщение, как это:
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; }
}
Затем у меня есть еще два класса, которые наследуются от сообщения, например:
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; } }
}
В таком случае, как я могу унифицировать интерфейс приложения, если производные классы имеют похожие методы, но эти методы имеют разные типы возвращаемых данных? (даже когда методы пытаются сделать то же самое)
Я знаю, что на примере я нарушаю принцип подстановки Лискова и принцип Открыто / Закрыто, как лучше всего обойти это?
Спасибо за вашу помощь!
Редактировать:
Для большей ясности я пытаюсь создать общий интерфейс для управления всеми возможными сообщениями в качестве базового "сообщения", потому что я хочу избежать использования typeof в классе потребителей.
например:
if(message is MessageWithCustomContent)
{
// do something with the contents.
}
else if(message is MessageWithCustomContent)
{
// do another thing with the contents.
}
etc...
3 ответа
Вы можете изменить Message на общий, а T будет указывать тип возвращаемого содержимого. Смотрите пример ниже.
Редактировать Вы можете использовать "IMessage" и "Message: 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, но прежде чем я это сделаю, вы на самом деле не делаете никакого наследования. Да, вы объявляете, что ваши классы наследуются, но на самом деле вы ничего не наследуете. Поэтому, прежде чем изучать LSP, возможно, вам нужно сначала получить контроль над наследованием.
Как я узнаю, что я ломаю LSP?
Дабы не сказать твой Message
Класс был таким, обратите внимание на виртуальные и абстрактные методы:
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();
}
Если вы не получите исключение, потому что один из наследующих классов решил, что ему не нужен ни один из этих методов, значит, вы не сломали 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() { ... }
}
Вы видите, что это сломает существующий код. И хуже всего то, что компилятор даже не сможет его перехватить, пока он не появится как ужасная ошибка в работе.
Ну, вам не хватает методов интерфейса в базовом классе. Абстрактные функции, которые реализуются в производных классах. Если вы получили сообщение, не зная, какого оно типа, как бы вы запросили его содержание? Вы можете добавить специфичные для производных методы в свою базу, но вам придется реализовать исключение not_implemented в виртуальной реализации в базовом классе, чтобы компенсировать все производные, не реализующие его, и добавить обработку исключений. Но тогда вы должны спросить себя: "действительно ли этот класс является производным? Чего я хочу достичь".