Выбрать главу

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

Методы, получающие и устанавливающие значения закрытых данных, должны быть открытыми.

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

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

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

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

Для стека определены две основные операции: поместить данные в стек и извлечь их оттуда. Первая операция помещает значение на вершину стека, а вторая — извле кает значение из вершины стека. Следовательно, операция извлечения является без возвратной: как только значение извлекается из стека, оно удаляется и уже недоступно в стеке.

В рассматриваемом здесь примере создается класс Stack, реализующий функции стека. В качестве базовых средств для хранения данных в стеке служит закрытый мас сив. А операции размещения и извлечения данных из стека доступны с помощью от крытых методов класса Stack. Таким образом, открытые методы действуют по упо мянутому выше принципу "последним пришел — первым обслужен". Как следует из приведенного ниже кода, в классе Stack сохраняются символы, но тот же самый меха низм может быть использован и для хранения данных любого другого типа. // Класс для хранения символов в стеке. using System; class Stack { // Эти члены класса являются закрытыми. char[] stck; // массив, содержащий стек int tos; // индекс вершины стека // Построить пустой класс Stack для реализации стека заданного размера. public Stack(int size) { stck = new char[size]; // распределить память для стека tos = 0; } // Поместить символы в стек. public void Push(char ch) { if(tos==stck.Length) { Console.WriteLine(" - Стек заполнен."); return; } stck[tos] = ch; tos++; } // Извлечь символ из стека. public char Pop() { if(tos==0) { Console.WriteLine(" - Стек пуст."); return (char) 0; } tos--; return stck[tos]; } // Возвратить значение true, если стек заполнен. public bool IsFull() { return tos==stck.Length; } // Возвратить значение true, если стек пуст. public bool IsEmpty() { return tos==0; } // Возвратить общую емкость стека. public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в данный момент в стеке. public int GetNum() { return tos; } }

Рассмотрим класс Stack более подробно. В начале этого класса объявляются две следующие переменные экземпляра. // Эти члены класса являются закрытыми. char[] stck; // массив, содержащий стек int tos; // индекс вершины стека

Массив stck предоставляет базовые средства для хранения данных в стеке (в дан ном случае — символов). Обратите внимание на то, что память для этого массива не распределяется. Это делается в конструкторе класса Stack. А член tos данного класса содержит индекс вершины стека.

Оба члена, tos и stck, являются закрытыми, и благодаря этому соблюдается прин цип "последним пришел — первым обслужен". Если же разрешить открытый доступ к члену stck, то элементы стека окажутся доступными не по порядку. Кроме того, член tos содержит индекс вершины стека, где находится первый обслуживаемый в стеке элемент, и поэтому манипулирование членом tos в коде, находящемся за преде лами класса Stack, следует исключить, чтобы не допустить разрушение самого стека. Но в то же время члены stck и tos доступны пользователю класса Stack косвенным образом с помощью различных отрытых методов, описываемых ниже.

Рассмотрим далее конструктор класса Stack. // Построить пустой класс Stack для реализации стека заданного размера. public Stack(int size) { stck = new char[size]; // распределить память для стека tos = 0; }

Этому конструктору передается требуемый размер стека. Он распределяет память для базового массива и устанавливает значение переменной tos в нуль. Следователь но, нулевое значение переменной tos указывает на то, что стек пуст.

Открытый метод Push() помещает конкретный элемент в стек, как показано ниже. // Поместить символы в стек. public void Push(char ch) { if (tos==stck.Length) { Console.WriteLine(" - Стек заполнен."); return; } stck[tos] = ch; tos++; }

Элемент, помещаемый в стек, передается данному методу в качестве параметра ch. Перед тем как поместить элемент в стек, выполняется проверка на наличие свободного места в базовом массиве, а именно: не превышает ли значение переменной tos длину массива stck. Если свободное место в массиве stck есть, то элемент сохраняется в нем по индексу, хранящемуся в переменной tos, после чего значение этой переменной инкрементируется. Таким образом, в переменной tos всегда хранится индекс следую щего свободного элемента массива stck.

Для извлечения элемента из стека вызывается открытый метод Pop(), приведен ный ниже. // Извлечь символ из стека. public char Pop() { if(tos==0) { Console.WriteLine(" - Стек пуст."); return (char) 0; } tos--; return stck[tos]; }

