C#. Пример использования абстрактного класса, который содержит абстрактные свойства и методы
Пример использования абстрактного класса, который содержит абстрактные свойства и методы
В примере подробно описывается пошаговый процесс разработки абстрактного класса, который содержит абстрактные свойства и методы.
Пройдя данную инструкцию, вы научитесь:
- разрабатывать программы с использованием абстрактных классов, абстрактных методов и свойств;
- создавать иерархии классов, которые максимально используют преимущества полиморфизма.
Содержание
- Условие задачи
- Теоретические сведения
- Выполнение
- 1. Создать приложение по шаблону Console Application. Текст программы
- 2. Начальный текст программы
- 3. Соображение относительно общего размещения и построения классов
- 4. Разработка абстрактного класса Figure
- 4.1. Внутреннее поле name . Задать имя фигуры
- 4.2. Добавление конструктора класса. Инициализировать внутреннее поле класса
- 4.3. Свойство Name . Доступ к внутреннему полю класса Figures
- 4.4. Абстрактное свойство Area2 . Получить площадь фигуры
- 4.5. Абстрактный метод Area() . Получить площадь фигуры
- 4.6. Виртуальный метод Print() . Вывод значений полей класса
- 5.1. Внутренние поля класса. Стороны треугольника
- 5.2. Конструктор Triangle() . Инициализация внутренних полей базового класса Figures и производного класса Triangle
- 5.3. Метод SetABC() . Запись значений во внутренние поля класса Triangle
- 5.4. Метод GetABC() . Получить значение внутренних полей класса Triangle
- 5.5. Свойство Area2 . Переопределение абстрактного свойства базового класса Figures
- 5.6. Метод Area() . Переопределение абстрактного метода базового класса Figures
- 5.7. Функция Print() . Переопределение виртуальной функции базового класса
Поиск на других ресурсах:
Условие задачи
Разработать абстрактный класс Figure , в котором определить следующие элементы:
- скрытое внутреннее поле name (название фигуры);
- конструктор с 1 параметром, инициализирующий поле name указанным значением;
- свойство Name для доступа к внутреннему полю name;
- абстрактное свойство Area2 , предназначенное для получения площади фигуры;
- абстрактный метод Area() , предназначенный для получения площади фигуры;
- виртуальный метод Print() , который выводит название фигуры.
Разработать класс Triangle , который наследует (расширяет) возможности класса Figure . В классе реализовать следующие элементы:
- скрытые внутренние поля a , b , c (стороны треугольника);
- конструктор с 4 параметрами;
- методы доступа к полям класса SetABC() , GetABC() . Каждый метод получает 3 параметра которые есть длинами сторон треугольника;
- свойство Area2 , определяющее площадь треугольника по его сторонам a , b , c ;
- метод Area() , возвращающий площадь треугольника по его сторонам;
- виртуальный метод Print() для вывода внутренних полей класса. Метод обращается к одноименному методу базового класса.
Теоретические сведения
Абстрактный класс – это класс, в котором есть хотя бы один абстрактный элемент (метод, свойство). Если в абстрактном классе объявлен абстрактный элемент (метод, свойство) то перед именем такого класса ставится ключевое слово abstract . Абстрактный метод не содержит тела метода. Если в производном классе нужно определить конкретную реализацию элемента (метода, свойства) абстрактного класса, то при объявлении элемента указывается ключевое слово override .
Выполнение
1. Создать приложение по шаблону Console Application. Текст программы
В разных версиях Microsoft Visual Studio оконный интерфейс создания приложений отличается. Но, в общем, чтобы создать приложение, нужно вызвать команду New… из меню File . В результате откроется окно, в котором нужно задать язык программирования C# и из перечня доступных шаблонов проектов выбрать Console Application.
В результате откроется окно уточнения, в котором нужно задать название приложения (ConsoleApplication1), папку для размещения проекта и, возможно, другие настройки.
2. Начальный текст программы
После создания приложения система создаст базовый программный код, который можно модифицировать.
Как видно из вышеприведенного кода, программа на языке C# содержит объявление класса Program . В классе Program объявлена статическая функция Main() , которая служит точкой входа в программу. В этой функции позже будет добавлен программный код тестирования работы разработанных классов.
3. Соображение относительно общего размещения и построения классов
В соответствии с условием задачи, в программе нужно разработать 2 класса: Figure , Triangle . Чтобы не усложнять себе работу, классы будут размещаться в пространстве имен ConsoleApplication1 перед объявлением класса Program .
Как видно из кода выше, перед объявлением класса Figure указывается ключевое слово abstract . Это значит, что данный класс есть абстрактный, поскольку в нем будут размещены абстрактные методы и свойства (смотрите код ниже).
4. Разработка абстрактного класса Figure
4.1. Внутреннее поле name . Задать имя фигуры
Сначала в тело абстрактного класса нужно добавить скрытую ( private ) внутреннюю переменную name , которая есть названием фигуры. Название фигуры имеет тип string . После этого, текст класса Figure имеет следующий вид
4.2. Добавление конструктора класса. Инициализировать внутреннее поле класса
Конструктор класса Figure должен инициализировать внутреннее поле name значением в случае, когда создается экземпляр (объект) этого класса. Конструктор имеет видимость public. На данный момент текст класса Figure следующий
4.3. Свойство Name . Доступ к внутреннему полю класса Figures
Для доступа к внутреннему полю из экземпляра нужно создать свойство Name . Поскольку во внутреннее поле можно записывать (и читать), то свойство Name будет типа get / set .
4.4. Абстрактное свойство Area2 . Получить площадь фигуры
В соответствии с условием задачи, в классе должно быть абстрактное свойство Area2 , которое возвращает площадь фигуры. Свойство есть абстрактным, поскольку класс Figure представляет собой фигуру в общих чертах. На этом этапе неизвестно, какая это фигура: круг, прямоугольник, треугольник или другое. Поэтому, невозможно определить площадь фигуры на данный момент. Итак, свойство Area2 должно быть абстрактным. Наличие абстрактного свойства делает весь класс Figure также абстрактным.
В вышеприведенном коде, чтобы указать что свойство Area2 есть абстрактным, перед его объявлением указывается ключевое слово abstract .
Кроме того, данное свойство имеет следующие характеристики:
- свойство есть автоматическим. Об этом символизирует спецификатор get без указания фигурных скобок <> . Автоматическое свойство означает, что компилятор сам создает дополнительное поле для представления значения. В данном случае это есть лишним;
- свойство используется только для чтения ( get ), поэтому не содержит спецификатора set .
4.5. Абстрактный метод Area() . Получить площадь фигуры
В обобщенном классе Figure , метод вычисления площади фигуры Area() объявляется абстрактным по тем же соображениям, что и свойство Area2 (смотрите пункт 4.4.)
Программный код метода Area() в классе Figure имеет следующий вид
4.6. Виртуальный метод Print() . Вывод значений полей класса
Данный пример демонстрирует применение полиморфизма. В классе Figure объявляется виртуальный метод Print() , который может быть использован в унаследованных классах для вывода значений внутренних полей класса.
Текст метода Print() в классе Figure следующий
Для того, чтобы метод базового класса использовал все преимущества полиморфизма (виртуальный метод) он должен быть объявлен с ключевым словом virtual . Метод Print() объявленным как виртуальный. Это значит, что все методы производных классов, которые имеют такое же имя Print() и параметры, могут быть вызваны единым способом по ссылке на базовый класс Figure . Ниже, в функции main() это будет продемонстрировано.
Чтобы методы Print() производных классов поддерживали полиморфизм, нужно в их объявлении указывать спецификатор override .
5. Разработка класса Triangle
Класс Triangle представляет конкретную геометрическую фигуру треугольник, заданный его сторонами a , b , c . В отличие от класса Figure , класс Triangle не является абстрактным – это есть специализация обобщенного класса Figure .
5.1. Внутренние поля класса. Стороны треугольника
Класс Triangle представлен длинами сторон треугольника a , b , c . Класс есть производным от абстрактного класса Figure .
Внутренние поля класса Triangle с соответствующими именами есть типа double . Первое, что надо сделать, это объявить внутренние поля класса, как показано ниже.
5.2. Конструктор Triangle() . Инициализация внутренних полей базового класса Figures и производного класса Triangle
Конструктор класса Triangle вызывается во время создания экземпляра этого класса. Данный конструктор вызывает конструктор базового класса Figure с помощью использования ключевого слова base . В данном случае это есть обязательно в соответствии с требованиями компилятора: первым выполняется конструктор базового класса, затом выполняется конструктор производного класса.
Конструктор класса Triangle() имеет 4 параметра:
- параметр название фигуры name . Это поле базового класса Figure ;
- три параметра a , b , c которые являются длинами сторон треугольника. В теле конструктора делается проверка на корректность указания сторон a , b , c .
Текст конструктора в классе имеет следующий вид
5.3. Метод SetABC() . Запись значений во внутренние поля класса Triangle
Метод SetABC() предназначен для записи значений в поля экземпляра класса. Метод есть общедоступным ( public ) и не содержит дополнительных спецификаторов.
Текст метода SetABC() в теле класса имеет следующий вид
5.4. Метод GetABC() . Получить значение внутренних полей класса Triangle
Чтобы получить значение полей класса, используется метод GetABC() . Значения полей получаются с помощью параметров метода, которые реализованы как out-параметры. Следует напомнить, out-параметр метода позволяет изменять значения аргумента в вызывающем коде. out-параметру внутри тела метода обязательно нужно присваивать значения, иначе компилятор выдаст сообщение об ошибке.
Текст метода GetABC() в классе Triangle следующий
5.5. Свойство Area2 . Переопределение абстрактного свойства базового класса Figures
В соответствии с условием задачи, в классе Triangle нужно реализовать свойство Area2 , которое будет вычислять площадь треугольника. Это свойство переопределяет одноименное свойство базового класса Figure . Поэтому, в объявлении свойства указывается ключевое слово override .
Само по себе свойство Area2 в классе Triangle уже не может быть абстрактным. Это связанно с тем, что базируясь на значениях полей a , b , c можно без проблем вычислить площадь треугольника (по формуле Герона). В одноименном свойстве базового класса Figure этого делать нельзя, поскольку неизвестно, для какой фигуры вычислять площадь (треугольник, окружность, прямоугольник), поскольку класс Figure есть базовым для всех фигур, которые будут унаследованы от него.
Свойство Area2 предназначено для получения площади треугольника по значениям внутренних полей a , b , c класса Triangle . Поэтому, свойство реализовано с аксессором get и предназначено только для чтения. В данном свойстве нет смысла использовать аксессор set .
Программный код свойства Area2 в классе Triangle следующий
5.6. Метод Area() . Переопределение абстрактного метода базового класса Figures
Метод Area() класса Triangle переопределяет ( override ) одноименный абстрактный ( abstract ) метод базового класса Figure . Поэтому, при объявлении метода Area() указывается спецификатор override . Пара этих методов обеспечивает так называемый полиморфизм. Более подробно о преимуществах и особенностях применения полиморфизма описывается здесь.
При объявлении метода (или свойства) в абстрактном классе нет смысла одновременно указывать два слова abstract и virtual для того, чтобы обеспечить полиморфизм. Если класс абстрактный, то достаточно только одного слова abstract . Если класс не абстрактный, то указывается virtual .
Текст метода Area() в классе Triangle
5.7. Функция Print() . Переопределение виртуальной функции базового класса
В соответствии с условием задачи, в классе Triangle должна быть использована функция Print() , которая предназначена для вывода значений внутренних полей класса. Эта функция переопределяет ( override ) одноименную виртуальную ( virtual ) функцию базового класса Figures , поэтому при объявлении функции указывается ключевое слово override .
В теле метода Print() происходит вызов одноименного метода базового класса с помощью строки
Текст метода Print() в классе Triangle следующий:
6. Реализация функции Main() . Тестирование работы классов
В функции Main() класса Program тестируется работа классов Figures и Triangle .
7. Текст программы. Результаты
Ниже приведен текст всей программы, которая решает данную задачу.
Результат работы программы
8. Задача для проверки знаний
Нужно доделать предыдущую программу, добавив к ней еще один класс.
Разработать класс TriangleColor , который наследует (расширяет) возможности класса Triangle . В классе реализовать следующие элементы:
- скрытое внутреннее поле color (цвет фона треугольника);
- конструктор с 5 параметрами, который вызовет конструктор базового класса;
- свойство Color , предназначенное для доступа к внутреннему полю color;
- свойство Area2 , которое вызывает одноименное свойство базового класса для вычисления площади треугольника;
- метод Area() , возвращающий площадь треугольника по его сторонам;
- виртуальный метод Print() для вывода внутренних полей класса. Метод обращается к одноименному методу базового класса.
Полный текст программы с текстом класса TriangleColor можно просмотреть здесь .
как найти площадь абстрактной фигуры?
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
1. Дано действительное число – цена 1 кг конфет. Вывести стоимость 0,5; 1; 1,5 … 10 кг конфет. (Подсказка – используем цикл WHILE).
Программа должна иметь следующий вид:
Компьютер запрашивает стоимость одного килограмма конфет.
Пользователь вводит стоимость одного килограмма конфет, и компьютер выводит на экран:
Задача с геометрическими фигурами (ООП)
Будучи джуниор разработчиком на php я проходил множество собеседований. За это время я решил (и не решил) множество задач. Однако среди них есть одна, решая которую я был уверен, что делаю все правильно, но с опытом я понял, что это совсем не так. Конечно, не совсем корректно говорить «правильно» и «неправильно», скажем так — «не соответствует best practices». Сегодня я бы хотел поговорить об этой задаче, поделиться своими мыслями о ее решении и почему SOLID сложнее чем кажется на первый взгляд.
Задача
У вас есть 4 фигуры квадрат, прямоугольник, круг и треугольник. Необходимо создать классы для каждой фигуры.
Звучит очень абстрактно и просто. Штош, давайте попробуем решить эту задачу.
Решение
Первый раз на собеседовании я думал, что все очевидно и предложил такое решение:
Хоть многие из вас уже увидели нарушение SOLID, давайте по порядку:
Неправильное использование абстрактного класса.
Когда я писал этот код на собеседовании, я думал «Ну возможно у них есть общая логика, поэтому нужно использовать абстрактный класс». Такой подход неверен и даже опасен. Не просто так у нас есть принцип YAGNI («You Ain’t Gonna Need It» или в переводе на русский — «Вам это не понадобится»). Разберемся подробнее. В данном случае, мы создаем абстрактный класс, который можно заменить интерфейсом.Да, абстрактный класс может выполнять роль интерфейса, но у него есть существенный недостаток — наследование. Само по себе наследование отличный инструмент, однако сильная (при этом явно не нужная в данном примере) зависимость классов может усложнить ваш код и повысить сложность его расширения.
Наследование квадрата от прямоугольника.
Такое наследование нарушает одно из правил SOLID — принцип подстановки Барбары Лисков и является классическим примером нарушения этого принципа. Проблема заключается в том, что высоту и ширину прямоугольника можно изменять независимо, а высоту и ширину квадрата можно изменять только вместе. Т.е. мы не сможем подставить класс потомок вместо класса родителя.Отлично! С этим мы разобрались, ну теперь то мы точно напишем правильно, не так ли?
Второе решение
Что же здесь не правильно на этот раз? Давайте разберемся:
Нарушение принципа SOLID — принципа разделения интерфейсов.
Для всех фигур мы можем найти площадь и периметр, следовательно у фигуры должны быть методы для их получения — звучит логично. Но это будет логично до тех пор пока вы не столкнетесь, например, с тем, что вам может не понадобиться искать периметр одной из фигур. В таком случае вам придется реализовывать метод, который вам не нужен, покрывать его тестами и поддерживать. Некоторые принимают ошибочное решение — выбрасывать исключение при вызове этого метода. Ошибочное, потому что в таком случае мы нарушим принцип подстановки Барбары Лисков (не сможем потомком подменить родителя).
Давайте напишем этот код еще раз, но теперь следуя принципу разделения интерфейсов
Третье решение
Классы наших фигур реализуют данные интерфейсы при необходимости. Если нам нужна реализация обоих методов в классе фигуры, то мы используем оба интерфейса, если только один — то один интерфейс.
Вывод
Третье решение — то, к чему я пришел. Оно мне кажется самым лучшим на данный момент, но я не исключаю вероятность того, что я изменю свое мнение с набором новых знаний и опыта. Это моя первая статья на habr и я надеюсь, что она будет полезна начинающим программистам.
Как найти площадь абстрактной фигуры
x = φ ( t ) = 2 cos t y = ψ ( t ) = 3 sin t φ α = a ⇔ 2 cos α = 0 ⇔ α = π 2 + πk , k ∈ Z , φ β = b ⇔ 2 cos β = 2 ⇔ β = 2 πk , k ∈ Z
— ∫ 0 π 2 3 sin t · 2 cos t ‘ d t = 6 ∫ 0 π 2 sin 2 t d t = 3 ∫ 0 π 2 ( 1 — cos ( 2 t ) d t = = 3 · t — sin ( 2 t ) 2 0 π 2 = 3 · π 2 — sin 2 · π 2 2 — 0 — sin 2 · 0 2 = 3 π 2
— ∫ 0 π 3 sin t · 2 cos t ‘ d t = 6 ∫ 0 π sin 2 t d t = 3 ∫ 0 π ( 1 — cos ( 2 t ) d t = = 3 · t — sin ( 2 t ) 2 0 π = 3 · π — sin 2 · π 2 — 0 — sin 2 · 0 2 = 3 π
У нас x = φ ( t ) = 3 cos 3 t , y = ψ ( t ) = 2 sin 3 t .
t 0 0 π 8 π 4 3 π 8 π 2 5 π 8 3 π 4 7 π 8 π x 0 = φ ( t 0 ) 3 2 . 36 1 . 06 0 . 16 0 — 0 . 16 — 1 . 06 — 2 . 36 — 3 y 0 = ψ ( t 0 ) 0 0 . 11 0 . 70 1 . 57 2 1 . 57 0 . 70 0 . 11 0 t 0 9 π 8 5 π 4 11 π 8 3 π 2 13 π 8 7 π 4 15 π 8 2 π x 0 = φ ( t 0 ) — 2 . 36 — 1 . 06 — 0 . 16 0 0 . 16 1 . 06 2 . 36 3 y 0 = ψ ( t 0 ) — 0 . 11 — 0 . 70 — 1 . 57 — 2 — 1 . 57 — 0 . 70 — 0 . 11 0 φ ( α ) = a ⇔ 3 cos 3 t = 0 ⇔ α = π 2 + πk , k ∈ Z , φ ( β ) = b ⇔ 3 cos 3 t = 3 ⇔ β = 2 πk , k ∈ Z
— ∫ 0 π 2 2 sin 3 t · 3 cos 3 t ‘ d t = 18 ∫ 0 π 2 sin 4 t · cos 2 t d t = = 18 ∫ 0 π 2 sin 4 t · ( 1 — sin 2 t ) d t = 18 ∫ 0 π 2 sin 4 t d t — ∫ 0 π 2 sin 6 t d t
∫ sin 4 t d t = — cos t · sin 3 t 4 + 3 4 ∫ sin 2 t d t = = — cos t · sin 3 t 4 + 3 4 — cos t · sin t 2 + 1 2 ∫ sin 0 t d t = = — cos t · sin 3 t 4 — 3 cos t · sin t 8 + 3 8 t + C ⇒ ∫ 0 π 2 sin 4 t d t = — cos t · sin 3 t 4 — 3 cos t · sin t 8 + 3 8 t 0 π 2 = 3 π 16 ∫ sin 6 t d t = — cos t · sin 5 t 6 + 5 6 ∫ sin 4 t d t ⇒ ∫ 0 π 2 sin 6 t d t = — cos t · sin 5 t 6 0 π 2 + 5 6 ∫ 0 π 2 sin 4 t d t = 5 6 · 3 π 16 = 15 π 96