Подтвердить что ты не робот

Программирование на нескольких интерфейсах

Мне очень нравится подсказка: "Программа против интерфейса, а не реализация", и я стараюсь следовать ей последовательно. Однако я сомневаюсь, как сохранить этот принцип, когда я должен отделить свой код от объектов, которые должны наследоваться от нескольких интерфейсов. Типичным примером может быть:

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

но я не уверен, что это более компактное и эффективное решение: у меня могут быть более сложные множественные иерархии наследования, и это добавляет сложности, потому что я должен создавать интерфейсы только для работы в качестве контейнеров. Есть лучшее дизайнерское решение?

4b9b3361

Ответ 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. Программирование против классов не всегда вредно, я зависим от ситуации.