Программирование на нескольких интерфейсах
Мне очень нравится подсказка: "Программа против интерфейса, а не реализация", и я стараюсь следовать ей последовательно. Однако я сомневаюсь, как сохранить этот принцип, когда я должен отделить свой код от объектов, которые должны наследоваться от нескольких интерфейсов. Типичным примером может быть:
namespace ProgramAgainstInterfaces
{
interface IMean
{
void foo();
}
class Meaning : IMean , IDisposable
{
public void Dispose()
{
Console .WriteLine("Disposing..." );
}
public void foo()
{
Console .WriteLine("Doing something..." );
}
}
class DoingSomething
{
static void Main( string[] args)
{
IMean ThisMeaning = (IMean ) new Meaning (); // Here the issue: I am losing the IDisposable methods
ThisMeaning.foo();
ThisMeaning.Dispose(); // Error: i cannot call this method losing functionality
}
}
}
Возможным способом решения этого может быть определение ad-hoc-интерфейса, который наследует оба интерфейса:
namespace ProgramAgainstInterfaces
{
interface IMean
{
void foo();
}
interface ITry : IMean , IDisposable
{
}
class Meaning : ITry
{
public void Dispose()
{
Console .WriteLine("Disposing..." );
}
public void foo()
{
Console .WriteLine("Doing something..." );
}
}
class DoingSomething
{
static void Main( string[] args)
{
ITry ThisMeaning = (ITry ) new Meaning (); // This works
ThisMeaning.foo();
ThisMeaning.Dispose(); // The method is available
}
}
}
но я не уверен, что это более компактное и эффективное решение: у меня могут быть более сложные множественные иерархии наследования, и это добавляет сложности, потому что я должен создавать интерфейсы только для работы в качестве контейнеров. Есть лучшее дизайнерское решение?
Ответы
Ответ 1
Если объект "IMean" всегда состоит из одноразового использования, тогда вы должны сделать интерфейс таким образом:
public interface IMean : IDisposable
{
...
}
Однако, если имеет смысл иметь объект, реализующий IMean, не будучи одноразовым, то я думаю, что решение, которое вы предлагаете, является лучшим: создать промежуточный интерфейс, чтобы вы могли:
public interface IMean
{
...
}
public interface IDisposableMean : IMean, IDisposable
{
...
}
Ответ 2
У вас должен быть interface
IDisposable
не класс Meaning
. Таким образом, при нажатии на interface
вы не теряете эту способность IDisposable
(потому что она определена на уровне interface
).
Вот так:
namespace ProgramAgainstInterfaces
{
interface IMean : IDisposable
{
void foo();
}
interface ITry : IMean
{
}
class Meaning : ITry
{
public void Dispose()
{
Console .WriteLine("Disposing..." );
}
public void foo()
{
Console .WriteLine("Doing something..." );
}
}
class DoingSomething
{
static void Main( string[] args)
{
ITry ThisMeaning = (ITry ) new Meaning (); // This works
ThisMeaning.foo();
ThisMeaning.Dispose(); // The method is available
}
}
}
Ответ 3
Вы также можете ввести общий тип T
, который должен реализовывать несколько интерфейсов. Вот пример использования IFoo
и IDisposable
:
class Program
{
static void Main(string[] args)
{
}
interface IFoo
{
void Foo();
}
class Bar<T> where T : IFoo, IDisposable
{
public Bar(T foo)
{
foo.Foo();
foo.Dispose();
}
}
}
Это немного сложно. Это может иметь смысл, если IFoo : IDisposable
является неправильным с точки зрения дизайна.
Ответ 4
Если у вас есть код, который требует типа, он реализует несколько разных интерфейсов, которые точно вам нужно делать. Но есть много вариантов того, что может произойти, в зависимости от семантики вашего кода.
Например, ваше собственное предлагаемое решение приемлемо, если IMean
не обязательно IDisposable
, но есть много потребителей, которые требуют, чтобы их IMean
были одноразовыми. Вы также можете использовать абстрактный базовый класс, чтобы сделать это - "программа для интерфейса" не использует "интерфейс", как в "конструкции языка, определенной ключевым словом interface
", а скорее "абстрактной версией объекта".
Фактически вы можете потребовать, чтобы любые типы, которые вы потребляете, реализуете ITry
(и, следовательно, одноразовые), и просто документируйте, что для некоторых типов отлично реализовать Dispose
как no-op. Если вы потребляете абстрактный базовый класс, вы также можете предоставить эту реализацию no-op по умолчанию.
Другим решением будет использование дженериков:
void UseDisposableMeaning<T>(T meaning) where T : IMean, IDisposable
{
meaning.foo();
meaning.Dispose();
}
// This allows you to transparently write UseDisposableMeaning(new Meaning());
Еще одним случаем будет потребитель, который строго требует только IMean
, но также должен быть осведомлен о необходимости. Вы можете справиться с этим, ловя рыбу для типов:
IMean mean = new Meaning();
var disposable = mean as IDisposable;
if (disposable != null) disposable.Dispose();
Хотя это приемлемое практическое решение (особенно учитывая, что IDisposable
является "не просто интерфейсом" ), вам обязательно нужно сделать шаг назад, если вы снова и снова будете делать это; в общем, любая форма "переключения типа" считается плохой практикой.
Ответ 5
Чтобы построить композиционно, вы можете просто проверить, поддерживает ли объект определенную часть функциональности (интерфейса) путем кастинга:
например.
// Try and cast
var disposable = ThisMeaning as IDisposable;
// If the cast succeeded you can safely call the interface methods
if(disposable != null)
disposable.Dispose();
Это означает, что вы обнаруживаете реализации во время выполнения, а не должны знать их во время компиляции, а ваши типы не нуждаются в реализации IDisposable
.
Это удовлетворяет одноразовому требованию, не зная, что IMean
имеет тип Meaning
(вы все еще можете работать с IMean
refs)
Ответ 6
Код для интерфейсов не для реализации - "интерфейсы" - общий термин, это не означает буквально ключевое слово interface
. Использование класса abstract
придерживается принципа. И, возможно, лучше, если вам нужна какая-то реализация. Кроме того, подкласс всегда может выполнять interface
по мере необходимости.
Ответ 7
Ваша альтернатива казалась в этом случае немного искусственной. Пусть IMean "реализует" IDisposable. Программирование против классов не всегда вредно, я зависим от ситуации.