В этом методе сначала проверяется значение переменной tos. Если оно равно нулю, значит, стек пуст. В противном случае значение переменной tos декрементиру ется, и затем из стека возвращается элемент по указанному индексу.

Несмотря на то что для реализации стека достаточно методов Push() и Pop(), по лезными могут оказаться и другие методы. Поэтому в классе Stack определены еще четыре метода: IsFull(), IsEmpty(), Capacity() и GetNum(). Эти методы предо ставляют всю необходимую информацию о состоянии стека и приведены ниже. // Возвратить значение true, если стек заполнен. public bool IsFull() { return tos==stck.Length; } // Возвратить значение true, если стек пуст. public bool IsEmpty() ( return tos==0; } // Возвратить общую емкость стека. public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в данный момент в стеке. public int GetNum() { return tos; }

Метод IsFull() возвращает логическое значение true, если стек заполнен, а ина че — логическое значение false. Метод IsEmpty() возвращает логическое значение true, если стек пуст, а иначе — логическое значение false. Для получения общей ем кости стека (т.е. общего числа элементов, которые могут в нем храниться) достаточно вызвать метод Capacity(), а для получения количества элементов, хранящихся в на стоящий момент в стеке, — метод GetNum(). Польза этих методов состоит в том, что для получения информации, которую они предоставляют, требуется доступ к закры той переменной tos. Кроме того, они служат наглядными примерами организации безопасного доступа к закрытым членам класса с помощью открытых методов.

Конкретное применение класса Stack для реализации стека демонстрируется в приведенной ниже программе. // Продемонстрировать применение класса Stack. using System; // Класс для хранения символов в стеке. class Stack { // Эти члены класса являются закрытыми. char[] stck; // массив, содержащий стек int tos; // индекс вершины стека // Построить пустой класс Stack для реализации стека заданного размера. public Stack (int size) { stck = new char[size]; // распределить память для стека tos = 0; } // Поместить символы в стек. public void Push(char ch) { if(tos==stck.Length) { Console.WriteLine(" - Стек заполнен."); return; } stck[tos] = ch; tos++; } // Извлечь символ из стека. public char Pop() { if(tos==0) { Console.WriteLine(" - Стек пуст."); return (char) 0; } tos--; return stck[tos]; } // Возвратить значение true, если стек заполнен. public bool IsFull() { return tos==stck.Length; } // Возвратить значение true, если стек пуст. public bool IsEmpty() { return tos==0; } // Возвратить общую емкость стека. public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в данный момент в стеке. public int GetNum() { return tos; } } class StackDemo { static void Main() { Stack stk1 = new Stack(10); Stack stk2 = new Stack(10); Stack stk3 = new Stack(10); char ch; int i; // Поместить ряд символов в стек stk1. Console.WriteLine("Поместить символы А-J в стек stk1."); for(i=0; !stk1.IsFull(); i++) stk1.Push((char) ('A' + i)); if(stk1.IsFull()) Console.WriteLine("Стек stk1 заполнен."); // Вывести содержимое стека stk1. Console.Write("Содержимое стека stk1: "); while( !stk1.IsEmpty() ) { ch = stk1.Pop(); Console.Write(ch); } Console.WriteLine(); if(stk1.IsEmpty()) Console.WriteLine("Стек stk1 пуст.\n"); // Поместить дополнительные символы в стек stk1. Console.WriteLine("Вновь поместить символы A-J в стек stk1."); for(i=0; !stk1.IsFull(); i++) stk1.Push((char) ('A' + i)); // А теперь извлечь элементы из стека stk1 и поместить их в стек stk2. // В итоге элементы сохраняются в стеке stk2 в обратном порядке. Console.WriteLine("А теперь извлечь символы из стека stk1\n" + "и поместить их в стек stk2."); while( !stk1.IsEmpty() ) { ch = stk1.Pop(); stk2.Push(ch); } Console.Write("Содержимое стека stk2: "); while( !stk2.IsEmpty() ) { ch = stk2.Pop(); Console.Write(ch); } Console.WriteLine("\n"); // Поместить 5 символов в стек. Console.WriteLine("Поместить 5 символов в стек stk3."); for(i=0; i < 5; i++) stk3.Push((char) ('A' + i)); Console.WriteLine("Емкость стека stk3: " + stk3.Capacity()); Console.WriteLine("Количество объектов в стеке stk3: " + stk3.GetNum()); } } При выполнении этой программы получается следующий результат.