- •Лабораторная 3 Объектно-ориентированное проектирование и программирование
- •Объектно-ориентированное проектирование Язык объектного моделирования uml. Виды диаграмм
- •Виды диаграмм
- •Диаграмма сценариев или прецедентов
- •Диаграммы классов
- •Диаграмма последовательности
- •Объектно-ориентированное программирование
- •Класс (поля, методы и свойства, области видимости, разработчики и пользователи класса)
- •Инкапсуляция, наследование и полиморфизм как свойства класса
- •Пример программирования классов
- •Пример приложения, использующего классы
Инкапсуляция, наследование и полиморфизм как свойства класса
Класс имеет три важных свойства: инкапсуляция, наследование и полиморфизм.
Инкапсуляция связана с областями видимости членов класса. Будем понимать под инкапсуляцией доступность объекта исключительно посредством его свойств и методов. Другими словами, концепция инкапсуляции призвана обеспечивать безопасность проектирования и реализации программы на основе локализации манипулирования объектом в областях его полей и методов.
Иначе говоря, полями объекта возможно оперировать исключительно посредством его методов. Практическая важность концепции инкапсуляции для языка C#) определяется следующими фундаментальными свойствами.
Прежде всего, реализация концепции инкапсуляции обеспечивает совместное хранение данных (полей) и функций (методов) внутри объекта.
Наследование (inheritance) является одним из самых важных механизмов в ООП. Любой класс может наследоваться от другого класса, а это значит, что он будет иметь все те же члены, что и класс, от которого он унаследован. В терминологии ООП класс, от которого наследуется другой класс (или, другими словами, создается производный класс), называется родительским или базовым классом. Важно обратить внимание на то, что в С# напрямую наследоваться классы могут только от одного базового класса, хотя у базового класса может быть свой собственный базовый класс и т.д.
Механизм наследования позволяет расширять или создавать специфические классы от одного более общего базового класса. Например, возьмем класс, представляющий животное с фермы. Этот класс мог бы называться Animal и обладать методами вроде EatFood () (Кормить) или Breed () (Разводить).
Можно создать производный класс по имени Cow (Корова), который бы поддерживал все те же самые методы, но при этом также имел и свои собственные, например, Моо() (Мычать) и SupplyMilk() (Давать молоко), а также еще один производный класс по имени Chicken (Курица) с методами Cluck() (Кудахтать) и LayEgg() (Снести яйцо).
При применении наследования от базового класса важным становится вопрос о доступности членов. Закрытые члены базового класса не являются доступными для производного класса, а открытые — являются. Однако открытые члены доступны как для производного класса, так и для внешнего кода. Поэтому если бы использовать можно было только два таких уровня доступности, создание члена, доступного как для базового, так и для производного класса, но не для внешнего кода, оказалось бы невозможным.
Для исключения вероятности возникновения подобной проблемы существует еще и третий уровень доступности — protected, при котором доступ к члену имеют только производные классы. Что касается внешнего кода, то для него член с уровнем доступности protected идентичен члену с уровнем private.
Помимо уровня защиты для члена еще также может определяться и поведение, связанное с наследованием. Члены базового класса могут быть виртуальными (virtual), а это означает то, что они могут переопределяться в классе, который их наследует.
Другими словами, в производном классе для членов может предоставляться альтернативная реализация. Если никакой альтернативной реализации не предоставлено, тогда любой внешний код, получающий доступ к члену через производный класс, автоматически использует реализацию этого члена, которая содержится в базовом классе.
Виртуальные члены не могут быть закрытыми, поскольку это приводит к парадоксу: нельзя говорить, что член может переопределяться в производном классе и одновременно утверждать, что он не должен быть доступен в производном классе.
В примере с классом Animal можно было бы сделать виртуальным метод EatFood () и предоставить для него новую реализацию в каком-то производном классе, например, в Cow.
Одним из результатов наследования является наличие в классах, унаследованных от базового, некоторых общих возможностей с ним (полей и методов).
Благодаря этому зачастую оказывается возможным применять для объектов, которые как экземпляры классов с общим базовым типом, имеют идентичный синтаксис. Например, при наличии у класса Animal метода по имени EatFood () синтаксис для вызова этого метода из объектов производных классов Cow и Chicken будет выглядеть одинаково:
Cow myCow = new Cow() ;
Chicken myChicken = new Chicken ();
myCow.EatFood();
myChicken.EatFood();
Полиморфизм (polymorphism) позволяет присваивать переменной базового типа переменную одного из производных типов, как показано ниже:
AnimalmyAnimal = myCow;
Никакого приведения типов выполнять не требуется. Далее можно просто вызывать методы базового класса через эту переменную:
myAnimal.EatFood();
Данная строка кода приведет к вызову реализации метода EatFood (), содержащейся в производном классе. Важно обратить внимание на то, что вызывать подобным образом методы, определенные в производном классе, нельзя. То есть следующий код работать не будет:
myAnimal.Moo ();
Однако приводить переменную типа базового класса к переменной типа производного класса и вызывать метод производного класса показанным ниже образом можно:
Cow myNewCow = (Cow)myAnimal;
myNewCow.Moo();
Полиморфизм является чрезвычайно полезной технологией для решения задач с использованием минимального количества кода в разных объектах - потомках от одного единственного класса. Следует отметить, что полиморфизм может применяться не только в отношении классов, имеющих одинаковый родительский класс. Он точно так же может применяться и, скажем, в отношении дочерних и "внучатых" классов, главное, чтобы в их иерархии наследования присутствовал какой-то общий класс.
