Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
// Это работает нормально.
TimeUtilClass.PrintDate();
TimeUtilClass.PrintTime();
// Ошибка на этапе компиляции!
// Создавать экземпляры статического класса невозможно!
TimeUtilClass u = new TimeUtilClass ();
Console.ReadLine();
Импортирование статических членов с применением ключевого слова using языка C#
В версии C# 6 появилась поддержка импортирования статических членов с помощью ключевого слова using. В качестве примера предположим, что в файле C# определен обслуживающий класс. Поскольку в нем делаются вызовы метода WriteLine() класса Console, а также обращения к свойствам Now и Today класса DateTime, должен быть предусмотрен оператор using для пространства имен System. Из-за того, что все члены упомянутых классов являются статическими, в файле кода можно указать следующие директивы using static:
// Импортировать статические члены классов Console и DateTime.
using static System.Console;
using static System.DateTime;
После такого "статического импортирования" в файле кода появляется возможность напрямую применять статические методы классов Console и DateTime, не снабжая их префиксом в виде имени класса, в котором они определены. Например, модифицируем наш обслуживающий класс TimeUtilClass, как показано ниже:
static class TimeUtilClass
{
public static void PrintTime()
=> WriteLine(Now.ToShortTimeString());
public static void PrintDate()
=> WriteLine(Today.ToShortDateString());
}
В более реалистичном примере упрощения кода за счет импортирования статических членов мог бы участвовать класс С#, интенсивно использующий класс System.Math (или какой-то другой обслуживающий класс). Поскольку этот класс содержит только статические члены, отчасти было бы проще указать для него оператор using static и затем напрямую обращаться членам класса Math в своем файле кода.
Однако имейте в виду, что злоупотребление операторами статического импортирования может привести в результате к путанице. Во-первых, как быть, если метод WriteLine() определен сразу в нескольких классах? Будет сбит с толку как компилятор, так и другие программисты, читающие ваш код. Во-вторых, если разработчик не особенно хорошо знаком с библиотеками кода .NET Core, то он может не знать о том, что WriteLine() является членом класса Console. До тех пор, пока разработчик не заметит набор операторов статического импортирования в начале файла кода С#, он не может быть полностью уверен в том, где данный метод фактически определен. По указанным причинам применение операторов using static в книге ограничено.
К настоящему моменту вы должны уметь определять простые типы классов, содержащие конструкторы, поля и разнообразные статические (и нестатические) члены. Обладая такими базовыми знаниями о конструкции классов, можно приступать к ознакомлению с тремя основными принципами объектно-ориентированного программирования (ООП).
Основные принципы объектно-ориентированного программирования
Все объектно-ориентированные языки (С#, Java, C++, Visual Basic и т.д.) должны поддерживать три основных принципа ООП.
• Инкапсуляция. Каким образом язык скрывает детали внутренней реализации объектов и предохраняет целостность данных?
• Наследование. Каким образом язык стимулирует многократное использование кода?
• Полиморфизм. Каким образом язык позволяет трактовать связанные объекты в сходной манере?
Прежде чем погрузиться в синтаксические детали каждого принципа, важно понять их базовые роли. Ниже предлагается обзор всех принципов, а в оставшейся части этой и в следующей главе приведены подробные сведения, связанные с ними.
Роль инкапсуляции
Первый основной принцип ООП называется инкапсуляцией. Такая характерная черта описывает способность языка скрывать излишние детали реализации от пользователя объекта. Например, предположим, что вы имеете дело с классом по имени DatabaseReader, в котором определены два главных метода: Open() и Close().
// Пусть этот класс инкапсулирует детали открытия и закрытия базы данных.
DatabaseReader dbReader = new DatabaseReader();
dbReader.Open(@"C:AutoLot.mdf");
// Сделать что-то с файлом данных и закрыть файл.
dbReader.Close();
Вымышленный класс DatabaseReader инкапсулирует внутренние детали нахождения, загрузки, манипулирования и закрытия файла данных. Программистам нравится инкапсуляция, т.к. этот основной принцип ООП упрощает задачи кодирования. Отсутствует необходимость беспокоиться о многочисленных строках кода, которые работают "за кулисами", чтобы обеспечить функционирование класса DatabaseReader. Все, что понадобится — создать экземпляр и отправить ему подходящие сообщения (например, открыть файл по имени AutoLot.mdf, расположенный на диске С:).
С понятием инкапсуляции программной логики тесно связана идея защиты данных. В идеале данные состояния объекта должны быть определены с применением одного из ключевых слов private, internal или protected. В итоге внешний мир должен вежливо попросить об изменении либо извлечении лежащего в основе значения, что крайне важно, т.к. открыто объявленные элементы данных легко могут стать поврежденными (конечно, лучше случайно, чем намеренно). Вскоре будет дано формальное определение такого аспекта инкапсуляции.
Роль наследования
Следующий принцип ООП — наследование — отражает возможность языка разрешать построение определений новых классов на основе определений существующих классов. По сути, наследование позволяет расширять поведение базового (или родительского) класса за счет наследования его основной функциональности производным подклассом (также называемым дочерним классом). На рис. 5.2 показан простой пример.
Диаграмма на рис. 5.2 читается так: "шестиугольник (Hexagon) является фигурой (Shape), которая является объектом (Object)". При наличии классов, связанных такой формой наследования, между типами устанавливается отношение "является" ("is-a"). Отношение "является" называется наследованием.
Здесь можно предположить, что класс Shape определяет некоторое количество членов, являющихся общими для всех наследников (скажем, значение для представления цвета фигуры, а также значения для высоты и ширины). Учитывая, что класс Hexagon расширяет Shape, он наследует основную функциональность, определяемую классами Shape и Object, и вдобавок сам определяет дополнительные детали, связанные с шестиугольником (какими бы они ни были).
На заметку! В рамках платформ .NET/.NET Core класс System.Object всегда находится на вершине любой иерархии классов, являясь первоначальным родительским классом, и определяет общую функциональность для всех типов (как подробно объясняется в главе 6).
В мире ООП существует еще одна форма повторного использования кода: модель включения/делегации, также известная как отношение "имеет" ("has-a") или агрегация. Такая форма повторного использования не применяется для установки отношений "родительский-дочерний". На самом деле отношение "имеет" позволяет одному классу определять переменную-член другого класса и опосредованно (когда требуется) открывать доступ к его функциональности пользователю объекта.
Например, предположим, что снова моделируется автомобиль. Может возникнуть необходимость выразить идею, что автомобиль