Программирование: Интерфейсы и с чем их едят

» Раздел: C#

В этой статье буду рассмотрены основные моменты при использовании интерфейсов.
Перед прочтением рекомендуется ознакомиться с наследованием классов и преобразованиями типов объекта.
Интерфейс - контракт ссылочного типа, позволяющий определить некоторый функционал для класса или структуры, при этом не имеющий конкретной реализации.
Для определения интерфейса используется ключевое слово interface, где, как правило, название начинается с заглавной буквы I. Пример IVehicle, IFigure, IEnumerable и так далее...
По умолчанию интерфейсы, как классы или структуры, по уровню доступа являются internal и не могут напрямую в сборке иметь модификаторы доступа с более низким уровнем, таким как protected, protected internal, private, private internal, однако вы можете их сделать public.
Также есть еще несколько важных (для .Net программистов с начальным уровнем опыта) подводных камней:
  • члены интерфейса всегда публичные, так как основная задача интерфейса - обязать наследника класса или структуру получить весь его функционал, поэтому в интерфейсах вы не можете задать члену какой-либо модификатор доступа кроме как public
  • изменить модификатор доступа самого интерфейса на более низкий уровень вы все же сможете, но только в том случае, если сам интерфейс находится в структуре или классе
Рассмотрим по порядку весь спектр вышеперечисленного:

Пример

namespace Msey
{
    public interface IFruit
    {
    }

    interface IBuilding
    {
    }

    internal interface IVehicle // излишне, тк интерфейс уже по умолчанию {internаl}, как объяснялось выше
    {
    }

    private interface ICannot // так нельзя
    {
    }

class ForPrivateInterfaces
{
    private interface ICan // а вот так можно
    {
    }
}

    struct ForPrivateInterfacesToo
    {
        private interface ICanToo  // так тоже можно
        {
        }
    }
}

Пример

Реализация интерфейса в классе выглядит следующим образом:
namespace Msey
{
    interface IVehicle
    {
        void Move();
        void Stop();
    }

    class Car : IVehicle
    {
        public void Move() // мы должны обязательно реализовать все методы наследуемого интерфейса, иначе будет ошибка компиляции
        {            
        }

        public void Stop() 
        {            
        }
    }
}
Зачастую бывает необходимо наследовать несколько интерфейсов. C# поддерживает множественное наследование интерфейсов, чего, к сожалению (а на самом-то деле к счастью) нельзя сказать в силу наследования классов.
namespace Msey
{
    interface IFirst
    {
        void FirstMove();
    }

    interface ISecond
    {
        void SecondMove();
    }

    class Car : IFirst, ISecond
    {
        public void FirstMove()
        {            
        }

        public void SecondMove()
        {            
        }
    }
}
Случай, когда интерфейсы нужно объединить:
        public interface A
        {
            void ActionA();
        }

        public interface B
        {
            void ActionB();
        }

        public interface C
        {
            void ActionC();
        }

        interface ABC : A,B,C
        {
        }

        class ImplementationABC : ABC
        {
            public void ActionA()
            {
            }

            public void ActionB()
            {
            }

            public void ActionC()
            {
            }
        }
Кроме неявного применения интерфейсов рассмотренного выше, также еще существует и его явная реализация. При явной реализации указывается название метода или свойства вместе с названием интерфейса, при этом мы не можем применить какие-либо модификаторы доступа, так как, по умолчанию, члены явно реализованного интерфейса являются приватными и не могут быть доступными из экземпляра класса.

Пример

namespace Msey
{
    interface IFirst
    {
        void FirstMove();
    }

    interface ISecond
    {
        void SecondMove();
    }

    class Car : IFirst, ISecond
    {
        public void IFirst.FirstMove() // так нельзя, методу задан модификатор доступа
        {            
        }

        void ISecond.SecondMove() // а вот так можно; модификаторов доступа нет
        {            
        }
    }
}
Иногда бывают такие случаи, когда несколько наследуемых интерфейсов имеют члены с одинаковыми названиями, тогда нам на помощь приходит преобразование типов объекта и явное применение интерфейса:

Пример

namespace Msey
{
    interface IFirst
    {
        void Move();
    }

    interface ISecond
    {
        void Move();
    }

    class Car : IFirst, ISecond
    {
        void IFirst.Move()
        {
        }

        void ISecond.Move()
        {
        }

