Интерфейсы и с чем их едят

Добавлен , опубликован
Раздел:
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. // да-да, без обработчика отсутствия углов для краткости
Есть интерфейс, который является родителем трех классов-фигур. Их объединяет целочисленное значение, несущее информацию о количестве углов в фигуре. Как нам добавить все фигуры в список и вывести только необходимую информацию? Все верно. На помощь приходит интерфейс, который строго описывает поведение каждой фигуры, при этом не позволяя выходить за рамки дозволенного.
Буду чрезвычайно признателен правкам и дополнениям.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
29
6 лет назад
2
RealizationABC
Может все-таки Implementation?
Да. Исправил.
3
18
6 лет назад
3
спасибо за статью, просто и по полочкам, квик гайд фо даммиз лайк ми. Более развернуто, чем в "с# за 31 день"
2
6
6 лет назад
Отредактирован Эргалон
2
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
2
29
6 лет назад
2
Каким образом это будет объявлено без интерфейсов?
0
4
6 лет назад
0
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
Никто и не обязывает тебя пользоваться ими. Просто видимо ты ещё не нашёл такой задачи , где будут полезными только интерфейсы.
Ну хотя бы с IEnumerator ты хоть раз имел дело =)
0
8
6 лет назад
Отредактирован uranus
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 т.е. возможность прорисовки.
0
28
6 лет назад
0
как пример интерфейсы юзаются в шаблоне observer/listener
да и вообще большинство шаблонов проектирования использует интерфейсы
0
29
6 лет назад
Отредактирован Msey
0
Интерфейсы можно конечно заменить одним помойным абстрактным классом, но по мне - это уже признак говнаря, если дело касается чего-то серьезного с использованием solid, тестирования итд
Со временем это понимается
0
25
6 лет назад
0
Как не видел смысла в интерфейсах, так и не вижу. Там где нужна та или иная реализация в классе, она там и будет. Даже если несколько объектов, должны уметь делать что-то одно, это будет объявлено и без интерфейсов.
Ясно-понятно.
0
9
6 лет назад
Отредактирован AsagiriGen
0
Конкретно то, что вы в вверху описали можно сделать и наследованием класса.
2
8
6 лет назад
2
GeneralElConsul, класс наследуется один, интерфейсов может быть множество, миксинов в шарпе нет, я не понимаю, с чего тут разводить дискуссию.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.