Используя в сигнатурах клиентских методов и классов или интерфейс или тривиальный класс, который реализует данный интерфейс, вы тем самым неявно разделяете код на 2 части.
Это особенно проявляется, если объявить класс и интерфейс для DTO (domain transfer object). Он по своей сути много где используется и сможет заафектить много кода.
interface IMessage {
string Id {get;}
}
class Message: IMessage {
public string Id{get;}
}
Теперь где-то в коде
interface ISomeService {
void Do(IMessage m);
}
interface IOtherService {
void DoOther(Message m);
}
class Service : ISomeService, IOtherService {
public void Do(IMessage m);
void DoOther(Message m);
}
В чем проблема?
Дело в том, что методы Do и DoOther могут порождать другие классы и интерфейсы и таким образом произойдет неявный сплит вашего кода на 2 части – те, кто работают с IMessage и с Message.
Проблема усугубляется, если вы используете генерики
interface ISomeService<T> {
void Do(T m);
}
interface IOtherService<T> {
void DoOther(T m);
}
interface IThirdService<T> {
void Do(T m);
void DoOther(T m);
}
class Service : ISomeService<IMessage>, IOtherService<Message>,
IThirdService<?> {
public void Do(IMessage m);
void DoOther(Message m);
}
Решение этой проблемы достаточно очевидно – с самого начала использовать одну из 2-х сущностей везде – класс или интерфейс (для DTO в C# удобно использовать record).
Если вы уже попали в эту ловушку, то можно попробовать привести меньшую по объему ветку кода к общему типу данных.
Эта проблема кажется игрушечной. Однако, чтобы ее решить, вам нужно будет потратить значительную часть времени соизмеримую с объемом кода, на котором она наблюдается. Причем вам потребуется признать себя идиотом (ведь это идиотская проблема, так?). Это спровоцирует вас не решать ее, когда вы поймете, что что-то не так. И так до тех пор, пока вы не столкнетесь с чем-то похожим на пример выше, когда игнорировать проблему уже невозможно. Тогда вы поймете, что вы попали в ловушку.