        public void BothInvoke() 
        {
            ((IFirst)this).Move();  
            ((ISecond)this).Move();
        }
    }
}
Здесь разберем подробнее: при вызове BothInvoke(), через this берется ссылка на объект, в котором, собственно, и производится данный вызов. В нашем случае будет Car. Далее мы приводим тип объекта Car к интерфейсу IFirst и производим от его имени вызов метода Move().
Другими словами, от имени интерфейса IFirst мы в объекте Car вызываем метод Move().
Аналогично с интерфейсом ISecond.
((ISecond)this).Move();

Для чего нужны интерфейсы?

Это, пожалуй, самый частый вопрос который я слышал от начинающих программистов, и себя в том числе на начальных этапах программирования на C#. (ой, да ладно, я и до сих пор каждое утро себя спрашиваю).
  • Интерфейс в практическом смысле дает возможность указать из чего именно должен состоять тот или иной объект разрабатываемой модели без описания поведения объекта.
  • Интерфейс позволяет максимально ослабить зависимости между объектами.
  • Интерфейс обеспечивает тестируемость за счет предыдущего пункта

Элементарный пример использования интерфейса

 class Program
    { 
        public interface IFigure
        {
            int NumberOfAngles { get; }
        } 
 
        public class Triangle: IFigure
        {
            public int NumberOfAngles => 3;
        }
 
        public class Quad : IFigure
        {
            public int NumberOfAngles => 4;
        }
 
        public class Circle : IFigure
        {
            public int NumberOfAngles => 0;
        }
 
        static void Main(string[] args)
        {
            List<IFigure> AllFigures = new List<IFigure>();
 
            Triangle triangle = new Triangle();
            Quad quad = new Quad();
            Circle circle = new Circle();            
 
            AllFigures.Add(triangle);
            AllFigures.Add(quad);
            AllFigures.Add(circle);
 
            foreach (var figure in AllFigures)
                Console.WriteLine("Figure " + figure.GetType().Name + " has "+figure.NumberOfAngles + " angles.");
        }
    }
Вывод программы будет следующим:
Figure Triangle has 3 angles.
Figure Quad has 4 angles.
Figure Circle has 0 angles. // да-да, без обработчика отсутствия углов для краткости
Есть интерфейс, который является родителем трех классов-фигур. Их объединяет целочисленное значение, несущее информацию о количестве углов в фигуре. Как нам добавить все фигуры в список и вывести только необходимую информацию? Все верно. На помощь приходит интерфейс, который строго описывает поведение каждой фигуры, при этом не позволяя выходить за рамки дозволенного.
Буду чрезвычайно признателен правкам и дополнениям.

Просмотров: 654

» Лучшие комментарии


ZlaYa1000 #1 - 1 месяц назад 2
А чего никто не комментирует?)
Doc #2 - 1 месяц назад 6
RealizationABC
Может все-таки Implementation?
Msey #3 - 1 месяц назад 2
RealizationABC
Может все-таки Implementation?
Да. Исправил.
BrEd Pitt #4 - 1 месяц назад 3
спасибо за статью, просто и по полочкам, квик гайд фо даммиз лайк ми. Более развернуто, чем в "с# за 31 день"
Эргалон #5 - 1 месяц назад (отредактировано ) 0
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
Doc #6 - 1 месяц назад 2
Каким образом это будет объявлено без интерфейсов?
abidin #7 - 1 месяц назад 0
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
Никто и не обязывает тебя пользоваться ими. Просто видимо ты ещё не нашёл такой задачи , где будут полезными только интерфейсы.
Ну хотя бы с IEnumerator ты хоть раз имел дело =)
uranus #8 - 1 месяц назад (отредактировано ) 0
Эргалон, иногда объекты слишком разные, поэтому наследование классов не подходит, но в то же время за счет наследования интерфейсов можно будет хранить их в одних массивах/коллекциях, наделять их неким сходством.
» Как-то так
public interface IDrawable {
	void Draw(Graphics render);
}

public class Rectangle : IDrawable {
	...
	public void Draw(Graphics render) {
		render.DrawRectangle(pen,x,y,w,h);
	}
}

public class Shape : IDrawable {
	...
	public void Draw(Graphics render) {
		render.DrawEllipse(pen,x,y,w,h);
	}
}

