Уровень инкапсуляции protected имеют элементы класса которые
Все члены класса в языке Java — поля и методы — имеют модификаторы доступа. В прошлых темах мы уже сталкивались с модификатором public . Модификаторы доступа позволяют задать допустимую область видимости для членов класса, то есть контекст, в котором можно употреблять данную переменную или метод.
В Java используются следующие модификаторы доступа:
- public : публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public, видны другим классам из текущего пакета и из внешних пакетов.
- private : закрытый класс или член класса, противоположность модификатору public. Закрытый класс или член класса доступен только из кода в том же классе.
- protected : такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах
- Модификатор по умолчанию . Отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.
Рассмотрим модификаторы доступа на примере следующей программы:
public class Program < public static void main(String[] args) < Person kate = new Person("Kate", 32, "Baker Street", "+12334567"); kate.displayName(); // норм, метод public kate.displayAge(); // норм, метод имеет модификатор по умолчанию kate.displayPhone(); // норм, метод protected //kate.displayAddress(); // ! Ошибка, метод private System.out.println(kate.name); // норм, модификатор по умолчанию System.out.println(kate.address); // норм, модификатор public System.out.println(kate.age); // норм, модификатор protected //System.out.println(kate.phone); // ! Ошибка, модификатор private >> class Person < String name; protected int age; public String address; private String phone; public Person(String name, int age, String address, String phone)< this.name = name; this.age = age; this.address = address; this.phone = phone; >public void displayName() < System.out.printf("Name: %s \n", name); >void displayAge() < System.out.printf("Age: %d \n", age); >private void displayAddress() < System.out.printf("Address: %s \n", address); >protected void displayPhone()< System.out.printf("Phone: %s \n", phone); >>
В данном случае оба класса расположены в одном пакете — пакете по умолчанию, поэтому в классе Program мы можем использовать все методы и переменные класса Person, которые имеют модификатор по умолчанию, public и protected. А поля и методы с модификатором private в классе Program не будут доступны.
Если бы класс Program располагался бы в другом пакете, то ему были бы доступны только поля и методы с модификатором public.
Модификатор доступа должен предшествовать остальной части определения переменной или метода.
Инкапсуляция
Казалось бы, почему бы не объявить все переменные и методы с модификатором public , чтобы они были доступны в любой точке программы вне зависимости от пакета или класса? Возьмем, например, поле age, которое представляет возраст. Если другой класс имеет прямой доступ к этому полю, то есть вероятность, что в процессе работы программы ему будет передано некорректное значение, например, отрицательное число. Подобное изменение данных не является желательным. Либо же мы хотим, чтобы некоторые данные были достуны напрямую, чтобы их можно было вывести на консоль или просто узнать их значение. В этой связи рекомендуется как можно больше ограничивать доступ к данным, чтобы защитить их от нежелательного доступа извне (как для получения значения, так и для его изменения). Использование различных модификаторов гарантирует, что данные не будут искажены или изменены не надлежащим образом. Подобное сокрытие данных внутри некоторой области видимости называется инкапсуляцией .
Так, как правило, вместо непосредственного применения полей используют методы доступа. Например:
public class Program < public static void main(String[] args) < Person kate = new Person("Kate", 30); System.out.println(kate.getAge()); // 30 kate.setAge(33); System.out.println(kate.getAge()); // 33 kate.setAge(123450); System.out.println(kate.getAge()); // 33 >> class Person < private String name; private int age = 1; public Person(String name, int age)< setName(name); setAge(age); >public String getName() < return this.name; >public void setName(String name) < this.name = name; >public int getAge() < return this.age; >public void setAge(int age) < if(age >0 && age < 110) this.age = age; >>
И затем вместо непосредственной работы с полями name и age в классе Person мы будем работать с методами, которые устанавливают и возвращают значения этих полей. Методы setName, setAge и наподобие еще называют мьютейтерами (mutator), так как они изменяют значения поля. А методы getName, getAge и наподобие называют аксессерами (accessor), так как с их помощью мы получаем значение поля.
Причем в эти методы мы можем вложить дополнительную логику. Например, в данном случае при изменении возраста производится проверка, насколько соответствует новое значение допустимому диапазону.
Приватные и защищённые методы и свойства
Один из важнейших принципов объектно-ориентированного программирования – разделение внутреннего и внешнего интерфейсов.
Это обязательная практика в разработке чего-либо сложнее, чем «hello world».
Чтобы понять этот принцип, давайте на секунду забудем о программировании и обратим взгляд на реальный мир.
Устройства, которыми мы пользуемся, обычно довольно сложно устроены. Но разделение внутреннего и внешнего интерфейсов позволяет нам пользоваться ими без каких-либо проблем.
Пример из реальной жизни
Например, кофеварка. Простая снаружи: кнопка, экран, несколько отверстий… И, конечно, результат – прекрасный кофе! 🙂
Но внутри… (картинка из инструкции по ремонту)

