"Сокрытие деталей реализации называется инкапсуляцией (от слова "капсула"). " Сколько можно объяснять?! ИНКАПСУЛЯЦИЯ НЕ РАВНА СОКРЫТИЮ!!! Инкапсуляция это парадигма ООП, которая ОБЕСПЕЧИВАЕТ СОКРЫТИЕ!!! НО СОКРЫТИЕМ НЕ ЯВЛЯЕТСЯ!!! Если буровая коронка обеспечивает разрушение породы, то является ли она сама разрушением породы? Конечно нет! |
Классы: подробности
Пример класса с перегруженными методами и операциями
Рассмотрим решение следующей задачи.
В текстовом файле хранится база отдела кадров предприятия. На предприятии 100 сотрудников. Каждая строка файла содержит запись об одном сотруднике. Формат записи: фамилия и инициалы (30 поз., фамилия должна начинаться с первой позиции), год рождения (5 поз.), оклад (10 поз.). Написать программу, которая по заданной фамилии выводит на экран сведения о сотруднике, подсчитывая средний оклад всех запрошенных сотрудников.
Для решения этой задачи логично создать класс "сотрудник" и организовать из экземпляров этого класса массив. При описании класса полезно задаться следующим вопросом: какие обязанности должны быть возложены на этот класс? Очевидно, что первая обязанность — хранение сведений о сотруднике. Чтобы воспользоваться этими сведениями, клиент (то есть код, использующий класс) должен иметь возможность получить эти сведения, изменить их и вывести на экран. Кроме этого, для поиска сотрудника желательно иметь возможность сравнивать его имя с заданным.
Поля класса сделаем в соответствии с принципом инкапсуляции закрытыми, а доступ к ним организуем с помощью свойств. Это позволит контролировать процесс занесения данных в поля. Для идентификации попытки ввода неверных данных воспользуемся механизмом исключений. Чтобы сделать класс более универсальным, введем в него возможность увеличивать и уменьшать оклад сотрудника с помощью перегруженных операций класса.
На этом примере мы формализуем общий порядок создания программы, которого полезно придерживаться при решении даже простейших задач
I. Исходные данные, результаты и промежуточные величины.
Исходные данные. База сотрудников находится в текстовом файле. Прежде всего надо решить, хранить ли в оперативной памяти одновременно всю информацию из файла или можно обойтись буфером на одну строку. Если бы сведения о сотруднике запрашивались однократно, можно было бы остановиться на втором варианте, но поскольку поиск по базе будет выполняться более одного раза, всю информацию желательно хранить в оперативной памяти, поскольку многократное чтение из файла крайне нерационально.
Максимальное количество строк файла по условию задачи ограничено, поэтому можно выделить для их хранения массив из 100 элементов. Каждый элемент массива будет содержать сведения об одном сотруднике, организованные в виде класса.
Примечание
Строго говоря, для решения этой конкретной задачи запись о сотруднике может быть просто строкой, из которой при необходимости выделяется подстрока с окладом, преобразуемая затем в число, но мы для общности и удобства дальнейшей модификации программы и демонстрации синтаксиса будем использовать класс.
В программу по условию требуется также вводить фамилии искомых сотрудников. Очередную вводимую фамилию будем также хранить в стандартной строке типа string.
Результаты. В результате работы программы требуется вывести на экран требуемые элементы исходного массива. Поскольку эти результаты представляют собой выборку из исходных данных, дополнительная память для них не отводится. Кроме того, необходимо подсчитать средний оклад для найденных сотрудников. Для этого необходима переменная вещественного типа.
Промежуточные величины. Для поиска среднего оклада необходимо подсчитать количество сотрудников, для которых выводились сведения. Заведем для этого переменную целого типа.
II. Алгоритм решения задачи очевиден:
- Ввести из файла в массив сведения о сотрудниках.
- Организовать цикл вывода сведений о сотруднике:
- ввести с клавиатуры фамилию;
- выполнить поиск сотрудника в массиве;
- увеличить суммарный оклад и счетчик количества сотрудников;
- вывести сведения о сотруднике или сообщение об их отсутствии;
- Вывести средний оклад.
Необходимо решить, каким образом будет производиться выход из цикла вывода сведений о сотрудниках. Условимся, что для выхода из цикла вместо фамилии следует просто нажать клавишу Enter. Текст программы приведен в листинге 7.3.
using System; using System.IO; namespace ConsoleApplication1 { class Man { // 1 const int l_name = 30; string name; int birth_year; double pay; public Man() // конструктор по умолчанию { name = "Anonymous"; birth_year = 0; pay = 0; } public Man(string s) // 2 { name = s.Substring(0, l_name); birth_year = Convert.ToInt32(s.Substring(l_name, 4)); pay = Convert.ToDouble(s.Substring(l_name + 4)); if (birth_year < 0) throw new FormatException(); if (pay < 0) throw new FormatException(); } public override string ToString() // 3 { return string.Format( "Name: {0,30} birth: {1} pay: {2:F2}", name, birth_year, pay); } public int Compare(string name) // сравнение фамилии { return (string.Compare(this.name, 0, name + " ", 0, name.Length + 1, StringComparison.OrdinalIgnoreCase)); } // -------------------- свойства класса -------------------------- public string Name { get { return name; } set { name = value; } } public int Birth_year { get { return birth_year; } set { if (value > 0) birth_year = value; else throw new FormatException(); } } public double Pay { get { return pay; } set { if (value > 0) pay = value; else throw new FormatException(); } } // ------------------ операции класса ------------------------------ public static double operator +(Man man1, double a) { man1.pay += a; return man1.pay; } public static double operator +(double a, Man man1) { man1.pay += a; return man1.pay; } public static double operator -(Man man1, double a) { man1.pay -= a; if ( man1.pay < 0 ) throw new FormatException(); return man1.pay; } }; class Class1 { static void Main() { Man[] dbase = new Man[100]; int n = 0; try { StreamReader f = new StreamReader("dbase.txt"); // 4 string s; int i = 0; while ((s = f.ReadLine()) != null) // 5 { dbase[i] = new Man(s); Console.WriteLine(dbase[i]); ++i; } n = i - 1; f.Close(); } catch (FileNotFoundException e) { Console.WriteLine(e.Message); Console.WriteLine(" Проверьте правильность имени файла!"); return; } catch (IndexOutOfRangeException) { Console.WriteLine("Слишком большой файл!"); return; } catch (FormatException) { Console.WriteLine("Недопустимая дата рождения или оклад"); return; } catch (Exception e) { Console.WriteLine("Ошибка: " + e.Message); return; } int n_man = 0; double mean_pay = 0; Console.WriteLine("Введите фамилию сотрудника"); string name; while ((name = Console.ReadLine()) != "") // 6 { bool not_found = true; for ( int k = 0; k <= n; ++k ) { Man man = dbase[k]; if (man.Compare(name) == 0) { Console.WriteLine(man); ++n_man; mean_pay += man.Pay; not_found = false; } } if (not_found) Console.WriteLine("Такого сотрудника нет"); Console.WriteLine( "Введите фамилию сотрудника или Enter для окончания"); } if (n_man > 0) Console.WriteLine("Средний оклад: {0:F2}", mean_pay / n_man); } } }Листинг 7.3. Поиск в массиве классов
В операторе 1 описан класс Man для хранения информации об одном сотруднике. Кроме полей данных, в ней задан конструктор, выполняющий заполнение полей (оператор 2), переопределен метод преобразования в строку (оператор 3), что позволяет выводить экземпляр объекта на экран с помощью метода WriteLine класса Console, а также описан метод Compare, в котором фамилия сотрудника сравнивается с заданной через параметр.
Сравнение фамилии выполняется с помощью одного из вариантов метода Compare класса string, который позволяет сравнивать подстроки без учета регистра. После заданной фамилии добавлен пробел, поскольку если пробела нет, то искомая фамилия является частью другой, и эта строка нам не подходит.
Свойства и операции класса позволяют выполнять ввод и корректировку оклада и года рождения.
Входной файл следует создать до первого запуска программы в соответствии с форматом, заданным в условии задачи. Можно использовать любой текстовый редактор, поддерживающий кодировку Unicode (например, Блокнот). Файл для целей тестирования должен состоять из нескольких строк, причем необходимо предусмотреть случай, когда одна фамилия является частью другой (например, Иванов и Ивановский). Не забудьте проверить, выдается ли диагностическое сообщение, если файл не найден.
В операторе 4 описан экземпляр стандартного класса символьного потока. Цикл 5 выполняет построчное считывание из файла в строку s и заполнение очередного элемента массива dbase. В операторе 6 организуется цикл просмотра массива. Просматриваются только заполненные при вводе элементы.
Деструкторы
В C# существует специальный вид метода, называемый деструктором. Он вызывается сборщиком мусора непосредственно перед удалением объекта из памяти. В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта, например, проверяется, все ли ресурсы, используемые объектом, освобождены (файлы закрыты, удаленное соединение разорвано и т. п.)
[ атрибуты ] [ extern ] ~имя_класса() тело
Как видно из определения, деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется тильдой ( ), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой, если деструктор определен как внешний ( extern ).
Сборщик мусора удаляет объекты, на которые нет ссылок. Он работает в соответствии со своей внутренней стратегией в неизвестные для программиста моменты времени. Поскольку деструктор вызывается сборщиком мусора, невозможно гарантировать, что деструктор будет обязательно вызван в процессе работы программы. Следовательно, его лучше использовать только для гарантии освобождения ресурсов, а "штатное" освобождение выполнять в другом месте программы.
Вложенные типы
В классе можно определять типы данных, внутренние по отношению к классу. Так определяются вспомогательные типы, которые используются только содержащим их классом. Механизм вложенных типов позволяет скрыть ненужные детали и более полно реализовать принцип инкапсуляции. Непосредственный доступ извне к такому классу невозможен (имеется в виду доступ по имени без уточнения). Для вложенных типов можно использовать те же спецификаторы, что и для полей класса.
Например, введем в наш класс Monster вспомогательный класс Gun. Объекты этого класса без "хозяина" бесполезны, поэтому его можно определить как внутренний:
using System; namespace ConsoleApplication1 { class Monster { class Gun { ... } ... } }
Помимо классов, вложенными могут быть и другие типы данных: интерфейсы, структуры и перечисления. Мы рассмотрим их позже.
Вопросы и задания для самостоятельной работы студента
- Что такое индексатор, для каких классов он применяется?
- Может ли статический конструктор инициализировать поля экземпляра?
- Зачем нужна перегрузка методов и операций?
- Опишите способы перегрузки операций.
- Для каких классов имеет смысл использовать перегруженные операции?
- Какой тип имеет параметр унарной операции класса?
- Можно ли перегрузить операции простого и сложного присваивания?
- Как передать программе параметры из внешнего окружения?
Лабораторная работа 8. Классы и операции
Теоретический материал: глава 7.
Каждый разрабатываемый класс должен, как правило, содержать следующие элементы: скрытые поля, конструкторы с параметрами и без параметров, методы; свойства, индексаторы; перегруженные операции. Функциональные элементы класса должны обеспечивать непротиворечивый, полный, минимальный и удобный интерфейс класса. При возникновении ошибок должны выбрасываться исключения.
Задание
Описать класс для работы с одномерным массивом целых чисел (вектором). Обеспечить следующие возможности:
- задание произвольных целых границ индексов при создании объекта;
- обращение к отдельному элементу массива с контролем выхода за пределы массива;
- выполнение операций поэлементного сложения и вычитания массивов с одинаковыми границами индексов;
- выполнение операций умножения и деления всех элементов массива на скаляр;
- вывода на экран элемента массива по заданному индексу и всего массива.
Написать программу, демонстрирующую все разработанные элементы класса.