зачем нужны модификаторы доступа с
Введение в ООП с примерами на C#. Часть пятая. Всё о модификаторах доступа
Авторизуйтесь
Введение в ООП с примерами на C#. Часть пятая. Всё о модификаторах доступа
В прошлых статьях серии «Введение в ООП» мы рассматривали полиморфизм (а также нюансы использования его на практике), наследование и абстрактные классы. В этой части я постараюсь раскрыть все тонкости использования модификаторов доступа, которые знаю сам. Продолжаем погружаться в ООП!
Что такое модификаторы доступа?
Давайте в этот раз возьмём определение из Википедии (в русской Википедии статьи access modifiers нет, поэтому здесь приводим свой перевод — прим. перев.) :
Модификаторы доступа (или спецификаторы доступа) — ключевые слова в объектно-ориентированных языках, которые задают (внезапно!) параметры доступа для классов, методов и прочих элементов. Модификаторы доступа — специфичная часть языков программирования для облегчения инкапсуляции компонентов.
Модификаторы public, private, protected
Традиционно сразу переходим к практике. Давайте попробуем выполнить следующий код:
Результатом выполнения этого кода будет:
Теперь попробуем получить доступ к AAA() напрямую:
‘AccessModifiers.Modifiers.AAA()’ is inaccessible due to its protection level
Модификаторы доступа и наследование
Снова попробуем выполнить код:
Запускаем код и видим…
‘AccessModifiers.ModifiersBase.AAA()’ is inaccessible due to its protection level
Приватные члены недоступны даже дочерним классам. Публичные члены доступны всем, это понятно. Модификатор же protected по сути и обозначает, что член доступен только дочерним классам — вызов CCC() в примере выше не вызывает никаких ошибок.
Модификатор Internal для классов
Давайте рассмотрим следующий сценарий: мы создаём в новой библиотеке классов (назовём её AccessModifiersLibrary ) класс ClassA и помечаем его как internal :
Теперь в созданном ранее файле попробуем выполнить:
Compile time error: ‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
Модификаторы для пространств имён
Давайте попробуем сделать с предыдущим кодом следующее:
Конечно, это не скомпилируется:
Compile time error: A namespace declaration cannot have modifiers or attributes
Приватные классы
Если мы попробуем скомпилировать код, приведённый выше, то получим ошибку:
Compile time error: Elements defined in a namespace cannot be explicitly declared as private, protected, or protected internal
Подробнее о модификаторах членов класса
Что будет, если мы захотим назначить члену класса больше одного модификатора доступа?
Будет ошибка компиляции:
Compile time error: More than one protection modifier
А как поведёт себя язык, если мы создадим public метод в internal классе?
Вывод после компиляции:
‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
The type ‘AccessModifiersLibrary.ClassA’ has no constructors defined
‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and
no extension method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’
could be found (are you missing a using directive or an assembly reference?)
Как много ошибок… Дело в том, что какими бы модификаторами не обладали члены internal класса, их всё равно нельзя вызвать оттуда, где не виден сам класс. А что будет, если мы попробуем сделать наоборот — вызвать private или internal метод у public класса?
‘AccessModifiersLibrary.ClassA’ does not contain a definition
for ‘MethodClassA’ and no extension method ‘MethodClassA’ accepting a first argument
of type ‘AccessModifiersLibrary.ClassA’ could be found (are you missing a using directive or an assembly reference?)
‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and no extension
method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’ could be
found (are you missing a using directive or an assembly reference?)
Увы, так делать тоже нельзя.
Модификатор protected internal
Этот код компилируется без ошибок. Модификатор internal proteted (как не слишком сложно догадаться) даёт понять, что метод доступен как для вызовов из того же файла, в котором он объявлен, так и для вызовов из дочерних классов.
Protected поля
Здесь всё будет немного сложнее. Давайте напишем следующий код:
Если мы его запустим, то получим ошибку:
Cannot access protected member ‘AccessModifiers.AAA.a’ via a qualifier of type ‘AccessModifiers.AAA’;
the qualifier must be of type ‘AccessModifiers.BBB’ (or derived from it)
15–17 ноября, Онлайн, Беcплатно
(От редакции) Скорее всего, это сделано, чтобы нельзя было делать следующим образом:
Приоритет модификаторов
Compile time error: Inconsistent accessibility: base class ‘AccessModifiers.AAA’ is less accessible than class ‘AccessModifiers.BBB’
Inconsistent accessibility: return type ‘AccessModifiers.AAA’ is less accessible than method ‘AccessModifiers.BBB.MethodB()’
Inconsistent accessibility: field type ‘AccessModifiers.AAA’ is less accessible than field ‘AccessModifiers.BBB.aaa’
Подведём итоги:
Работу с константами и sealed классами (которая тоже осуществляется за счёт модификаторов доступа) мы разберём в следующей статье.
Открытые и закрытые члены класса, модификаторы доступа
Поддержка свойства инкапсуляции в классе дает два главных преимущества. Во-первых, класс связывает данные с кодом. И во-вторых, класс предоставляет средства для управления доступом к его членам.
В языке C#, по существу, имеются два типа членов класса: открытые и закрытые, хотя в действительности дело обстоит немного сложнее.
Для управления доступом используются модификаторы. Также приводятся рекомендации по организации доступа.
Доступ к членам класса
Доступ к открытому члену свободно осуществляется из кода, определенного за пределами класса. А закрытый член класса доступен только методам, определенным в самом классе. С помощью закрытых членов и организуется управление доступом.
Ограничение доступа к членам класса является основополагающей идеей объектно-ориентированного программирования, поскольку позволяет исключить неверное использование объекта.
Разрешая доступ к закрытым данным только с помощью строго определенного ряда методов, можно предупредить присваивание неверных значений этим данным, выполняя, например, проверку диапазона представления чисел.
Для закрытого члена класса нельзя задать значение непосредственно в коде за пределами класса. Но в то же время можно полностью управлять тем, как и когда данные используются в объекте.
Следовательно, правильно реализованный класс образует некий «черный ящик», которым можно пользоваться, но внутренний механизм его действия закрыт для вмешательства извне.
Модификаторы доступа
Управление доступом в языке C# организуется с помощью четырех модификаторов доступа: public, private, protected и internal.
Когда член класса обозначается спецификатором public, он становится доступным из любого другого кода в программе, включая и методы, определенные в других классах.
Когда же член класса обозначается спецификатором private, он может быть доступен только другим членам этого класса. Следовательно, методы из других классов не имеют доступа к закрытому члену (private) данного класса.
Если ни один из спецификаторов доступа не указан, член класса считается закрытым для своего класса по умолчанию. Поэтому при создании закрытых членов класса спецификатор private указывать для них необязательно.
С помощью модификатора доступа protected обеспечивается создание защищенного члена класса, доступ к которому открыт в пределах иерархии классов (в классах-наследниках).
А модификатор internal служит в основном для сборок.
Для наглядности давайте рассмотрим пример:
Комментарии. Создается некоторый класс ABCD с 4 полями (a,b,c,d), с двумя конструкторами и одним методом rewrite( ). В методе Main( ) класса Program инициализируются разными конструкторами два объекта класса ABCD – obj1 и obj2. Для каждого объекта вызывается его метод rewrite().
Результат выполнения программы:
Как мы видим, первый конструктор обнуляет все поля объекта obj1 (поле a изменяется после выполнения операции obj1.a=5, так как это поле объявлено как public).
Выполнение оператора obj1.b = 8; приведет к ошибке: «ConsoleApplication1.ABCD.b» недоступен из-за его уровня защиты. Как видите, доступ к членам b, c и d закрыт, но, несмотря на это, свободный доступ к ним организован с помощью public-конструктора c параметрами и public-метода rewrite( ).
Из всего сказанного выше можно сделать следующий важный вывод: закрытый член может свободно использоваться другими членами этого же класса, но недоступен для кода за пределами своего класса.
Организация закрытого и открытого доступа
Правильная организация закрытого и открытого доступа — залог успеха в объектно-ориентированном программировании. И хотя для этого не существует твердо установленных правил, ниже перечислен ряд общих принципов, которые могут служить в качестве руководства к действию:
1. Члены, используемые только в классе, должны быть закрытыми.
2. Данные экземпляра, не выходящие за определенные пределы значений, должны быть закрытыми, а при организации доступа к ним с помощью открытых методов следует выполнять проверку диапазона представления чисел.
3. Если изменение члена приводит к последствиям, распространяющимся за пределы области действия самого члена, то есть оказывает влияние на другие аспекты объекта, то этот член должен быть закрытым, а доступ к нему — контролируемым.
4. Члены, способные нанести вред объекту, если они используются неправильно, должны быть закрытыми. Доступ к этим членам следует организовать с помощью открытых методов, исключающих неправильное их использование.
5. Методы, получающие и устанавливающие значения закрытых данных, должны быть открытыми.
6. Переменные экземпляра допускается делать открытыми лишь в том случае, если нет никаких оснований для того, чтобы они были закрытыми.
Разумеется, существует немало ситуаций, на которые приведенные выше принципы не распространяются, а в особых случаях один или несколько этих принципов могут вообще нарушаться. Но в целом, следуя этим правилам, вы сможете создавать объекты, устойчивые к попыткам неправильного их использования.
Зачем в C# нужны интерфейсы и как их использовать?
Содержание статьи:
Вступление: знакомство с интерфейсами
Главная задача интерфейса — определить поведение, которое впоследствии будет реализовано в каком-то конкретном классе. У класса есть возможность поддерживать неограниченное количество интерфейсов, а значит и иметь множество поведений.
Поскольку в интерфейсах не заложена реализация, в них указывается только то, что необходимо сделать, но нет данных о том, как это сделать. Определяются свойства, функционал и методы без их реализации.
Давайте посмотрим на синтаксис:
Обратите внимание на то, что для объектов интерфейсов в C# чаще всего используется вначале буква I, но это требование не обязательно.
Интерфейсы используются для создания приложений, компоненты которых не связаны между собой.
Что определяет interface in C#?
Важно: интерфейс не может содержать поля свойства, реализуемые автоматически.
Модификаторы доступа интерфейсов
Члены классов имеют свои модификаторы доступа. Они определяют контекст применения метода или использования переменной.
Уровень доступа интерфейсов по умолчанию internal (только в рамках проекта). Чтобы сделать его общедоступным, применяется модификатор public — доступен из любого места в коде и даже из других программ:
Модификатор protected доступен из любого места в классе, а также в производных классах из других сборок:
Сравнительная таблица модификаторов
Вызывающий объект | Internal | Public | Private | Protected |
В Class | да | да | да | да |
Производный класс — в одной сборке | да | да | нет | да |
Непроизводный класс — в одной сборке | да | да | нет | нет |
Производный класс — в другой сборке | нет | да | нет | да |
Непроизводный класс — в другой сборке | нет | да | нет | нет |
Interface in C# и его применение
Объекты интерфейсов нельзя создать с помощью конструктора. Интерфейсы C# реализовываются в классах и структурах.
Вывод программы в консоли:
What is interface in С#? По сути, интерфейс — это залог того, что определенный тип обязательно реализует какой-то конкретный функционал.
Interface implementation: как реализуются интерфейсы в C# по умолчанию
Что мы знали раньше об интерфейсах?
Но начиная с версии C# 8.0 методы и свойства могут реализовываться в интерфейсах по умолчанию.
Допустим, что интерфейс в приложении реализуется классами. Чтобы добавить новый член, вам нужно обновить все классы для его поддержки или создать расширенный интерфейс с новым методом, который будет унаследован от предыдущего:
Но чем больше методов и функций в расширенном интерфейсе, тем все быстрее он становится неуправляемым. Эту проблему решают интерфейсы, реализуемые по умолчанию.
Они полезны при добавлении новых членов, не нарушая существующую реализацию:
С interface implementation в C# нет никакой необходимости в применении новых интерфейсов и изменениях в существующей реализации.
Классы могут переопределить реализацию по умолчанию.
Интерфейсы в C# могут:
Реализация интерфейсов по умолчанию (interface implementation в С#) позволяет разработчикам не нарушать существующую реализацию, но при этом вносить новые изменения.
Множественная реализация интерфейсов
В C# нет поддержки множественного наследования — унаследовать класс можно только от одного класса. Благодаря тому, что в C# класс может реализовать сразу несколько интерфейсов (implement interface в C#), ограничение с невозможностью множественного наследования можно обойти.
Все интерфейсы в class обычно указываются через запятую.
Здесь реализованы интерфейсы IAccount (определяет свойство CurrentSum и методы Put — поступление суммы на счет, Withdraw — изъятие суммы) и IClient (определяет свойство Name ).
Client class реализует оба интерфейса, после чего применяется в программе.
Интерфейсы в преобразованиях типов
В заключение
Интерфейс определяет методы, реализуемые в классе, который в C# наследует этот generic interface.
Интерфейсы используются для того, чтобы направить классы на реализацию методов: событий, индексаторов, свойств. В interface в C# описываются функциональные возможности, но без их реализации (он определяет только спецификацию).
Интерфейс — альтернатива множественному наследованию.
Interface в C# обладает такими особенностями:
Интерфейс может быть реализован в любом количестве классов, а в одном классе может быть реализовано любое количество интерфейсов.
Интерфейсы могут наследовать другие интерфейсы. Синтаксис такого наследования такой же, как синтаксис наследования классов. Например:
Interface в C# лучше всего использовать в случаях, когда все понятия нужно описать с точки зрения их функционального назначения, они пригодятся, когда не нужны детали реализации свойств и методов.
Видео: интерфейсы в C#: зачем нужны и как используются
ООП. Часть 3. Модификаторы доступа, инкапсуляция
Классы, методы и поля не всегда могут постоять за себя. Рассказываем, как быть защитником в объектно-ориентированном программировании.
Инкапсуляция (от лат. in capsule — в оболочке) — это заключение данных и функционала в оболочку. В объектно-ориентированном программировании в роли оболочки выступают классы: они не только собирают переменные и методы в одном месте, но и защищают их от вмешательства извне (сокрытие).
Важно! Инкапсуляция не является сокрытием. Сокрытие — это часть инкапсуляции.
Это может быть достаточно сложной концепцией для понимания. Поэтому, чтобы быстрее разобраться, в этой статье мы рассмотрим инкапсуляцию на примере уровней доступа.
Все статьи про ООП
Пишет о разработке сайтов, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Модификатор доступа public
Первый уровень, с которым сталкиваются все разработчики, — публичный. Чтобы сказать компилятору, что что-то должно быть доступно для всех, используется ключевое слово public.
Рассмотрим на примере класса Item:
Объявив экземпляр этого класса, можно обращаться к любым его полям в любом месте программы, где доступен сам объект (речь о локальных и глобальных переменных).
Так как поля публичные, в консоли они отобразятся без каких-либо проблем:
Это удобно, потому что можно в любой момент выполнить любое действие над объектом и его данными. Но в этом и кроется проблема: объект становится беззащитен перед любым вмешательством. Например, можно просто взять и изменить его цену:
Из-за того, что поле публичное, оно изменится:
Это плохо по нескольким причинам:
Разумеется, это не лучшее, что может случиться с приложением.
Модификатор доступа private
Чтобы поля были защищены от вмешательства, используется ключевое слово private — оно делает члены класса доступными только внутри самого класса.
Теперь эти поля нельзя будет изменить нигде, кроме как в методах этого класса. Но и получить их значение извне тоже не получится, а попытка вывести приведёт к ошибке:
Есть два способа сделать поле доступным только для чтения. Первый — использовать ключевое слово readonly, но оно запрещает менять значение вообще.
Второй способ заключается в том, чтобы передавать значения приватного члена класса через публичный. Например, с помощью методов:
К такой практике прибегают Java-разработчики, но в C# есть более элегантный способ — свойства.
Теперь, чтобы получить данные, нужно обратиться к свойству, а не к полю:
Преимущество этого в том, что можно разрешить получать данные, но запретить их менять. То есть прописать только геттер:
Обратите внимание, что можно просто написать set; или get; если не требуется дополнительная логика. Это сработает, если у поля и свойства одинаковые имена и если это примитивный тип (int, float, char, double и другие). Со ссылочными типами (объекты и строки) это не работает.
Также можно менять логику работы со значением:
Здесь поле будет изменено только в том случае, если ему пытаются указать значение, которое выше нуля.
То есть если запустить вот такой код:
Также можно создавать свойства без поля:
Это свойство вернёт true, если цена выше 5000, и false, если ниже.
Ключевое слово private можно также применять и к методам. Это делает их доступными только внутри класса.
Также приватным можно сделать сам класс, если он находится внутри другого класса:
Модификатор доступа internal
Иногда нужно сделать компонент доступным только внутри одного файла — например, в Program.cs, Item.cs или любом другом. Для этого используется ключевое слово internal.
Класс Backpack можно будет использовать только внутри файла Program.cs, и попытка объявить его внутри другого файла приведёт к ошибке.
Ключевое слово static
Статичность относится не совсем к уровням доступа, но тоже помогает заключить реализацию функционала в оболочку класса. Статичность позволяет обращаться к методам или полям, не создавая объект.
Метод Sum () используется в классе Program, хотя экземпляр класса Calc не создавался. При этом можно сделать статичным как отдельный метод или свойство, так и весь класс. В этом случае все поля и методы тоже должны быть статичными.
Это может быть нужно, чтобы создать набор инструментов, который будет использоваться в других частях программы. Хороший пример — класс Console, который тоже является статичным.
Другой пример — класс Math. Его можно использовать, чтобы выполнять различные математические операции (получение квадратного корня, модуляция, получение синуса, косинуса и так далее). У него много методов, а также он хранит различные константы вроде числа пи.
Домашнее задание
Напишите класс GameObject, в котором будут храниться координаты объекта. Координаты должны быть доступны для чтения, а их изменение должно происходить в методе Move ().
Заключение
Есть и другие ключевые слова:
Они будут рассмотрены в статье о наследовании.
Большая часть курса «Профессия С#-разработчик» посвящена именно ООП —
не только теории, но и практике. Вы научитесь писать программы, подбирая нужные инструменты — от инкапсуляции до полиморфизма. К концу курса у вас будет портфолио из нескольких проектов, а также все знания и навыки, которые нужны для получения первой работы.
Профессия С#-разработчик
130 часов — и вы научитесь писать программы на языке, созданном Microsoft. Вы создадите 5 проектов для портфолио, даже если до этого никогда не программировали. После обучения — гарантированное трудоустройство.