Множество деталей. Но мы можем пользоваться ею, ничего об этом не зная.
Кофеварки довольно надёжны, не так ли? Мы можем пользоваться ими годами, и если что-то пойдёт не так – отнесём в ремонт.
Секрет надёжности и простоты кофеварки – все детали хорошо отлажены и спрятаны внутри.
Если мы снимем защитный кожух с кофеварки, то пользоваться ею будет гораздо сложнее (куда нажимать?) и опаснее (может привести к поражению электрическим током).
Как мы увидим, в программировании объекты похожи на кофеварки.
Но, чтобы скрыть внутренние детали, мы будем использовать не защитный кожух, а специальный синтаксис языка и соглашения.
Внутренний и внешний интерфейсы
В объектно-ориентированном программировании свойства и методы разделены на 2 группы:
- Внутренний интерфейс – методы и свойства, доступные из других методов класса, но не снаружи класса.
- Внешний интерфейс – методы и свойства, доступные снаружи класса.
Если мы продолжаем аналогию с кофеваркой – то, что скрыто внутри: трубка кипятильника, нагревательный элемент и т.д. – это внутренний интерфейс.
Внутренний интерфейс используется для работы объекта, его детали используют друг друга. Например, трубка кипятильника прикреплена к нагревательному элементу.
Но снаружи кофеварка закрыта защитным кожухом, так что никто не может добраться до сложных частей. Детали скрыты и недоступны. Мы можем использовать их функции через внешний интерфейс.
Итак, всё, что нам нужно для использования объекта, это знать его внешний интерфейс. Мы можем совершенно не знать, как это работает внутри, и это здорово.
Это было общее введение.
В JavaScript есть два типа полей (свойств и методов) объекта:
- Публичные: доступны отовсюду. Они составляют внешний интерфейс. До этого момента мы использовали только публичные свойства и методы.
- Приватные: доступны только внутри класса. Они для внутреннего интерфейса.
Во многих других языках также существуют «защищённые» поля, доступные только внутри класса или для дочерних классов (то есть, как приватные, но разрешён доступ для наследующих классов) и также полезны для внутреннего интерфейса. В некотором смысле они более распространены, чем приватные, потому что мы обычно хотим, чтобы наследующие классы получали доступ к внутренним полям.
Защищённые поля не реализованы в JavaScript на уровне языка, но на практике они очень удобны, поэтому их эмулируют.
А теперь давайте сделаем кофеварку на JavaScript со всеми этими типами свойств. Кофеварка имеет множество деталей, мы не будем их моделировать для простоты примера (хотя могли бы).
Защищённое свойство «waterAmount»
Давайте для начала создадим простой класс для описания кофеварки:
class CoffeeMachine < waterAmount = 0; // количество воды внутри constructor(power) < this.power = power; alert( `Создана кофеварка, мощность: $` ); > > // создаём кофеварку let coffeeMachine = new CoffeeMachine(100); // добавляем воды coffeeMachine.waterAmount = 200;
Прямо сейчас свойства waterAmount и power публичные. Мы можем легко получать и устанавливать им любое значение извне.
Давайте изменим свойство waterAmount на защищённое, чтобы иметь больше контроля над ним. Например, мы не хотим, чтобы кто-либо устанавливал его ниже нуля.
Защищённые свойства обычно начинаются с префикса _ .
Это не синтаксис языка: есть хорошо известное соглашение между программистами, что такие свойства и методы не должны быть доступны извне. Большинство программистов следуют этому соглашению.
Так что наше свойство будет называться _waterAmount :
class CoffeeMachine < _waterAmount = 0; set waterAmount(value) < if (value < 0) throw new Error("Отрицательное количество воды"); this._waterAmount = value; >get waterAmount() < return this._waterAmount; >constructor(power) < this._power = power; >> // создаём новую кофеварку let coffeeMachine = new CoffeeMachine(100); // устанавливаем количество воды coffeeMachine.waterAmount = -10; // Error: Отрицательное количество воды
Теперь доступ под контролем, поэтому указать воду ниже нуля не удалось.
Свойство только для чтения «power»
Давайте сделаем свойство power доступным только для чтения. Иногда нужно, чтобы свойство устанавливалось только при создании объекта и после этого никогда не изменялось.
Это как раз требуется для кофеварки: мощность никогда не меняется.
Для этого нам нужно создать только геттер, но не сеттер:
class CoffeeMachine < // . constructor(power) < this._power = power; >get power() < return this._power; >> // создаём кофеварку let coffeeMachine = new CoffeeMachine(100); alert(`Мощность: $W`); // Мощность: 100W coffeeMachine.power = 25; // Error (no setter)
Геттеры/сеттеры
Здесь мы использовали синтаксис геттеров/сеттеров.
Но в большинстве случаев использование функций get. /set. предпочтительнее:
class CoffeeMachine < _waterAmount = 0; setWaterAmount(value) < if (value < 0) throw new Error("Отрицательное количество воды"); this._waterAmount = value; >getWaterAmount() < return this._waterAmount; >> new CoffeeMachine().setWaterAmount(100);
Это выглядит немного длиннее, но функции более гибкие. Они могут принимать несколько аргументов (даже если они нам сейчас не нужны). Итак, на будущее, если нам надо что-то отрефакторить, функции – более безопасный выбор.
С другой стороны, синтаксис get/set короче, решать вам.
Защищённые поля наследуются
Если мы унаследуем class MegaMachine extends CoffeeMachine , ничто не помешает нам обращаться к this._waterAmount или this._power из методов нового класса.
Таким образом, защищённые поля, конечно же, наследуются. В отличие от приватных полей, в чём мы убедимся ниже.
Приватное свойство «#waterLimit»
Новая возможность
Эта возможность была добавлена в язык недавно. В движках JavaScript пока не поддерживается или поддерживается частично, нужен полифил.
Есть новшество в языке JavaScript, которое почти добавлено в стандарт: оно добавляет поддержку приватных свойств и методов.
Приватные свойства и методы должны начинаться с # . Они доступны только внутри класса.
Например, в классе ниже есть приватное свойство #waterLimit и приватный метод #checkWater для проверки количества воды:
class CoffeeMachine < #waterLimit = 200; #checkWater(value) < if (value < 0) throw new Error("Отрицательный уровень воды"); if (value >this.#waterLimit) throw new Error("Слишком много воды"); > > let coffeeMachine = new CoffeeMachine(); // снаружи нет доступа к приватным методам класса coffeeMachine.#checkWater(); // Error coffeeMachine.#waterLimit = 1000; // Error
На уровне языка # является специальным символом, который означает, что поле приватное. Мы не можем получить к нему доступ извне или из наследуемых классов.
Приватные поля не конфликтуют с публичными. У нас может быть два поля одновременно – приватное #waterAmount и публичное waterAmount .
Например, давайте сделаем аксессор waterAmount для #waterAmount :
class CoffeeMachine < #waterAmount = 0; get waterAmount() < return this.#waterAmount; >set waterAmount(value) < if (value < 0) throw new Error("Отрицательный уровень воды"); this.#waterAmount = value; >> let machine = new CoffeeMachine(); machine.waterAmount = 100; alert(machine.#waterAmount); // Error
В отличие от защищённых, функциональность приватных полей обеспечивается самим языком. Это хорошо.
Но если мы унаследуем от CoffeeMachine , то мы не получим прямого доступа к #waterAmount . Мы будем вынуждены полагаться на геттер/сеттер waterAmount :
class MegaCoffeeMachine extends CoffeeMachine < method() < alert( this.#waterAmount ); // Error: can only access from CoffeeMachine >>
Во многих случаях такое ограничение слишком жёсткое. Раз уж мы расширяем CoffeeMachine , у нас может быть вполне законная причина для доступа к внутренним методам и свойствам. Поэтому защищённые свойства используются чаще, хоть они и не поддерживаются синтаксисом языка.
Приватные поля особенные.
Как мы помним, обычно мы можем получить доступ к полям объекта с помощью this[name]:
class User < . sayHi() < let fieldName = "name"; alert(`Hello, $`); > >
С приватными свойствами такое невозможно: this[‘#name’] не работает. Это ограничение синтаксиса сделано для обеспечения приватности.
Итого
В терминах ООП отделение внутреннего интерфейса от внешнего называется инкапсуляция.
Это даёт следующие выгоды:
Защита для пользователей, чтобы они не выстрелили себе в ногу
Представьте себе, что есть команда разработчиков, использующая кофеварку. Она была изготовлена компанией «Лучшие Кофеварки» и работает нормально, но защитный кожух был снят. Внутренний интерфейс стал доступен извне.
Все разработчики культурны – они используют кофеварку по назначению. Но один из них, Джон, решил, что он самый умный, и сделал некоторые изменения во внутренностях кофеварки. После чего кофеварка вышла из строя через два дня.
Это, конечно, не вина Джона, а скорее человека, который снял защитный кожух и позволил Джону делать свои манипуляции.
То же самое в программировании. Если пользователь класса изменит вещи, не предназначенные для изменения извне – последствия непредсказуемы.
Ситуация в программировании сложнее, чем с реальной кофеваркой, потому что мы не просто покупаем её один раз. Код постоянно подвергается разработке и улучшению.
Если мы чётко отделим внутренний интерфейс, то разработчик класса сможет свободно менять его внутренние свойства и методы, даже не информируя пользователей…
Если вы разработчик такого класса, то приятно знать, что приватные методы можно безопасно переименовывать, их параметры можно изменять и даже удалять, потому что от них не зависит никакой внешний код.
В новой версии вы можете полностью всё переписать, но пользователю будет легко обновиться, если внешний интерфейс остался такой же.
Люди обожают использовать простые вещи. По крайней мере, снаружи. Что внутри – это другое дело.
Программисты не являются исключением.
Всегда удобно, когда детали реализации скрыты, и доступен простой, хорошо документированный внешний интерфейс.
Для сокрытия внутреннего интерфейса мы используем защищённые или приватные свойства:
- Защищённые поля имеют префикс _ . Это хорошо известное соглашение, не поддерживаемое на уровне языка. Программисты должны обращаться к полю, начинающемуся с _ , только из его класса и классов, унаследованных от него.
- Приватные поля имеют префикс # . JavaScript гарантирует, что мы можем получить доступ к таким полям только внутри класса.
В настоящее время приватные поля не очень хорошо поддерживаются в браузерах, но можно использовать полифил.
#28 Модификаторы доступа элементов класса: public, private, protected. Инкапсуляция
![]()
Для элементов класса во многих языках существуют три уровня доступности:
- public — публичные (общедоступные элементы) элементы, напр. только с такими элементами мы имели дело в примерах первого урока по ООП. Публичные элементы доступны как изнутри методов класса так и из внешнего кода (т.е. такого кода, который не относится к методам класса).
- protected — т.н. «защищенные» элементы, к ним можно обращаться из методов класса и из методов его потомков (т.е. из методов классов унаследованных от данного), но нельзя из внешнего кода
- private — «приватные/закрытые», доступ к ним может быть только из собственных методов класса, но ни классы-потомки ни внешний код, не имеют доступа к этим элементам.
Далее рассмотрим использование этих модификторов на примерах.
ПРИМАЧЕНИЕ: в примерах далее мы будем использовать для protected и private т.н. строгие версии: strict protected и strict private. Это связано с тем, что в отличие от ряда других языков Паскаль «закрывает» доступ без «strict» не на уровне класса , а на уровне модуля (файла, т.е. без strict доступ даже для внешнего кода в том же файле будет открыт), но т.к. все наши примеры располагаются в одом файле, то мы используем дополнительное слово «strict», чтобы закрыть доступ даже на уровне одного файла.
В других языках используются точно такие же модификаторы, работающие подобным же образом, но ключевое слово «strict» не требуется.
Инкапсуляция
Инукапсуляция — это «сокрытие внутри», т.е. некий прием позволяющих сделать сделать скрытыми некие части системы от внешего на наблюдателя
Далее мы будем рассматривать способы сокрытия элементов класса от другого кода.
Пример №1 — private: доступ для кода класса
Рассмотрим пример кода:
type Cat = class // родительский класс public name: string; // поле name constructor create(nameValue: string); procedure sayHello(); end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; var CatItem: Cat; begin CatItem := Cat.create('Мурка'); CatItem.name := 'Мурка22'; // можем заменить имя снаружи CatItem.sayHello(); end.
— здесь поле name может быть изменено во внешнем, коде, как это и показано в теле программы.
Давай закроем доступ к полю name для внешенего кода, сделать это можно перенеся поле name в секцию private:
type Cat = class // родительский класс public // секция публичных элементов constructor create(nameValue: string); procedure sayHello(); strict private // секция закрытых элементов name: string; // приватное поле end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; var CatItem: Cat; begin CatItem := Cat.create('Мурка'); // CatItem.name := 'Мурка22'; // не сработает! CatItem.sayHello(); end.
— если расскомментировать строку с попыткой замены значения поля во внешнем коде дела программы, то компилятор Паскаля бросит ошибку.
Пример №2 — protected для защищенных полей. Доступ только классу и наследникам
Если мы хотим дать доступ к какому-либо элементу класса только методам этого класса и еще и классам-наследникам (но при этом не давать доступ коду из других областей видимости), то мы можем использовать секцию protected, рассмотрим пример:
type Cat = class // родительский класс public constructor create(nameValue: string); procedure sayHello(); strict protected name: string; end; Tiger = class(Cat) // класс-потомок procedure roar(); // метод рычания end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; procedure Tiger.roar(); begin writeln(self.name + ': rrrrrrrrrr!'); end; var CatItem: Cat; TigerItem: Tiger; begin CatItem := Cat.create('Мурка'); CatItem.sayHello(); TigerItem := Tiger.create('Шархан'); TigerItem.sayHello(); TigerItem.roar(); // TigerItem.name = '123'; // не сработает для защищенного поля! end.
Принцип минимальных необходимых прав
Когда вы выбираете какой именно доступ дать для метода или поля класса, руководствуйтесь популярным и в других областях принципом минимальных необходимых прав:
прав должно быть ровно столько, сколько необходимо для работы
- если какие-то элементы не должны использоваться наследниками или еще более отдаленным кодом — используйте private
- если же наследникам нужен доступ -то protected.
- Если же доступ к элементу необходим и из внешнего кода — тогда единственный вариант это объявлять его в открытой секции public
Видео-материалы
- Инкапсуляция, private, модификаторы доступа — разбор кода: https://youtu.be/fxA1Ns7SPrI
- Модификатор protected и наследование классов — разбор кода: https://youtu.be/S7tliDtRlRE
Задачи для самостоятельного решения
- Есть код:
type Cat = class // родительский класс public constructor create(nameValue: string); procedure sayHello(); function getName(): string; strict protected name: string; end; Tiger = class(Cat) public function getRoarStart(): string; // вернет строку для начала рыка рыка procedure roar(); // само рычание end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.getName() + '!'); end; function Cat.getName(): string; begin result := self.name; end; function Tiger.getRoarStart(): string; begin result := self.getName() + ':'; end; procedure Tiger.roar(); begin writeln(self.getRoarStart() + ' rrrrrrrrrr!'); end; var CatItem: Cat; TigerItem: Tiger; begin CatItem := Cat.create('Мурка'); CatItem.sayHello(); TigerItem := Tiger.create('Шархан'); TigerItem.sayHello(); TigerItem.roar(); // рычит end.
Определение


Инкапсуляция это набор инструментов для управления доступом к данным или методам которые управляют этими данными. С детальным определением термина “инкапсуляция” можно ознакомиться в моей предыдущей публикации на Хабре по этой ссылке. Эта статья сфокусирована на примерах инкапсуляции в Си++ и Си.
Инкапсуляция в Си++
По умолчанию, в классе ( class ) данные и методы приватные ( private ); они могут быть прочитаны и изменены только классом к которому принадлежат. Уровень доступа может быть изменен при помощи соответствующих ключевых слов которые предоставляет Си++.
В Си++ доступно несколько спецификаторов, и они изменяют доступ к данным следующим образом:
- публичные ( public ) данные — доступны всем;
- защищенные ( protected ) — доступны только классу и дочерним классам;
- приватные ( private ) —доступны только классу которому они принадлежат.
Для краткости, только два уровня (приватный и публичный) будут освещены в примерах.
Пример инкапсуляции
В классе Contact , публичные переменные и методы доступны из основной программы ( main ). Приватные переменные и методы могут прочитаны, вызваны или изменены только самим классом.
#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >void print_numbers() < cout >; int main() < Contact Tony; Tony.print_numbers(); // cout
Попытка напечатать или изменить приватную переменную mobile_number из основной программы ( main ) вызовет ошибку при компиляции потому как доступ к приватным данным в классе ограничен.
Нарушение инкапсуляции с Друзьями (Хорошая практика)
В Си++ присутствует ключевое слово “друг” ( friend ) которое позволяет добавить исключения в общие правила доступа к данным. Если функция или класс названы другом ( friend ) класса Contact — они получают свободный доступ к защищенным или приватным данным.
Существует два основных правила дружбы — дружба не наследуется и не взаимна. Также, наличие “друзей” не изменяет уровень защищенности данных — приватные данные остаются приватными с исключением в виде “друга”.
#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >// Declaring a global 'friend' function friend void print_numbers( Contact some_contact ); >; void print_numbers( Contact some_contact ) < cout int main()
В этом примере, функция print_numbers() — обычная функция, не метод класса Contact . Объявление функции print_numbers() “другом” класса Contact — единственная причина по которой функция print_numbers() имеет доступ к приватным данным. Если убрать строку с определением друга — код не скомпилируется.
Примечание: друзьями лучше не злоупотреблять. Добавление друга стоит рассматривать как исключение, не как общую практику.
Нарушение инкапсуляции с Преобразованием типов и Указателями (Плохая практика)
Прежде всего, стоит заметить что использовать указатели и преобразование типов таким способом — плохая идея. Этот способ не гарантирует получения нужных данных. Он плохо читается и плохо поддерживается. Невзирая на это, он существует.
Си++ получил в наследство от Си множество инструментов, один из которых — преобразование типов ( typecasting ). По умолчанию, все переменные и методы в классе приватные. В то же время, стандартный уровень доступа к данным в структуре ( struct ) — публичный. Возможно создать структуру или полностью публичный класс в котором данные будут расположены идентично данным в классе Contact и используя преобразование типов получить доступ к приватным данным.
#include using namespace std; class Contact < private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor < mobile_number = 12345678; home_number = 87654321; >void print_numbers() < cout >; struct Contact_struct < int mobile_number; int home_number; >; int main() < Contact Tony; Contact_struct * structured_Tony; Tony.print_numbers(); structured_Tony = (Contact_struct *) & Tony; structured_Tony->mobile_number = 20; structured_Tony->home_number = 30; Tony.print_numbers(); return 0; >
Приватные данные были прочитаны и изменены благодаря преобразованию типов
Инкапсуляция в Си
Традиционно считается что инкапсуляция — один из ключевых ООП принципов. Тем не менее, это не лимитирует использование этого принципа в процедурно-ориентированных языках. В Си, инкапсуляция используется давно, невзирая на отсутствие ключевых слов “приватный” и “публичный”.
Приватные переменные
В контексте инкапсуляции, все данные в Си могут быть рассмотрены как публичные по умолчанию. Уровень доступа к переменным в структурах ( struct ) может быть изменен на приватный если изолировать их определение от основной программы. Нужный эффект может быть достигнут при использовании отдельных заголовочных (header, .h) и исходных (source, .c) файлов.
В данном примере, структура была определена в отдельном исходном файле “private_var.c”. Поскольку инициализация структуры в Си требует выделения и освобождения памяти, несколько вспомогательных функций были добавлены.
#include "private_var.h" #include #include struct Contact < int mobile_number; int home_number; >; struct Contact * create_contact() < struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); > void delete_contact( struct Contact * some_contact )
В соответствующем заголовочном файле "private_var.h", структура Contact была объявлена, но ее содержание осталось скрытым для основной программы.
#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif /* PRIVATE_VAR */
Таким образом, для “main.c” содержание структуры неизвестно и попытки прочитать или изменить приватные данные вызовут ошибку при компиляции.
#include "private_var.h" #include int main() < struct Contact * Tony; Tony = create_contact(); // printf( "Mobile number: %d\n", Tony->mobile_number); // will cause compile time error delete_contact( Tony ); return 0; >
Получение доступа к приватным переменным с Указателями
Преобразование типов может быть использовано для преодоления инкапсуляции в Си также как и в Си++, но данный подход уже был описан. Зная, что в структуре данные расположены в порядке их декларации, указатели и арифметика указателей подойдет для достижения цели.
Доступ к переменным в структуре ограничен. Тем не менее, спрятаны только переменные, не память в которой хранятся данные. Указатели можно рассматривать как ссылку на адрес памяти, и если эта память доступна программе — данные сохраненные в этой памяти можно прочитать и изменить. Если указатель назначен на память в которой структура хранит свои данные — их можно прочитать. Используя то же определение структуры (те же “.c” и “.h” файлы) и модифицированный “main.c” файл, ограничение доступа было преодолено.
#include "private_var.h" #include int main()
Данные в структуре были прочитаны и модифицированы
Приватные функции
Функции, будучи внешними ( extern ) по умолчанию, видимы во всей так называемой единице трансляции ( translation unit ). Другими словами, если несколько файлов скомпилированы вместе в один объектный файл, любой из этих файлов сможет получить доступ к любой функции из любого другого файла. Использование ключевого слова “статический” ( static ) при создании функции ограничит ее видимость до файла в котором она была определена.Следовательно, для обеспечения приватности функции необходимо выполнить несколько шагов:
- функция должна быть объявлена статической ( static ) либо в исходном файле (.c), либо в соответствующем заголовочном файле (.h);
- определение функции должно находиться в отдельном исходном файле.
В данном примере, в файле “private_funct.c”, была определена статическая функция print_numbers() . К слову, функция delete_contact() успешно вызывает print_numbers() поскольку они находятся в одном файле.
#include "private_funct.h" #include #include struct Contact < int mobile_number; int home_number; >; struct Contact * create_contact() < struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); > static void print_numbers( struct Contact * some_contact ) < printf("Mobile number: %d, ", some_contact->mobile_number); printf("home number = %d\n", some_contact->home_number); > void delete_contact( struct Contact * some_contact )
В соответствующем заголовочном файле "private_funct.h", print_numbers() была декларирована как статическая функция.
#ifndef PRIVATE_FUNCT_H #define PRIVATE_FUNCT_H struct Contact; struct Contact * create_contact(); static void print_numbers( struct Contact * some_contact ); void delete_contact( struct Contact * my_points ); #endif /* PRIVATE_FUNCT_H */
Основная программа, “main.c”, успешно вызывает print_numbers() опосредовательно через delete_contact() , поскольку обе функции находятся в одном документе. Тем не менее, попытка вызвать print_numbers() из основной программы вызовет ошибку.
#include "private_funct.h" #include int main() < struct Contact * Tony; Tony = create_contact(); // print_numbers( Tony ); // will cause compile time error delete_contact( Tony ); return 0; >
Получение доступа к приватным функциям
Вызвать функцию print_numbers() из основной программы возможно. Для этого можно использовать ключевое слово goto или передавать в main указатель на приватную функцию. Оба способа требуют изменений либо в исходном файле “private_funct.c”, либо непосредственно в теле самой функции. Поскольку эти методы не обходят инкапсуляцию а отменяют её, они выходят за рамки этой статьи.
Заключение
Инкапсуляция существует за пределами ООП языков. Современные ООП языки делают использование инкапсуляции удобным и естественным. Существует множество способов обойти инкапсуляцию и избежание сомнительных практик поможет ее сохранить как в Си, так и в Си++.
- инкапсуляция
- oop concepts
- encapsulation
