Интерфейсы использую, если это необходимо :-) Как я понимаю, что это необходимо, сказать сложно. Все зависит от задачи. Если рассматривать вопрос с позиции создания интерфейсов, то иногда это может быть очевидно, а иногда приходится подумать, стоит использовать интерфейсы или нет. Пихать их где попало - плохая идея.
Готовые интерфейсы, да, часто используются. Самым популярным в
.NET наверное будет
IDisposable :-)
Из общедоступных практических примеров использования собственных интерфейсов:
- Простой интерфейс, описывающий всего один метод.
interface ILoginForm
{
void WebDocumentLoaded(System.Windows.Forms.WebBrowser webBrowser, Uri url);
}
В проекте 100500 форм. Для некоторых форм может потребоваться индивидуальная обработка результатов, как здесь. А для других - нет. Все формы наследуются от базового класса, в котором я могу проверить, реализует текущий экземпляр интерфейс с индивидуальным обработчиком результатов или нет, и вызвать его.
if (typeof(ILoginForm).IsAssignableFrom(this.GetType()))
{
this.CanLogin = false;
Debug.WriteLine("ILoginForm", "LoginForm");
((ILoginForm)this).WebDocumentLoaded(this.webBrowser1, e.Url);
}
- Вот другой пример интерфейса.
export interface ILocalization {
Loading: string;
LoadingFileContents: string;
// ...
}
Файлы с ресурсами локализации реализуют этот интерфейс.
export class RU implements ILocalization {
public Loading: string = 'Загрузка...';
public LoadingFileContents: string = 'Получение содержимого файла...';
// ...
}
Это просто упрощает работу с кодом и больше ничего.
Можно было бы использовать обычные текстовые ключи, но тогда программистам пришлось бы постоянно смотреть, какие есть ключи, делать много лишних движений и вероятность ошибок возрастает.
- Еще пример интерфейса для вспомогательных классов работы с базами данных. Он просто описывает, каким должен быть класс.
interface IDBClient
{
public function ExecuteNonQuery();
public function ExecuteScalar();
public function GetRow();
// ...
}
А толку от этого никакого.
Интерфейсы для слоев взаимодействия с БД я делаю частенько, с замахом на замену СУБД, но сделать это красиво в любом случае не получится, все равно придется вносить корректировки при изменении источника данных, так что практической пользы от таких интерфейсов мало, но иногда она есть, только для других целей.
- А вот тут я реализовал множество готовых интерфейсов, чтобы получить нужные свойства и поведение класса. Например, интерфейс IConvertible позволяет адекватно реагировать на Convert.To*. Интерфейс ISerializable позволяет описать правила сериализации экземпляра класса. И т.п.
// реализация метода ToInt32
public int ToInt32(IFormatProvider provider)
{
// если пусто, возвращаем ноль
if (!this.HasValue) { return 0; }
// если что-то есть, извлекаем числа и пробуем вернуть int32
return Convert.ToInt32(OAuthUtility.GetNumber(this.Data));
}
- Вот пример, где можно было использовать интерфейсы, но я принял решение в пользу базовых классов (на раннем этапе). Некоторые поставщики OAuth позволяют обновлять маркер доступа и/или отзывать его, но не все. В базовом классе нижнего уровня для определениях этих особенностей я сделал два свойства, причем даже не абстрактных.
public bool SupportRevokeToken { get; protected set; }
public bool SupportRefreshToken { get; protected set; }
Клиенты, которым нужны эти свойства, задают их в конструкторе, выглядит это так.
public GoogleClient(string clientId, string clientSecret) : base(/*...*/)
{
// ...
base.SupportRevokeToken = true;
base.SupportRefreshToken = true;
}
Не очень красиво, но ошибкой это назвать нельзя. Если бы я использовал интерфейсы, то для отзыва и обновления маркера мне бы пришлось делать два разных интерфейса, в каждом по одному bool свойству, либо пустых (но с явно определенным свойством для других программистов все было бы очевидней; это тоже спорный вопрос). Можно было пойти дальше и измельчить на интерфейсы другие особенности, но это было бы плохим решением, т.к. программистам пришлось бы указывать портянку интерфейсов, а потом писать много кода. С базовыми классами код писать не нужно, даже не нужно вникать в то, как это работает; в большинстве случаев достаточно просто выполнить простую конфигурацию дочернего класса в конструкторе. Но я не могу утверждать на 100%, что это лучшее решение для данного случая.
- Пример, где ReactJS и TypeScript заставляют клепать интерфейсы и я этому совсем не рад.
export interface ILoginState {
Username?: string;
Password?: string;
// ...
}
export default class Index extends Page<any, ILoginState> {
constructor() {
this.state = {
Username: '',
Password: ''
};
}
// ...
}
Можно забить и использовать анонимные типы, но для порядка и удобства работы с кодом придется открывать собственный завод по производству интерфейсов :-)