Инкапсуляция и ее использование в программах с. Идея защиты данных

24.04.2019 Разное

С усложнением программ основной идеей обеспечения их управляемости стало моделирование . Реальный мир состоит из взаимодействующих между собой объектов. С точки зрения программирования объекты, характеризуемые разнородными данными, можно описать как раз структурами , а взаимодействие объектов обеспечить функциями , которые будут работать с данными этих структур. При классическом процедурном подходе плохо лишь то, что данные "сами по себе", а функции их обработки тоже "сами по себе", тогда как моделировать удобнее и естественнее объекты, обладающие некой целостностью.

В принципе, механизм структур позволяет решить задачу объединения данных и функций, которые их обрабатывают, в одном объекте – ведь в структуры можно включать такие объекты, как указатели на функции . Указатель на функцию - это переменная, которая содержит адрес функции определённого типа с определённым набором параметров. Соответственно, косвенное обращение по этому указателю представляет собой вызов функции. А если переставить указатель на другую функцию того же типа с соответствующим списком параметров, то и будет вызвана другая функция. Поведение программы становится гибким, а значит, возможности моделирования возрастают.

Приведём пример. Мы хотим написать программу, позволяющую табулировать различные функции одним и тем же кодом. Естественным решением будет описание структуры, содержащей нужную информацию о функции:

#include #define M_PI 3.1415926 /* вспомогательные */ #define EPS 1e-9 /* константы */ using namespace std; typedef double (*function) (double); //указатель на функцию вида double имя(double) struct tab { //структура-табулятор функций double x1, x2; //пределы табулирования int n; //количество шагов от x1 до x2 function f; //конкретная функция, которую табулируем, //заданная указателем на неё static void run(tab t); //функция, которая табулирует f(x) };

Какая конкретно функция табулируется, будет определять указатель f , включённый в структуру. Передавая структуру типа tab в функцию табулирования run , мы сможем решить поставленную задачу:

Void run(tab t) { //функция табулирования любой функции double dx = (t.x2 - t.x1) / t.n; cout << endl; cout.width(10); cout << "X"; cout.width(10); cout << "Y"; for (double x = t.x1; x <= t.x2 + EPS; x += dx) { cout << endl; cout.width(10); cout << x; cout.width(10); cout << t.f(x); } }

Допишем программу, построив таблицы значений нескольких функций:

//конкретные функции для примера double f1(double x) { return sin(x); } double f2(double x) { return cos(x); } double f3(double x) { return x*x; } int main() { tab fun1 = { 0, M_PI, 10, f1 }; run(fun1); //описали и табулировали одну функцию tab fun2 = { 0, M_PI/2, 10, f2 }; run(fun2); //а теперь совсем другую fun1.f = f3; run(fun1); //программно поменяли функцию в структуре, переставив //указатель на другой объект cin.sync(); cin.get(); return 0; }

В нашем коде хорошо то, что программа может динамически менять свою логику, подставляя нужные функции в структуры. Однако хватает и неудобств:

  • в функцию run приходится передавать структуру или указатель на неё;
  • по-прежнему функция run никак не связана со структурой;
  • нельзя разделить или ограничить доступ к переменным и функциям структуры.

Можно сделать функцию "полноценным" членом структуры, но тогда всё равно придётся использовать инструментарий классов, немного забегая вперёд (показаны только изменённые участки кода):

Struct tab { double x1, x2; int n; function f; void run(); //изменено }; //... void tab::run(void) { //изменено double dx = (this->x2 - this->x1) / this->n; cout << endl; cout.width(10); cout << "X"; cout.width(10); cout << "Y"; for (double x = this->x1; x <= this->x2 + EPS; x += dx) { cout << endl; cout.width(10); cout << x; cout.width(10); cout << this->f(x); } } //... int main() { //изменено tab fun1 = { 0, M_PI, 10, f1 }; fun1.run(); tab fun2 = { 0, M_PI / 2, 10, f2 }; fun2.run(); fun1.f = f3; fun1.run(); cin.sync(); cin.get(); return 0; }

Примечания :

1. В этом коде this - "указатель объекта на самого себя", this->x1 означает, что надо взять переменную x1 , описанную именно внутри текущей структуры (в fun1 при вызове fun1.run();), а не в другой структуре или глобально.

2. Оператор:: имеет в C++ наивысший приоритет и означает указание области видимости объекта; в нашем случае то, что функция run относится к структурному типу tab .

В таком коде видно, что функция относится именно к структуре, мы вызываем её в виде fun1.run() , а не run(fun1) . Кроме того, мы сэкономили память стека, не передавая целые структуры внутрь функций.

Хотя у любого объекта класса есть собственная копия всех данных-членов, каждая функция-член существует в единственном экземпляре. Функция-член может обращаться к данным-членам разных объектов с помощью указателя this . Код с классами легче модифицируется и остаётся управляемым в больших проектах. Все современные языки и системы программирования объектно ориентированы, то есть, используют механизм классов.

Итак, объектно-ориентированное программирование (ООП) - парадигма программирования, основанная на понятиях класса и объекта .

Объект – это сущность, обладающая определённым состоянием, которое характеризуется свойствами , и поведением, которое характеризуется методами .

С точки зрения процедурного программирования, свойствам соответствуют переменные или массивы, описывающие состояние объекта, методам - функции, позволяющие управлять свойствами или обмениваться информацией с внешним миром.

Например, у класса "Студент" могут быть такие свойства как фамилия, группа, дата рождения, стипендия и т.д., методы "показать данные", "сменить номер группы", "начислить стипендию" и т.п.

Объекты всегда принадлежат одному или нескольким классам, которые определяют поведение объекта и являются его моделью. Термины "объект" и "экземпляр класса" взаимозаменяемы.

Класс - абстрактный тип данных, определяющий интерфейс и реализацию для всех своих экземпляров.

Например, объектом (экземпляром) класса "Студент" является конкретный "студент Иванов":

Student Ivanov; //или чаще: Student *Ivanov = new Student ();

Объекты обладают тремя базовыми свойствами (они же – три базовых принципа ООП ):

1. Инкапсуляция - механизм языка, разделяющий и ограничивающий доступ к составляющим объект компонентам (методам и свойствам). Например, приватные свойства и методы класса доступны только для экземпляров этого же, но не других классов.

Так, для класса "Служащий" свойство "Зарплата" может быть приватно. А для начисления зарплаты или получения сведений о ней мы можем предусмотреть в классе публичные методы "ПолучитьВеличинуЗП", "НачислитьЗП" и т.п. Вообще, чаще всего свойства класса приватны, а основные методы публичны. Это связано ещё и с тем, что прямое изменение свойств класса в виде объект.свойство=значение не даёт возможности выполнения дополнительных действий и затрудняет модификацию программы.

Пояснение этой важной мысли:

Object.property=a;

конечно, выглядит естественней, чем

Object.setproperty(a);

Но что, если свойство, изменённое нами "напрямую", меняет кто-то или что-то ещё? А если понадобилось при каждом "прямом" присваивании значения свойству сделать нечто дополнительно (сообщить в налоговую, что на счету добавилось денег)? А если нужно при присваивании провести встроенный контроль на допустимость значения? А в программе уже 1000 записей вида object.property=a ? В случае же object.setproperty(a) мы изменим только один-единственный метод setproperty в классе объекта.

2. Наследование - механизм языка, позволяющий описать новый класс на основе уже существующего (родительского, базового) класса. Класс-потомок может добавить собственные методы и свойства, а также пользоваться родительскими методами и свойствами.

Например, класс "Студент" в нашей программе может быть потомком класса "Человек", и пользоваться рядом его свойств и методов ("Фамилия", "Дата рождения"), а также иметь свои, отсутствующие в базовом классе ("номер группы", "стипендия").

3. Полиморфизм – это единообразная обработка разнотипных данных, возможность перегрузки классами своих методов и методов классов-предков.

Например, базовый класс "Геометрическая фигура" может иметь метод "Нарисовать", по-разному реализованный в классах-потомках "Прямоугольник" и "Окружность". Но для программиста все методы будут вызываться под одним именем draw , что удобно.

В языках программирования, построенных на парадигме ООП, обычно имеется иерархия встроенных классов, на вершине которой находится класс с именем вроде Object . А все остальные классы являются его потомками того или иного иерархического "поколения".

Итак, использование классов позволяет:

  • повысить степень повторного использования кода;
  • обеспечить удобный механизм для коллективной разработки приложений;
  • писать сколь угодно большие проекты, основанные на иерархии классов, в то время как процедурная программа длиннее 10-20 тысяч строк неизбежно становится плохо управляемой;
  • более гибко управлять созданием, удалением и доступностью объектов в процессе выполнения программы;
  • за счет механизма инкапусляции обеспечивать безопасность приложений.

Общий вид оператора описания класса следующий:

Class ИмяКласса { private: //область видимости в пределах класса, //доступ имеют только функции-члены данного класса Список членов класса; protected: //могут использоваться методами данного //и производных от него классов Список членов класса; public: //видимы вне класса, может осуществляться //доступ извне Список членов класса; };

Например,

Class Student { private: char *name; //имя - приватный член класса public: void show (void); //функция "показать" – публичная void setname (char *); //функция "установить имя" - публичная };

По умолчанию члены класса приватны, так что private: в начале можно было не указывать.

Функции-члены связаны с именем класса оператором:: и обычно описаны сразу после описания класса, к которому принадлежат. В остальном они выглядят как обычные функции С++:

Void Student::show (void) { //функция относится к классу Student }

Доступ к членам класса осуществляет оператор. (точка), как и для структур, он позволяет связать свойство или метод с конкретным экземпляром класса. При использовании указателей, как и со структурами, конструкция (*указатель).поле заменяется на указатель->поле.

Для того, чтобы вызываемая функция точно "знала", с каким из объектов класса она работает, Си++ неявно передает в любую нестатическую функцию дополнительный скрытый параметр - указатель на текущий объект, называемый this . Параметр this видим в теле вызываемой функции, устанавливается при вызове в значение адреса начала объекта и может быть использован для доступа к членам объекта.

Void Student::setname (char *name) { strcpy (this->name, name); }

Указание this позволит не перепутать имеющие одинаковые имена name параметр функции и имя одного из свойств класса.

Конструктор

Класс на Си++ может иметь любое количество конструкторов , предназначенных для инициализации данных класса при создании нового экземпляра. Конструктор всегда имеет то же имя, что и класс, в котором он определён, с созданием экземпляра всегда связано явное или неявное выполнение конструктора. Если отсутствует явно описанный конструктор, создается конструктор по умолчанию. Конструктор вызывается компилятором явно при создании объекта и неявно при выполнении оператора new, применяемого к объекту, а также при копировании объекта данного класса. Конструктор не возвращает значений. Как правило, он описан в public-секции класса. В остальном он оформляется как обычная функция, например с явно описанным конструктором наш класс примет вид:

#include using namespace std; class Student { private: char *name; public: Student (char *); //конструктор с 1 параметром void show (void); void setname (char *); }; Student::Student (char *name=NULL) { //реализация конструктора if (name) { this->name = new char ; if (this->name) setname (name); } else this->name=NULL; } void Student::setname (char *name) { strcpy (this->name, name); } void Student::show (void) { cout << endl; if (this->name) cout << this->name; else cout << "NULL"; } int main() { Student *Ivanov = new Student ("Ivanov"); Ivanov->show(); Student Petrov("Petrov"); Petrov.show(); cin.sync(); cin.get(); return 0; }

Заметим, что в этом несложном классе уже хватает "подводных камней", например, функция setname не проверяет, достаточно ли места там, куда она копирует строку:

Petrov.setname("Popandopulo"); //крах программы

С другой стороны, мы не можем "заставлять" функцию каждый раз перераспределять память, освобождая прежнее содержимое – ведь для объектов из стека, таких как Petrov , делать delete попросту нельзя. Одним из возможных решений было бы "не копировать лишнего", учитывая то, что имени может быть и не задано:

Void Student::setname (char *name) { if (this->name) { int len = strlen(this->name); strncpy (this->name, name, len); this->name="\0"; } } //... Student Petrov("Petrov"); Petrov.setname("Popandopulo"); Petrov.show(); //Popand Student Sidorov; Sidorov.setname("Sidorov"); Sidorov.show(); //NULL

Однако такая функция может породить серьёзные проблемы при копировании в неинициализированный объект наподобие того, что делается в строке

Student Copeikin = *Ivanov;

У такого объекта name может быть заполнено "мусором" и len получит неправильное значение, что неизбежно вызовет ошибку при попытке освобождения памяти. Приемлемой была бы такая реализация:

Void Student::setname (char *name) { if (this->name) delete this->name; int len = strlen(name); if (len) { this->name = new char ; if (this->name!=NULL) strcpy(this->name,name); } }

Но и это будет работать при условии, что свойство this->name предварительно инициализировалось в конструкторе до вызова setname .

Конструктор копирования для класса X имеет один аргумент типа &X и выполняется при присваивании экземпляров класса:

Student *Ivanov = new Student ("Ivanov"); Student Copeikin = *Ivanov;

Компилятор "опознаёт" конструктор копирования потому, что его параметром является ссылка на объект класса. Это тот самый объект, что находится справа от знака "присвоить", а объект слева от " = " доступен через this . Напишем реализацию конструктора копирования:

Public: //... Student (Student &); //конструктор копирования //... Student::Student (Student &from) { //реализация конструктора копирования this->name = new char ; if (this->name) setname (from.name); }

Деструктор

Деструктор выполняет действия, противоположные действиям конструктора, то есть, "разрушает" объект данного класса и вызывается явно или неявно. Неявный вызов деструктора связан с прекращением существования объекта из-за завершения области его определения. Явное уничтожение объекта выполняет оператор delete . Деструктор имеет то же имя, что класс, но предваренное символом ~ . Деструкторы не могут получать аргументы и быть перегружены. Класс может объявить только один общедоступный деструктор. Если класс не содержит объявления деструктора, компилятор автоматически создаст его.

Для нашего класса явный деструктор может иметь вид:

Student::~Student () { //реализация деструктора if (name) delete name; }

Здесь в области видимости внутри тела деструктора нет других переменных с именем name , так что this->name можно не писать.

При этом деструктор был описан в секции public класса:

Public: //... ~Student(); //деструктор

Если мы, при наличии явного деструктора, в приведённом выше коде

Student Copeikin = *Ivanov;

не задали явного конструктора копирования, конструктор копирования по умолчанию выполнил Copeikin.name = Ivanov->name , то есть, не скопировал строку, а лишь установил указатель Copeikin.name на тот же адрес памяти, куда показывал Ivanov->name . После явного или неявного удаления объекта Ivanov следствием попытки показать значение Copeikin.name будет крах программы. Подобные "потери памяти" очень трудноуловимы, поэтому за работой явных деструкторов нужно следить особенно внимательно.

Существует "правило большой тройки" для любых классов: если в классе есть явный деструктор, то требуется явный конструктор копирования и оператор "=". Возможно, имеет смысл определять в этом случае и явный конструктор без аргументов.

Пример 1 . Класс "паскалевских" массивов на C++. Для этих массивов элементы можно нумеровать с произвольного целого числа, а не только с нуля.

#include #include #define MAXDOUBLE 1.797693E+308 using namespace std; class Array { private: int low,hi; double *value; public: Array() : low(0), hi(0), value(NULL) {} Array(int); Array(int,int); ~Array(); double get (int index); void set (double value,int index); //Способ лучше: переопределить оператор в классе }; Array::Array (int k1, int k2) { low=k1; hi=k2; value = new double ; } Array::Array (int n) { new Array (0,n-1); } Array::~Array () { delete value; } double Array::get (int index) { return (indexhi ? MAXDOUBLE: value); } void Array::set (double newvalue,int index) { if (index>=low || index<=hi) value=newvalue; } int main () { Array my(-5,5); for (int i=-5; i<=5; i++) { my.set(i+5.,i); cout << "\nKey=" << i << ", value=" << my.get(i); } my.set(my.get(5),-5); cout << "\nItem -5 changed=" << my.get(-5); cout << "\nKey=-10 (bad), value=" << my.get(-10) << endl; system("pause"); return 0; }

Узнав о переопределении операторов, мы сможем обращаться к объектам класса в виде my вместо my.get(5) .

Оператор: (двоеточие) в конструкторе класса означает "список инициализации". Это список, в котором через запятую перечислены пары из имени члена класса и значения, которое необходимо ему присвоить, взятого в круглые скобки. Через список инициализация членов класса происходит перед тем, как выполняется вход в тело конструктора, и выполняется быстрее. К тому же, константные данные можно инициализировать только через список.

Пример 2 . Определим альтернативный класс "Студент" и выполним над его объектами основные описанные в лекции действия.

#include #include #include #include using namespace std; class Student { private: //Обычно атрибут дается основным свойствам char *Name; int Group; public: //Основные методы //Конструкторы: Student (void); //по умолчанию: Student *s=new Student(); Student (char *,int); //фамилия и номер группы: //Student *s2=new Student("Petrov",210); Student (Student &); //копирования ~Student (); //деструктор void setName (char *); //Функции для получения char *getName (void); //и установки свойств void setGroup (int); //с атрибутом private int getGroup (void); //Прочие функции void showStudent (void); }; Student::Student (void) { Name = NULL; Group = 0; } Student::Student (char *newName,int newGroup=0) { Name =(char *)malloc((strlen(newName)+1)*sizeof(char)); if (Name != NULL) { strcpy(Name,newName); Group = 0; } Group = newGroup; } Student::Student (Student &From) { setName (From.Name); setGroup (From.Group); } Student::~Student () { delete Name; } void Student::setName (char *Name) { int n=strlen(Name); if (n>0) { this->Name = new char ; if (this->Name!=NULL) strcpy(this->Name,Name); } } void Student::setGroup (int g) { Group=g; } char * Student::getName () { return Name; } int Student::getGroup () { return Group; } void Student::showStudent (void) { printf ("\n%s,%d\n",Name,Group); } int main () { Student *NoName = new Student (); Student *Ivanov = new Student ("Ivanov"); Ivanov->setGroup(110); Student *Petrov319 = new Student ("Petrov",319); NoName->showStudent(); Ivanov->showStudent(); Petrov319->showStudent(); delete Petrov319; Student Popov("Popov",210); Popov.showStudent(); Popov=*Ivanov; Popov.showStudent(); system("pause"); return 0; }

Немного примеров-рассуждений о конструкторах и деструкторах есть также в .

Программа, разработанная с помощью ОО подхода (объектно-ориентированного), представляет собой действующую модель решаемой задачи.

Терминология

Объект - конструкция, в которой инкапсулировано состояние (свойства ) и поведение (методы ). По факту, методы реализуют поведение объекта. В чистом ОО языке программирования все данные являются объектами. От простых базовых типов данных до сложных экземпляров классов.

От общего к частному: ООП » (инкапсуляция, наследование, полиморфизм, [композиция]) » GRASP » GoF

Преимущества, цели и принципы ОО подхода

  1. Естественность . Построение модели на функциональном уровне, а не на уровне реализации. Использование объектов из конкретной доменной модели (предметной области).
  2. Надежность , сопровождение , расширение . Достигается за счет изолированности компонент системы. Изменение одной компоненты не влияет (не должно влиять) на другие части.

Инкапсуляция

Понятие инкапсуляции ООП полностью заимствовало из модульного программирования.

Три характерных признака эффективной инкапсуляции

  1. Абстракция - процесс упрощения сложной схемы взаимодействия объектов путём изъятия всех несущественных, оставляя только корневые объекты и супер-важные связи. Преимущества : упрощение решения и повторное использование объектов из разных доменных областей. Важно : грань между абстракцией и детализацией очень тонка.
  2. Сокрытие реализации (за интерфейсом) - нужно для защиты объектов от пользователя и наоборот.
  3. Делегирование ответственности .

Правила эффективной абстракции

  1. Нужно рассматривать общий случай, а не конкретный;
  2. Нужно распознать основной принцип, а не только общую последовательность;
  3. Абстрагируясь, нужно вспоминать о решаемой задаче;
  4. Абстракцию можно применять к задачам, которые уже решались вами не менее 3-х раз;
  5. Абстракция не всегда очевидна. А также не возможно написать абстрактную программу на любой случай;

Как эффективно скрывать реализацию и создавать слабосвязанные объекты

  1. Доступ к данным абстрактного типа должен осуществляться только через методы интерфейса (скрываем реализацию);
  2. Исключить возможность бесконтрольного доступа к структурам данных;
  3. Не следует угадывать тип данных, если поведение метода не описано в интерфейсе;
  4. Быть осторожным при написании 2-х тесно связанных типов;

Наследование

Наследование используется для простоты расширения класса. При наследовании нужно соблюдать сигнатуру методов (конструкторы тоже, с версии PHP 5.4). При наследовании допускается понижение строгости при указании области видимости (уровня доступа).

Полиморфизм

Полиморфизм ввели для простого и гибкого изменения программ.

Классы, методы, свойства

Типы классов:

  • Абстрактный;
    • Класс, который содержит по крайней мере один абстрактный метод, должен быть определен как абстрактный;
    • Нельзя создать экземпляр абстрактного класса;
  • Интерфейс;
    • Методы интерфейсов не могут содержать реализацию и должны быть публичными;
    • Класс не может реализовать два интерфейса, содержащих одноименную функцию;
    • Интерфейсы могут наследоваться;
    • Сигнатуры методов в реализующем классе должны точно соответствовать сигнатурам в интерфейсе;

Типы методов:

  • Абстрактный;
    • Не содержат реализации, а требует ее в дочернем классе;
  • Статический;
    • Реализация не переопределяется (в отличие от абстрактных);

Преимущества Объектно-ориентированного программирования.

1) Построение сложных систем 2) Повышение надежности программного обеспечения 3) Улучшение сопровождения программного обеспечения. Возможность внесения изменений отдельных компонент изменения остальных частей 4) Расширение и масштабирование программного кода 5) Создание повторно используемого программного кода.

Под обьектом будем понимать понятие, абстракцию или любой предмет с четко очерченными границами, имеющий смысл в аспекте рассматриваемой проблемы. Введение объектов имеет следующие цели:

1) добиться большего понимания задачи;

