C#. Приклад використання абстрактного класу, що містить абстрактні властивості та методи

Приклад використання абстрактного класу, що містить абстрактні властивості та методи

У прикладі детально розписується покроковий процес побудови абстрактного класу, який містить абстрактні властивості та методи.

Пройшовши дану інструкцію, ви навчитесь:

  • правильно розробляти програми з використанням абстрактних класів, абстрактних методів та властивостей;
  • правильно будувати ієрархії класів, що максимально використовують переваги поліморфізму.

Зміст


Умова задачі

Розробити абстрактний клас 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. Початковий текст програми

Після створення додатку система створить базовий програмний код, який можна модифікувати.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

Як видно з вищенаведеного коду, програма на мові C# містить оголошення класу Program. У класі Program оголошена статична функція Main(), яка служить точкою входу в програму. У цій функції пізніше буде додано програмний код тестування роботи розроблених класів.

 

3. Міркування щодо загального розміщення та побудови класів

Згідно з умовою задачі, у програмі потрібно розробити 2 класи: Figure, Triangle. Щоб не ускладнювати собі роботу, класи будуть розміщуватись в просторі імен ConsoleApplication1 перед оголошенням класу Program.

// Загальне розміщення класів
namespace ConsoleApplication1
{
  abstract class Figure
  {
    ...
  }

  class Triangle : Figure
  {
    ...
  }

  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

Як видно з вищенаведеного коду, перед оголошенням класу Figure вказується ключове слово abstract. Це означає, що даний клас є абстрактний, оскільки в ньому будуть розміщені абстрактні методи та властивості (дивіться код нижче).

 

4. Розробка абстрактного класу Figure
4.1. Внутрішнє поле name. Задати ім’я фігури

Спочатку в тіло абстрактного класу потрібно додати приховану (private) внутрішню змінну name, що є назвою фігури. Назва фігури є тип string. Після цього, текст класу Figure має наступний вигляд

...

abstract class Figure
{
  // 1. Приховане поле класу
  private string name; // Назва фігури
}

...

 

4.2. Додавання конструктора класу. Ініціалізувати внутрішнє поле класу

Конструктор класу Figure повинен ініціалізувати внутрішнє поле name значенням у випадку, коли створюється екземпляр (об’єкт) цього класу. Конструктор має видимість public. На даний момент текст класу Figure наступний

...

abstract class Figure
{
  // 1. Приховане поле класу
  private string name; // Назва фігури

  // 2. Конструктор класу
  public Figure(string name)
  {
    this.name = name;
  }
}

...

 

4.3. Властивість Name. Доступ до внутрішнього поля класу Figures

Для доступу до внутрішнього поля з екземпляру потрібно створити властивість Name. Оскільки у внутрішнє поле можна записувати (і читати також), то властивість Name буде типу get/set.

abstract class Figure
{
  ...

  // 3. Властивість доступу до поля класу
  public string Name
  {
    get { return name; }
    set { name = value; }
  }
}

 

4.4. Абстрактна властивість Area2. Отримати площу фігури

Згідно з умовою задачі, у класі повинна бути абстрактна властивість Area2, яка повертає площу фігури. Властивість є абстрактною, оскільки клас Figure представляє собою фігуру в загальних рисах. На цьому етапі невідомо, яка це фігура: коло, прямокутник, трикутник чи інше. Тому, неможливо визначити площу фігури на даний момент. Отже, властивість Area2 має бути абстрактною. Наявність абстрактної властивості робить весь клас Figure також абстрактним.

...

abstract class Figure
{
  ...

  // 4. Абстрактна властивість, яка повертає площу фігури
  public abstract double Area2 { get; }
}

У вищенаведеному коді, щоб вказати що властивість Area2 є абстрактною, перед її оголошенням вказується ключове слово abstract.

Крім того, дана властивість має такі характеристики:

  • властивість є автоматичною. Про це символізує специфікатор get без вказання фігурних дужок { }. Автоматична властивість означає, що компілятор сам створює додаткове поле для представленян значення. У даному випадку це є зайвим;
  • властивість використовується тільки для читання (get), тому не містить специфікатора set.

 

4.5. Абстрактний метод Area(). Отримати площу фігури

В узагальненому класі Figure, метод обчислення площі фігури Area() оголошується абстрактним з таких самих причин, як і властивість Area2 (дивіться пункт 4.4.)

Програмний код методу Area() у класі Figure має такий вигляд

...

abstract class Figure
{
  ...

  // 5. Абстрактний метод, який повертає площу фігури
  //   Метод не має тіла методу
  public abstract double Area();
}

...

 

4.6. Віртуальний метод Print(). Виведення значень полів класу

Даний приклад демонструє застосування поліморфізму. У класі Figure оголошується віртуальний метод Print(), який може бути використаний в успадкованих класах для виведення значень внутрішніх полів класу.

Текст методу Print() у класі Figure наступний

...

abstract class Figure
{
  ...

  // 6. Віртуальний метод, який виводить значення полів класу
  public virtual void Print()
  {
    Console.WriteLine("name = {0}", name);
  }
}

...

Для того, щоб метод базового класу використовував усі переваги поліморфізму (віртуальний метод) він повинен бути оголошений з ключовим словом 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. Перше, що треба зробити, це оголосити внутрішні поля класу як показано нижче.

 

...

// Клас, що реалізує трикутник. У класі немає абстрактних методів,
// тому слово abstract не ставиться перед оголошенням класу.
class Triangle : Figure
{
  // 1. Внутрішні поля класу
  double a, b, c;
}

...

 

5.2. Конструктор Triangle(). Ініціалізація внутрішніх полів базового класу Figures та похідного класу Triangle

Конструктор класу Triangle викликається під час створення екземпляру цього класу. Даний конструктор викликає конструктор базового класу Figure з допомогою використанян ключового слова base. У даному випадку це є обов’язково згідно з вимогами компілятора: першим виконується конструктор базового класу, потім виконується конструктор похідного класу.

Конструктор класу Triangle() має 4 параметри:

  • параметр назва фігури name. Це поле базового класу Figure;
  • три параметри a, b, c що є довжинами сторін трикутника. У тілі конструктора робиться перевірка на коректність задавання сторін a, b, c.

Текст конструктора в класі має такий вигляд

...

class Triangle : Figure
{
  ...

  // 2. Конструктор класу
  public Triangle(string name, double a, double b, double c)
                : base(name)
  {
    // Перевірка на коректність значень a, b, c
    if (((a + b) > c) && ((b + c) > a) && ((a + c) > b))
    {
      this.a = a; this.b = b; this.c = c;
    }
    else
    {
      Console.WriteLine("Incorrect values a, b, c.");
      Console.WriteLine("By default: a=1, b=1, c=1.");
      this.a = this.b = this.c = 1;
    }
  }

...

 

5.3. Метод SetABC(). Запис значень у внутрішні поля класу Triangle

Метод SetABC() призначений для запису значень в поля екземпляру класу. Метод є загальнодоступний (public) і не містить додаткових специфікаторів.

Текст методу SetABC() в тілі класу має такий вигляд

...

class Triangle : Figure
{
  ...

  // 3. Реалізація методів доступу до прихованих полів a, b, c
  // 3.1. Встановлення значень полів a, b, c
  public void SetABC(double a, double b, double c)
  {
    if (((a + b) > c) && ((b + c) > a) && ((a + c) > b))
    {
      this.a = a; this.b = b; this.c = c;
    }
    else
    {
      this.a = this.b = this.c = 1;
    }
  }
}

...

 

5.4. Метод GetABC(). Отримати значення внутрішніх полів класу Triangle

Щоб отримати значення полів класу, використовується метод GetABC(). Значення полів отримуються з допомогою параметрів методу, які реалізовані як out-параметри. Варто нагадати, out-параметр методу дозволяє змінювати значення аргументу у викликаючому коді. out-параметру всередині тіла методу обов’язково потрібно присвоювати значення, інакше компілятор видасть повідомлення про помилку.

Текст методу GetABC() в класі Triangle наступний

...

class Triangle : Figure
{
  ...

  // 3.2. Читання значень полів - звернути увагу на модифікатор out
  public void GetABC(out double a, out double b, out double c)
  {
    // параметру a присвоїти значення внутрішнього поля a (this.a)
    a = this.a;

    // аналогічно записати значення в інші параметри
    b = this.b;
    c = this.c;
  }
}

...

 

5.5. Властивість Area2. Перевизначення абстрактної властивості базового класу Figures

Згідно з умовою задачі, у класі Triangle потрібно реалізувати властивість Area2, яка буде обчислювати площу трикутника. Ця властивість перевизначає однойменну властивість базового класу Figure. Тому, в оголошенні властивості вказується ключове слово override.

Сама по собі властивість Area2 у класі Triangle вже не може бути абстрактною. Це пов’язане з тим, що базуючись на значеннях полів a, b, c можна без проблем обчислити площу трикутника (за формулою Герона). В однойменній властивості базового класу Figure цього зробити не можна, оскільки невідомо, для якої фігури обчислювати площу (трикутник, коло, прямокутник), оскільки клас Figure є базовим для усіх фігур, що будуть успадковані з нього.

Властивість Area2 призначена для отримання площі трикутника на основі значень внутрішніх полів a, b, c класу Triangle. Тому, властивість реалізована з аксесором get і призначена тільки для читання. У даній властивості немає сенсу використовувати аксесор set.

Програмний код властивості Area2 у класі Triangle наступний

...

class Triangle : Figure
{
  ...

  // 4. Перевизначення абстрактної властивості Area2 класу Figure,
  //   ключове слово override обов'язкове
  public override double Area2
  {
    get
    {
      // 1. Провести обчислення
      double p, s;
      p = (a + b + c) / 2;
      s = Math.Sqrt(p * (p - a) * (p - b) * (p - c));

      // 2. Вивести результат у властивості - для контролю
      Console.WriteLine("Property Triangle.Area2: s = {0:f3}", s);

      // 3. Повернути результат
      return s;
    }
  }
}

 

5.6. Метод Area(). Перевизначення абстрактного методу базового класу Figures

Метод Area() класу Triangle перевизначає (override) однойменний абстрактний (abstract) метод базового класу Figure. Тому, при оголошенні методу Area() вказується специфікатор override. Пара цих методів забезпечує так званий поліморфізм. Більш детально про переваги та особливості застосування поліморфізму описується тут.

При оголошенні методу (чи властивості) в абстрактному класі немає сенсу одночасно вказувати два слова abstract і virtual для того, щоб забезпечити поліморфізм. Якщо клас абстрактний, то достатньо тільки одного слова abstract. Якщо клас не абстрактний, то вказується virtual.

Текст методу Area() у класі Triangle

...

class Triangle : Figure
{
  ...

  // 5. Реалізація методу Area(), який в класі Figure
  //   оголошений як абстрактний
  public override double Area()
  {
    // 1. Провести обчислення
    double p, s;
    p = (a + b + c) / 2;
    s = Math.Sqrt(p * (p - a) * (p - b) * (p - c));

    // 2. Вивести результат у властивості - для контролю
    Console.WriteLine("Method Triangle.Area(): s = {0:f3}", s);

    // 3. Повернути результат
    return s;
  }
}

...

 

5.7. Функція Print(). Перевизначення віртуальної функції базового класу

Згідно з умовою задачі, в класі Triangle повинна бути використана функція Print(), яка призначена для виведення значень внутрішніх полів класу. Ця функція перевизначає (override) однойменну віртуальну (virtual) функцію базового класу Figures, тому при оголошенні функції вказується ключове слово override.

У тілі метода Print() відбувається виклик однойменного методу базового класу з допомогою рядка

// Викликати метод Print() базового класу - необов'язково
base.Print();

Текст методу Print() у класі Triangle наступний:

...

class Triangle : Figure
{
  ...

  // 6. Метод Print(), який перевизначає
  // однойменний метод базового класу Figures
  public override void Print()
  {
    // Викликати метод Print() базового класу - необов'язково
    base.Print();

    // Вивести значення внутрішніх полів a, b, c
    Console.WriteLine("a = {0:f2}", a);
    Console.WriteLine("b = {0:f2}", b);
    Console.WriteLine("c = {0:f2}", c);
  }
}

 

6. Реалізація функції Main(). Тестування роботи класів

У функції Main() класу Program тестується робота класів Figures та Triangle.

...

class Program
{
  static void Main(string[] args)
  {
    // Демонстрація поліморфізму з використанням
    // абстрактоного класу.
    // 1. Оголосити посилання на базовий клас
    Figure refFg;

    // 2. Оголосити екземпляр класу Figure
    // 2.1. Неможливо створити екземпляр абстрактного класу
    // Figure objFg = new Figure("Figure"); - помилка!

    // 2.2. Оголосити екземпляр класу Triangle
    Triangle Tr = new Triangle("Triangle", 2, 3, 2);

    // 3. Виклик методу Print() з допомогою посилання на базовий клас
    refFg = Tr;
    refFg.Print();

    // 4. Виклик методу Area() з допомогою посилання на базовий клас
    refFg = Tr;
    refFg.Area(); // викликається метод Triangle.Area()

    // 5. Використання властивості Area2 з допомогою посилання на базовий клас
    refFg = Tr;
    double area = refFg.Area2; // властивість Triangle.Area2
    Console.WriteLine("area = {0:f3}", area);

    Console.ReadKey();
  }
}

...

 

7. Текст програми. Результати

Нижче наведено текст всієї програми, що розв’язує дану задачу.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication8
{
  // Абстрактний клас Figure - містить абстрактний метод Area()
  // і абстрактну властивість Area2
  abstract class Figure
  {
    // 1. Приховане поле класу
    private string name; // Назва фігури

    // 2. Конструктор класу
    public Figure(string name)
    {
      this.name = name;
    }

    // 3. Властивість доступу до поля класу
    public string Name
    {
      get { return name; }
      set { name = value; }
    }

    // 4. Абстрактна властивість, яка повертає площу фігури
    public abstract double Area2 { get; }

    // 5. Абстрактний метод, який повертає площу фігури
    //   Метод не має тіла методу
    public abstract double Area();

    // 6. Віртуальний метод, який виводить значення полів класу
    public virtual void Print()
    {
      Console.WriteLine("name = {0}", name);
    }
  }

  // Клас, що реалізує трикутник. У класі немає абстрактних методів,
  // тому слово abstract не ставиться перед оголошенням класу.
  class Triangle : Figure
  {
    // 1. Внутрішні поля класу
    double a, b, c;

    // 2. Конструктор класу
    public Triangle(string name, double a, double b, double c)
                : base(name)
    {
      // Перевірка на коректність значень a, b, c
      if (((a + b) > c) && ((b + c) > a) && ((a + c) > b))
      {
        this.a = a; this.b = b; this.c = c;
      }
      else
      {
        Console.WriteLine("Incorrect values a, b, c.");
        Console.WriteLine("By default: a=1, b=1, c=1.");
        this.a = this.b = this.c = 1;
      }
    }

    // 3. Реалізація методів доступу до прихованих полів a, b, c
    // 3.1. Встановлення значень полів a, b, c
    public void SetABC(double a, double b, double c)
    {
      if (((a + b) > c) && ((b + c) > a) && ((a + c) > b))
      {
        this.a = a; this.b = b; this.c = c;
      }
      else
      {
        this.a = this.b = this.c = 1;
      }
    }

    // 3.2. Читання значень полів - звернути увагу на модифікатор out
    public void GetABC(out double a, out double b, out double c)
    {
      a = this.a; b = this.b; c = this.c;
    }

    // 4. Перевизначення абстрактної властивості Area2 класу Figure,
    //   ключове слово override обов'язкове
    public override double Area2
    {
      get
      {
        // 1. Провести обчислення
        double p, s;
        p = (a + b + c) / 2;
        s = Math.Sqrt(p * (p - a) * (p - b) * (p - c));

        // 2. Вивести результат у властивості - для контролю
        Console.WriteLine("Property Triangle.Area2: s = {0:f3}", s);

        // 3. Повернути результат
        return s;
      }
    }

    // 5. Реалізація методу Area(), який в класі Figure
    //   оголошений як абстрактний
    public override double Area()
    {
      // 1. Провести обчислення
      double p, s;
      p = (a + b + c) / 2;
      s = Math.Sqrt(p * (p - a) * (p - b) * (p - c));

      // 2. Вивести результат у властивості - для контролю
      Console.WriteLine("Method Triangle.Area(): s = {0:f3}", s);

      // 3. Повернути результат
      return s;
    }

    // 6. Віртуальний метод Print
    public override void Print()
    {
      base.Print();
      Console.WriteLine("a = {0:f2}", a);
      Console.WriteLine("b = {0:f2}", b);
      Console.WriteLine("c = {0:f2}", c);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Демонстрація поліморфізму з використанням
      // абстрактного класу.
      // 1. Оголосити посилання на базовий клас
      Figure refFg;

      // 2. Оголосити екземпляр класу Figure
      // 2.1. Неможливо створити екземпляр абстрактного класу
      // Figure objFg = new Figure("Figure"); - помилка!

      // 2.2. Оголосити екземпляр класу Triangle
      Triangle Tr = new Triangle("Triangle", 2, 3, 2);

      // 3. Виклик методу Print() з допомогою посилання на базовий клас
      refFg = Tr;
      refFg.Print();

      // 4. Виклик методу Area() з допомогою посилання на базовий клас
      refFg = Tr;
      refFg.Area(); // викликається метод Triangle.Area()

      // 5. Використання властивості Area2 з допомогою посилання на базовий клас
      refFg = Tr;
      double area = refFg.Area2; // властивість Triangle.Area2
      Console.WriteLine("area = {0:f3}", area);

      Console.ReadKey();
    }
  }
}

Результат роботи програми

name = Triangle
a = 2,00
b = 3,00
c = 2,00
Method Triangle.Area(): s = 1,984
Property Triangle.Area2: s = 1,984
area = 1,984

 

8. Завдання для перевірки знань

Доробити попередню програму, додавши до неї ще один клас.

Розробити клас TriangleColor, який успадковує (розширює) можливості класу Triangle. У класі реалізувати наступні елементи:

  • приховане внутрішнє поле color (колір фону трикутника);
  • конструктор з 5 параметрами, який викликає конструктор базового класу;
  • властивість Color, яка призначена для доступу до внутрішнього поля color;
  • властивість Area2, яка викликає однойменну властивість базового класу для обчислення площі трикутника;
  • метод Area(), який повертає площу трикутника за його сторонами;
  • віртуальний метод Print() для виведення внутрішніх полів класу. Метод звертається до однойменного методу базового класу.

Повний текст програми з текстом класу TriangleColor можна переглянути тут.

 


Зв’язані теми