Концепция инкапсуляции и ее реализация в языке C#
Формализуем понятие инкапсуляции в рамках объектно-ориентированного подхода к программированию.
В неформальной постановке вопроса под инкапсуляцией будем понимать доступность объекта исключительно посредством его свойств и методов.
Другими словами, концепция инкапсуляции призвана обеспечивать безопасность проектирования и реализации программного обеспечения на основе локализации манипулирования объектом в областях его полей и методов.
Иначе говоря, свойствами объекта можно оперировать исключительно посредством его методов. Это замечание касается как свойств, явно определенных в описании объекта, так и свойств, унаследованных данным объектом от другого (других).
Практическая важность концепции инкапсуляции для современных языков объектно-ориентированного программирования (в том числе и для языка C#) определяется следующими фундаментальными свойствами.
Прежде всего, реализация концепции инкапсуляции обеспечивает совместное хранение данных (или, иначе, полей) и функций (или, иначе, методов) внутри объекта.
Как следствие, механизм инкапсуляции приводит к сокрытию информации о внутреннем "устройстве" объекта данных (или, в терминах языков ООП, свойств и методов объекта) от пользователя того или иного объектно-ориентированного приложения.
Таким образом, пользователь, получающий программное обеспечение как сервис, оказывается изолированным от особенностей среды реализации.
Рассмотрев определение понятия инкапсуляции (пока на интуитивном уровне), перейдем к описанию формализаций этой фундаментальной для объектно-ориентированного программирования концепции на основе уже известных нам формальных теорий computer science.
Оказывается, что понятие инкапсуляции вполне адекватно формализуется посредством таких теоретических формальных систем, как ламбда-исчисление А.Черча и комбинаторная логика Х.Карри.
При этом при интерпретации концепции инкапсуляции в терминах формальной системы ламбда-исчисления, в роли объектов выступают ламбда-термы, в роли свойств - связанные переменные, а в роли методов - свободные переменные.
В случае же интерпретации концепции инкапсуляции в терминах формальной системы комбинаторной логики в роли объектов выступают комбинаторы, в роли свойств - переменные, а в роли методов - комбинаторы.
Поясним более подробно реализацию механизма сокрытия информации посредством концепции инкапсуляции.
Рассмотрим в достаточно обобщенном виде схему взаимодействия объекта и данных.
Вначале представим схему такого рода для традиционного императивного языка программирования (например C и Pascal). При этом в нашем рассуждении под термином "объект" будем понимать произвольный объект языка программирования, безотносительно к концепции объектно-ориентированного программирования.
В этом случае объявления данных и процедуры обработки данных отделены друг от друга. Зачастую в ранних языках программирования описания языковых объектов и процедуры манипулирования ими выделены в особые разделы.
В этой связи принципиальным недостатком ранних императивных языков программирования является то обстоятельство, что доступ к данным может быть получен методами, которые изначально не предназначались разработчиками приложений для манипулирования этими данными. Вообще говоря, управление объектом может осуществляться посредством произвольной процедуры или функции, и у среды проектирования и реализации программного обеспечения нет возможности централизованного управления этим процессом. Такой подход к программированию, безусловно, является довольно непоследовательным и весьма небезопасным.
В отличие от предыдущей схемы, при объектно-ориентированном подходе к проектированию и реализации программного обеспечения взаимодействие объектов языка программирования и конкретизирующих его данных реализуется принципиально иным образом. По существу, объект и данные составляют единое и неделимое целое.
Прежде всего, определение (или описание свойств классов) и процедуры манипулирования этими свойствами (или методы) для каждого объекта языка программирования при объектно-ориентированном подходе хранятся совместно.
Кроме того, среда проектирования и реализации программного обеспечения (например, Microsoft Visual Studio .NET) не предоставляет иных возможностей доступа к объекту, кроме как посредством методов, изначально предназначенных для манипулирования данным объектом.
Таким образом, концепция инкапсуляции в языках объектно-ориентированного программирования служит целям обеспечения единообразия определения, хранения и работы с информацией о языковых объектах.
Заметим, что инкапсуляция является безусловно необходимым требованием для каждого объекта.
В то же время в практике программирования степень инкапсуляции объекта (в широком значении этого слова) определяется его описанием, а также описанием порождающих его объектов (в действительности это, как правило, описания базовых и производных классов).
В этой связи в языках объектно-ориентированного программирования вводится понятие области видимости как степени доступности произвольного языкового объекта.
Применительно к языку программирования C# области видимости объектов подразделяются на следующие виды.
Общедоступные объекты описываются с помощью зарезервированного слова public и характеризуются доступностью из произвольного места программы, для которого определено пространство имен с описанием рассматриваемого объекта. При этом элементы интерфейсов и перечислений являются общедоступными (public) объектами по умолчанию.
С другой стороны, типы, описанные в составе пространств имен в форме классов, структур, интерфейсов, перечислений или делегатов являются по умолчанию видимыми из сборки с описанием объекта и описываются с помощью зарезервированного слова internal.
Наконец, элементы классов и структур, в частности, поля, методы, свойства и вложенные типы являются по умолчанию доступными из описаний соответствующих классов или структур и описываются с помощью зарезервированного слова private.
Проиллюстрируем обсуждение использования модификаторов областей видимости объектов (public и private) следующим содержательным примером фрагмента программы на языке C#:
public class Stack{ private int[] val; // private используется // и по умолчанию private int top; // private используется // и по умолчанию public Stack(){ ... } public void Push(int x){ ... } public int Pop(){ ... } }
Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса стека Stack, реализованного на основе массива целочисленных элементов (поле val). Голова стека представляет собой целочисленное значение (поле top). Над стеком определены операции инициализации (метод Stack), а также вставки (метод Push) и удаления (метод Pop) элемента.
Как явствует из примера, класс Stack и все манипулирующие его объектами методы (Stack, Push и Pop) являются общедоступными (public), тогда как поля являются доступными локально (private), т.е. только из описания данного класса.
Заметим, что в приведенном выше примере фрагмента программы с описанием областей видимости использовались только базовые возможности модификаторов видимости языка программирования C#.
Оказывается, что язык программирования C# располагает механизмами реализации дополнительных (или расширенных) по сравнению с базовыми областей видимости объектов.
Рассмотрим более подробно особенности основных типов расширенных областей видимости объектов в языке программирования C#.
К числу расширенных областей видимости следует отнести доступность языковых объектов как непосредственно из класса с описанием объекта, так и из его производных классов. В данном случае для описания языкового объекта используется зарезервированное слово protected.
Кроме того, для описания доступности языкового объекта лишь из сборки с его описанием используется зарезервированное слово internal.
Наконец, для описания доступности языкового объекта непосредственно из класса с описанием данного объекта, его производных классов, а также из сборки с описанием данного объекта используется зарезервированное слово protected internal.
Проиллюстрируем обсуждение использования модификаторов расширенных областей видимости объектов (protected и internal) следующим содержательным примером фрагмента программы на языке C#:
class Stack { protected int[] values = new int[32]; protected int top = -1; public void Push(int x) { ... } public int Pop() { ... } } class BetterStack : Stack { public bool Contains(int x) { foreach (int y in values) if(x==y) return true; return false; } } class Client { Stack s = new Stack(); ... s.values[0]; ... // ошибка при компиляции! }
Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса стека Stack (для хранения 32 целочисленных элементов и значением -1 в вершине), а также реализованного на его основе усовершенствованного класса стека BetterStack, дополнительно реализующего повторяющиеся элементы стека. В отличие от предыдущего примера, все поля класса стека Stack доступны как из данного класса, так и из классов, производных от него, поскольку описаны посредством ключевого слова protected.
После обсуждения использования (степеней) инкапсуляции применительно к классам в целом, рассмотрим особенности приложения данной концепции к таким фундаментальным объектам языка программирования C# как поля и константы.
Инициализация не является безусловно необходимой для полей в языке программирования C#. Тем не менее, доступ к полям и методам изначально запрещен (что соответствует использованию по умолчанию модификатора доступа private). В случае структуры поля инициализации не подлежат.
Простейшей иллюстрацией описания поля в языке программирования C# является следующий пример, содержащий определение класса C с целочисленным полем value:
class C { int value = 0; }
В случае константы инициализация также не является необходимым требованием. Тем не менее, значение константы должно быть вычислимым в процессе компиляции.
Простейшей иллюстрацией описания константы в языке программирования C# является следующий пример, содержащий определение константы size, представляющей собой целочисленное значение двойной длины (long):
const long size = ((long)int.MaxValue+1)/4;
Заметим, что фрагмент
...(long) ...
в правой части присваивания представляет собой явное преобразование типов языковых объектов.
Содержание раздела