Шаг 154.
Язык программирования C#. Начала
Абстрактные классы и интерфейсы. Использование абстрактных классов

    На этом шаге мы рассмотрим простой пример использования абстрактных классов.

    Для начала мы рассмотрим очень простой пример, в котором описывается абстрактный класс, а затем на основе этого класса создаются производные классы. Рассмотрим программу, представленную в примере ниже.

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

namespace pr154_1
{
    // Абстрактный класс: 
    abstract class Base {
        // Защищенное целочисленное поле: 
        protected int num;
        // Конструктор: 
        public Base(int n) {
            // Вызов метода: 
            set(n);
        }
        // Абстрактные методы: 
        public abstract void show(); 
        public abstract void set(int n); 
        public abstract int get();
    }

    // Производный класс на основе абстрактного класса: 
    class Alpha: Base {
        // Защищенное целочисленное поле: 
        protected int val;
        // Конструктор:
        public Alpha(int n): base(n) {
            // Вызов метода: 
            show();
        }
        // Переопределение абстрактного метода: 
        public override void show() {
            // Отображение сообщения:
            Console.WriteLine("Alpha: {0}, {1} и {2}", num, val, get());
        }
        // Переопределение абстрактного метода: 
        public override void set(int n) {
            // Присваивание значений полям:
            num = n;
            val = n % 10;
        }
        // Переопределение абстрактного метода: 
        public override int get() { 
            return num / 10;
        }
    }

    // Производный класс на основе абстрактного класса: 
    class Bravo: Base {
        // Защищенное целочисленное поле: 
        protected int val;
        // Конструктор:
        public Bravo(int n): base(n) {
            // Вызов метода: 
            show();
        }
        // Переопределение абстрактного метода: 
        public override void show() {
            // Отображение сообщения:
            Console.WriteLine("Bravo: {0}, {1} и {2}", num, val, get());
        }
        // Переопределение абстрактного метода: 
        public override void set(int n) {
            // Присваивание значений полям:
            num = n;
            val = n % 100;
        }
        // Переопределение абстрактного метода: 
        public override int get() { 
            return num / 100;
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Объектная переменная абстрактного класса:
            Base obj;
            // Создание объектов производных классов:
            Alpha A = new Alpha(123);
            Bravo B = new Bravo(321);
            // Объектной переменной базового класса присваивается
            // ссылка на объект производного класса:
            obj = A;
            Console.WriteLine("После выполнения команды obj = A");
            // Вызов методов через объектную переменную
            // базового класса:
            obj.set(456);
            obj.show();
            // Объектной переменной базового класса присваивается
            // ссылка на объект производного класса:
            obj = B;
            Console.WriteLine("После выполнения команды obj = B");
            // Вызов методов через объектную переменную
            // базового класса:
            obj.set(654);
            obj.show();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы следующий:


Рис.1. Результат выполнения программы

    В программе описан абстрактный класс Base. Он описан с ключевым словом abstract. В классе объявлены три абстрактных метода (все описаны с ключевым словом abstract):

Все эти методы должны быть описаны в производном классе, создаваемом на основе данного абстрактного класса. Но не все "содержимое" абстрактного класса является "абстрактным". В нем описано закрытое целочисленное поле num, а также конструктор с одним целочисленным аргументом. В теле конструктора содержится команда set(n), которой метод set() вызывается с аргументом n, переданным конструктору. Интересно здесь то, что метод set() абстрактный и в классе Base не описан, а только объявлен.


Хотя на основе абстрактного класса объект создать нельзя, но можно описать конструктор для абстрактного класса. Этот конструктор будет вызываться при создании объекта производного класса, поскольку в этом случае сначала вызывается конструктор базового класса. В нашем примере в теле конструктора класса Base вызывается абстрактный метод set(), в классе не описанный. Но проблемы при этом не возникает, поскольку выполняться конструктор базового класса будет при создании объекта производного класса, в котором метод set() должен быть описан. Проще говоря, на момент, когда метод set() будет вызываться, он уже будет описан.

    На основе класса Base создается два класса: Alpha и Bravo. Эти классы очень похожи, но описываются по-разному. Начнем с общих моментов для обоих классов. И в классе Alpha, и в классе Bravo появляется дополнительное целочисленное поле val. В каждом из классов описан конструктор с целочисленным аргументом, который передается конструктору базового класса. В теле конструктора вызывается метод show(). Но описывается метод show() в каждом классе со своими особенностями. В классе Alpha при вызове метода show() отображаются название класса Alpha, значения полей num и val и результат вызова метода get(). Метод show() для класса Bravo описан так же, но название класса отображается другое. Метод get() в каждом классе также свой. В классе Alpha метод get() описан так, что результатом возвращается значение num/10. Это значение поля num, если в нем отбросить разряд единиц. В классе Bravo результатом метода get() является значение num/100, которое получается отбрасыванием разрядов единиц и десятков в значении поля num.

    Метод set() в классе Alpha переопределен таким образом, что при целочисленном аргументе n выполняются команды

  num = n; 
и
  val = n % 10;   . 
То есть полю num присваивается значение аргумента, а полю val в качестве значения присваивается остаток от деления значения аргумента на 10 (это последняя цифра в десятичном представлении числового значения аргумента n).

    В классе Bravo метод set() описан похожим образом, но полю val значение присваивается командой

  val = n % 100;
(остаток от деления значения аргумента на 100, или число из двух последних цифр в десятичном представлении значения аргумента n).


Обращаем внимание, что все абстрактные методы из базового класса в производном классе описываются с ключевым словом override.

    В главном методе программы мы командой

  Base obj;
объявляем объектную переменную obj абстрактного класса Base. Командами
  Alpha A = new Alpha(123);
и
  Bravo B = new Bravo(321);
создаются объекты производных классов. При этом в консольном окне появляются сообщения, содержащие название класса для созданного объекта, значения полей и результат вызова метода get() из созданного объекта.


В сообщении, кроме имени класса, отображается еще три числа. Первое - это значение поля num. Второе - это значение поля val. Для объекта класса Alpha это последняя цифра в значении поля num, а для объекта класса Bravo это две последние цифры в значении поля num. Третье число - значение, возвращаемое методом get(). Для объекта класса Alpha это значение поля num без последней цифры, а для объекта класса Bravo это значение поля num без двух последних цифр.

    После выполнения команды

  obj = A;
объектной переменной базового абстрактного класса присваивается ссылка на объект производного класса Alpha. Далее командой
  obj.set(456);
меняются значения полей объекта, после чего командой
  obj.show();
проверяются значения полей и результат вызова метода get().

    Затем выполняется команда

  obj = B;   ,
при помощи которой объектной переменной базового абстрактного класса присваивается ссылка на объект производного класса Bravo. Командой
  obj.set(654); 
вносятся изменения в значения полей объекта, а командой
  obj.show(); 
проверяется результат.


Стоит заметить, что если мы вызываем через объектную переменную абстрактного базового класса переопределенные методы из объекта производного класса, то версия метода определяется на основе класса объекта, из которого вызывается метод.

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг