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 можно просмотреть здесь.

 


Связанные темы