2) введение основы для реализации на ЭВМ.

Состояние : одно из стабильных условий, в которых объект может существовать, охарактеризованных количественно; в любой момент времени состояние объекта

включает перечень свойств и их текущее значение, которые собственно

определяют поведение объекта.

Поведение : для каждого объекта существует определенный набор действий, который с ним можно произвести. Например, возможные действия с некоторым файлом: создать/закрыть/открыть/удалить и тд

Уникальность: это то, что отличает объект от других объектов.

Классы: шаблон поведения объектов определенного

типа с заданными параметрами, определяющими состояние. Все экземпляры одного класса (объекты, порожденные от одного класса) имеют один и тот же набор свойств и общее поведение, то есть одинаково реагируют на одинаковые сообщения.

Объектно-ориентированное программирование (ООП) - это методо-

логия программирования, опирающаяся на три базовых принципа:

1. инкапсуляцию;

2. наследование;

3. полиморфизм.

===================

1) Инкапсуляция - процесс отделения друг от друга элементов объекта, определяющих его внутреннее устройство и поведение от внешнего (интерфейса); говорят инкапсуляция скрывает реализациию класса. Поэтому понятия инкапсуляции и сокрытия информации можно считать синонимами при рассмотрении в Java. С целью реализации принципа инкапсуляции, каждый класс должен иметь все необходимые методы для обработки имеющихся данных. Инкапсуляция связывает в класс аттрибуты с методами которые имеют отношения к их обработке.

