Материал предоставлен http://it.rfet.ru

Практический пример организации управления доступом

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

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

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

В рассматриваемом здесь примере создается класс 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 и nstck, являются закрытыми, и благодаря этому соблюдается принцип “последним пришел — первым обслужен”. Если же разрешить открытый доступ к члену 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 stkl = new Stack(10);
    Stack stk2 = new Stack(10);
    Stack stk3 = new Stack(10);
    char ch;
    int i;

    // Поместить ряд символов в стек stkl.
    Console.WriteLine("Поместить символы A-J в стек stk1.");
    for (i = 0; !stkl.IsFull(); i++)
      stkl.Push((char)('A' + i));

    if (stkl.IsFull())
      Console.WriteLine("Стек stk1 заполнен.");

    // Вывести содержимое стека stkl.
    Console.Write("Содержимое стека stk1:  ");
    while (!stkl.IsEmpty()) {
      ch = stkl.Pop();
      Console.Write(ch);
    }

    Console.WriteLine();

    if (stkl.IsEmpty())
      Console.WriteLine("Стек stk1 пуст.\n");

    // Поместить дополнительные символы в стек stkl.
    Console.WriteLine("Вновь поместить символы A-J в стек stk1.");
    for (i = 0; !stkl.IsFull(); i++)
      stkl.Push((char)('A' + i));

    // А теперь извлечь элементы из стека stkl и поместить их в стек stk2.
    // В итоге элементы сохраняются в стеке stk2 в обратном порядке.
    Console.WriteLine("А теперь извлечь символы из стека stk1\n" +
    "и поместить их в стек stk2.");
    while (!stkl.IsEmpty()) {
      ch = stkl.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());
  }
}

При выполнении этой программы получается следующий результат.

Поместить символы A-J в стек stk1.
Стек stk1 заполнен.
Содержимое стека stk1:  JIHGFEDCBA
Стек stk1 пуст.

Вновь поместить символы A-J в стек stk1.
А теперь извлечь символы из стека stk1
и поместить их в стек stk2.
Содержимое стека stk2:  ABCDEFGHIJ

Поместить 5 символов в стек stk3.
Емкость стека stk3:  10
Количество объектов в стеке stk3:  5
Организация закрытого и открытого доступаПередача объектов методам по ссылке