...
List<IDrawable> gameObjects;
....
Graphics g = Graphics.FromImage(canvas);
while (gameLoop) {
	g.Clear(color);
	foreach (var go in gameObjects) {
		go.Draw(g);
	}
}
При этом разные элементы могут наследовать от разных классов, какой-нибудь Label от UI, например, но объединяет их всех интерфейс IDrawable т.е. возможность прорисовки.
nvc123 #9 - 1 месяц назад 0
как пример интерфейсы юзаются в шаблоне observer/listener
да и вообще большинство шаблонов проектирования использует интерфейсы
Msey #10 - 1 месяц назад (отредактировано ) 0
Интерфейсы можно конечно заменить одним помойным абстрактным классом, но по мне - это уже признак говнаря, если дело касается чего-то серьезного с использованием solid, тестирования итд
Со временем это понимается
NanO #11 - 1 месяц назад 0
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
Ясно-понятно.
GeneralElConsul #12 - 1 месяц назад (отредактировано ) 0
Конкретно то, что вы в вверху описали можно сделать и наследованием класса.
uranus #13 - 1 месяц назад 2
GeneralElConsul, класс наследуется один, интерфейсов может быть множество, миксинов в шарпе нет, я не понимаю, с чего тут разводить дискуссию.
GeneralElConsul #14 - 1 месяц назад 2
Хм, а давайте подумаем, почему Майкрософт сделали и интерфейс IComparer, и класс Comparer. Ну да, наверное, чтобы в случае класса его наследовали когда хочется унаследовать именно класс (настроение такое - хочу наследовать классы сегодня!). А в случае интерфейса - когда, увы, уже наследуешь один класс и для второго класса места нет, поэтому есть возможность наследовать интерфейс.
Ведь вся суть использования интерфейсов именно в том, что именно для них поддерживается множественное наследование, да.
Doc #15 - 1 месяц назад 6
Множественное наследование просто побочный эффект. Интерфейс просто объявляет контракт. Важное свойство интерфейса в том что он не имеет собственного состояния.
nvc123 #16 - 4 недели назад (отредактировано ) -1
Doc, тут люди не понимают зачем нужен полиморфизм а ты про контракты и состояния
ну а вообще если говорить простым языком (т.е. для полных нубов) то интерфейсы нужны для того чтобы убедится что класс имеет реализацию необходимых методов
GeneralElConsul #17 - 4 недели назад (отредактировано ) 0
Да просто человек сказал, что он не понимает смысл интерфейсов, а вы ему давай писать то, для чего и обычный абстрактный класс сгодиться, не затрагивая сути его существования - так он точно не изменит свою точку зрения.
Doc #18 - 4 недели назад 2
Ну отсутствие состояния и реализации методов (что и позволяет множественно наследоваться) и отличает интерфейс от абстрактного класса.
nvc123 #19 - 4 недели назад (отредактировано ) 1
GeneralElConsul, интерфейс это класс в котором все методы абстрактные и публичные (и прочие искусственные фичи наподобие множественного наследования)
но тут судя по вопросу человек не понимает не в чём отличие абстрактного класса от интерфейса а применение полиморфизма
lentinant #20 - 3 недели назад 1
Важное свойство интерфейса в том, что он ограничивает доступ к реализации класса. Классу А, работающему со сложным классом Б, не обязательно знать о всех методах и свойствах класса Б. Однако, если класс Б реализовывает определенный интерфейс, класс А может об этом знать, и вместо того чтобы работать с многочисленными методами класса Б, он может общаться с ним через интерфейс, получив доступ только к тем методам, которые ему важны.
Есть даже простой пример на реальных объектах - корпус вашего системника. Внутри системника куча функционала, разъемов, проводов и т.д., однако сам корпус обеспечивает вам доступ к самым важным элементам - кнопкам управления и выходам для различных девайсов. Представьте себе если бы порт USB находился на самой материнке, и чтобы найти его, вам приходилось бы высматривать его с десятков других разъемов на материнке.
GeneralElConsul #21 - 3 недели назад 0
Да банально позволяет "выдергивать" из класса только тот функционал, который нужен.
Batnik #22 - 2 недели назад 2
Будут еще статьи про C#?
Hate #23 - 2 недели назад 3
Batnik:
Будут еще статьи про C#?
будут
Msey #24 - 2 недели назад 1
Batnik:
Будут еще статьи про C#?
Да. Я как раз над этим работаю.
Просто щас тут еще и понкурс по картам вк3 и времени мало остается, но статьи будут!
*конкурс