В соответствии с принципом инкапсуляции обьъект не должен получать прямой доступ к атрибутам другого объекта. Чтобы получить доступ к атрибутам класса необходимо обратиться к общедоступным public методам этого класса. А уже те могут получить доступ, по необходимости к другим методам этого класса или напрямую к атрибутам этого класса. При использовании объектно-ориентированного подхода не принято применять прямой доступ к свойствам какого-либо класса из методов других классов. Для доступа к свойствам класса принято задействовать специальные методы этого класса для получения и изменения его свойств.

Таким образом инкапсуляция приводит к следующему:

1) исключение возможности искажения данных или случайного несанкционированного их изменения 2) Упрощение процесса изменения структуры данных.

19. Основные принципы ооп. Наследование. Модификаторы доступа.

2) Наследование (inheritance) - это механизм создания нового класса на основе уже существующего используя структуру и поведение (одиночное наследование), или других (множественное наследование) классов. Наследование вводит иерархию "общее-частное", в которой подкласс наследует от одного или нескольких более общих суперклассов. Подклассы обычно дополняют или переопределяют унаследованную структуру и поведение.

Исходный класс называется - базовым илисуперклассом (класс предок), а класс потомок -подклассом . От потомков, в свою очередь, можно наследовать, получая очередных потомков. И так далее. Набор классов, связанных отношением наследования, называетсяиерархией классов . А класс, стоящий во главе иерархии, от которого унаследованы все остальные (прямо или опосредованно), называетсябазовым классом иерархии . В Java все классы являются потомками класса Object.

Абстрактный класс - это класс, методы которого не имеют реализации. Абстрактный класс включает то общее, что характерно целому набору классов. Применение абстрактных классов позволяет лучше понять взаимосвязи между существующими классами.

Инкапсуляция (программирование)

В языках программирования инкапсуля́ция имеет одно из следующих значений, либо их комбинацию:

  • языковой механизм ограничения доступа к определённым компонентам объекта;
  • языковая конструкция, способствующая объединению данных с методами (или другими функциями), обрабатывающими эти данные.

Инкапсуляция - один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией , полиморфизмом и наследованием).

В то же время, в языках поддерживающих замыкания , инкапсуляция рассматривается как понятие не присущее исключительно объектно-ориентированному программированию. Также, реализации абстрактных типов данных (например, модули) предлагают схожую модель инкапсуляции.

Сокрытие реализации целесообразно применять в следующих целях:

  • предельная локализация изменений при необходимости таких изменений,
  • прогнозируемость изменений (какие изменения в коде нужно сделать для заданного изменения функциональности) и прогнозируемость последствий изменений.

Примеры

C++

Class A { public : int a, b; //данные открытого интерфейса int ReturnSomething() ; //метод открытого интерфейса private : int Aa, Ab; //скрытые данные void DoSomething() ; //скрытый метод } ;

Класс А инкапсулирует свойства Aa, Ab и метод DoSomething, представляя внешний интерфейс ReturnSomething, a, b.

C#

Целью инкапсуляции является обеспечение согласованности внутреннего состояния объекта. В для инкапсуляции используются публичные свойства и методы объекта. Переменные, за редким исключением, не должны быть публично доступными. Проиллюстрировать инкапсуляцию можно на простом примере. Допустим, нам необходимо хранить вещественное значение и его строковое представление (например, для того, чтобы не производить каждый раз конвертацию в случае частого использования). Пример реализации без инкапсуляции таков:

Class NoEncapsulation { public double Value ; public string ValueString; }

При этом мы можем отдельно изменять как само значение Value , так и его строковое представление, и в некоторый момент может возникнуть их несоответствие (например, в результате исключения). Пример реализации с использованием инкапсуляции:

Class EncapsulationExample { private double valueDouble; private string valueString; public double Value { get { return valueDouble; } set { valueDouble = value ; valueString = value . ToString () ; } } public string ValueString { get { return valueString; } set { double tmp_value = Convert. ToDouble (ValueString) ; //здесь может возникнуть исключение valueDouble = tmp_value; valueString = ValueString; } } }

Здесь доступ к переменным valueDouble и valueString возможен только через свойства Value и ValueString . Если мы попытаемся присвоить свойству ValueString некорректную строку и возникнет исключение в момент конвертации, то внутренние переменные останутся в прежнем, согласованном состоянии, поскольку исключение вызывает выход из процедуры.

Delphi

PHP5

Class A { private $a ; // скрытое свойство private $b ; // скрытое свойство private function DoSomething() //скрытый метод { //actions } public function ReturnSomething() //открытый интерфейс { //actions } } ;

В этом примере закрыты свойства $a и $b для класса A с целью предотвращения повреждения этих свойств другим кодом, которому необходимо предоставить только права на чтение.

Java

Class A { private int a; private int b; private void doSomething() { //скрытый метод //actions } public int returnSomething() { //открытый метод return a; } }

JavaScript

A = function() { // private var _property; var _privateMethod = function() { /* actions */ } // скрытый метод // public this .getProperty = function() { // открытый интерфейс return _property; } this .setProperty = function(value) { // открытый интерфейс _property = value; _privateMethod() ; } }

См. также


Wikimedia Foundation . 2010 .

Смотреть что такое "Инкапсуляция (программирование)" в других словарях:

    - (лат. in в, capsula коробочка; итал. incapsulare закупоривать) 1. Изоляция, закрытие чего либо мешающего, ненужного, вредного с целью исключения отрицательного влияния на окружающее. (Поместить радиоактивные отходы в капсулу, закрыть… … Википедия

    В объектно ориентированном программировании сокрытие внутренней структуры данных и реализации методов объекта от остальной программы. Другим объектам доступен только интерфейс объекта, через который осуществляется все взаимодействие с ним. По… … Финансовый словарь

    Инкапсуляция свойство языка программирования, позволяющее объединить данные и код в объект и скрыть реализацию объекта от пользователя. При этом пользователю предоставляется только спецификация (интерфейс) объекта. Пользователь может… … Википедия

    У этого термина существуют и другие значения, см. Интерфейс (значения). Интерфейс (от лат. inter «между», и face «поверхность») семантическая и синтаксическая конструкция в коде программы, используемая для специфицирования… … Википедия

    У этого термина существуют и другие значения, см. Полиморфизм. Эта статья или раздел нуждается в переработке. Пожалуйста, улучшите статью … Википедия

    У этого термина существуют и другие значения, см. Контейнер. Контейнер в программировании структура (АТД), позволяющая инкапсулировать в себя объекты разных типов. Среди «широких масс» программистов наиболее известны контейнеры, построенные … Википедия

    У этого термина существуют и другие значения, см. Абстракция (значения). Типичное представление архитектуры компьютера в … Википедия

    Эта статья во многом или полностью опирается на неавторитетные источники. Информация из таких источников не соответствует требованию проверяемости представленной информации, и такие ссылки не показывают значимость темы статьи. Статью можно… … Википедия

Хранение объектов в памяти. Доступ к свойствам из методов

Вопрос о том, как именно объекты хранятся в памяти, не столь прост. Очевидно, что свойства объекта являются полными аналогами полей записи и хранятся точно так же, как и записи. Но как быть с методами? Ведь это не переменные, а куски исполнимого машинного кода. А если мы создали несколько переменных, принадлежащих к объектному типу, как быть со свойствами и методами?

Для устранения неоднозначности в этом вопросе было введено понятие "экземпляр объекта ". Каждая переменная объектного типа является экземпляром соответствующего типа. Для каждого экземпляра создается свой набор свойств, под которые выделяется память. Свойства одного экземпляра никак не связаны со свойствами другого экземпляра. Это и понятно: если в программе есть объект "зубчатое колесо", и мы должны моделировать зубчатую передачу, то свойства у ведущего и ведомого колес будут разными. А вот методы у всех экземпляров совершенно одинаковы. Их хранят в единственной копии для всех экземпляров объекта данного типа (Рис. 5.2).

Иными словами, все экземпляры объекта пользуются одним набором методов, но разными и не связанными между собой наборами свойств.

Рис. 9.2. Хранение объектов в памяти.

Следующая трудность возникает при доступе к свойству объекта. Рассмотрим пример кода:

TYPE TA=CLASS
a:WORD;
PROCEDURE abc;

PROCEDURE TA.abc;
VAR a:BYTE;
BEGIN
a:=10
END;

Чему будет присваиваться значение 10 при выполнении метода abc – локальной переменной с именем а, описанной внутри этого метода, или же свойству объекта с тем же именем а? Если свойству объекта, то каким образом программа будет знать, о каком из экземпляров объекта идет речь? Метод abc – общий на все экземпляры, в каждом из экземпляров есть свойство а. Какое же из них менять?

Для устранения неопределенности используется ключевое слово SELF – указатель на текущий экземпляр объекта:

PROCEDURE TA.abc;
VAR a:BYTE;
BEGIN
Self.a:=10 { присваивание свойству }
a:=20 { присваивание локальной переменной }
END;

Self всегда знает, с каким экземпляром объекта идет работа. Откуда? Это очень просто – с каждым экземпляром объекта связано имя объектной переменной. Это же имя ставится перед вызываемым методом, например, x.abc. Имея такую информацию, компилятор будет знать, что внутри метода слово Self в данный момент заменяет собой имя переменной х.

Создаваемые программистом объекты могут включаться в библиотеки и использоваться не только им лично, но и многими другими разработчиками программ. Следовательно, нужно предусмотреть какую-то защиту от неверного использования свойств и методов. С другой стороны, как мы уже видели, прямое изменение значений свойств объекта – операция крайне нежелательная (изменили модуль зуба, а диаметр пересчитать забыли). Эти два соображения привели к появлению принципа инкапсуляции , гласящего: значения свойств не должны меняться напрямую, а только через вызовы соответствующих методов . Рассмотрим два примера программного кода:

В первом случае значение свойства а можно менять напрямую, просто написав с.а:=10. Это явное нарушение принципа инкапсуляции. Во втором случае для изменения значения свойства а введен специальный метод SetA. Внутри этого метода при необходимости будут пересчитываться значения других свойств, зависящих от значения свойства а. Кроме того, можно выполнять проверку корректности присеваемого свойству значения. Если речь идет о свойстве "число зубьев зубчатого колеса", то это число не может быть меньше шести (что, кстати, отражено в самом слове "шестеренка"), иначе не получится плавного зацепления.

Таким образом, инкапсуляция нужна по следующим соображениям:

1. Изменение значения одного из свойств часто должно приводить к изменению значения другого свойства.

2. Возможна проверка корректности присваиваемых свойству значений.

Однако ввести метод для присваивания значения свойству еще недостаточно. Ничего же не запрещает горе-программисту не использовать этот метод и по-прежнему писать с.а:=10, что разрушит все наши построения. Поэтому для полного соблюдения принципа инкапсуляции имеется возможность создания скрытых свойств , которые нельзя будет менять напрямую, а только через вызовы методов:

TYPE TA=CLASS
PRIVATE
a:REAL;
PUBLIC
b:REAL
END;

Перед списком скрытых свойств ставится слово PRIVATE, а перед списком открытых для изменения свойств – слово PUBLIC. В приведенном фрагменте свойство а является скрытым и попытка выполнить оператор вида с.а:=10 приведет к ошибке "Field identifier expected". Однако внутри методов все скрытые свойства видны по-прежнему. Рекомендуется делать скрытыми все свойства объекта