Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы

Рассказывает Akhil Mittal


В прошлых статьях серии “Введение в ООП” мы рассматривали полиморфизм (а также его нюансы на практике) и наследование. В этой мы поговорим о самой захватывающей части ООП-парадигмы — об абстрактных классах. В целом концепция абстрактных классов в C# ничем не отличается от таковой в других языках, но в C# работать с ней приходится несколько иначе.

Что такое абстрактные классы

В плане терминологии давайте доверимся MSDN:

Модификатор abstract указывает, что реализация сущности с данным модификатором является неполной или отсутствует. Модификатор abstract может использоваться с классами, методами, свойствами, индексаторами и событиями. Модификатор abstract в объявлении класса указывает, что класс предназначен только для использования в качестве базового класса для других классов. Члены, помеченные как абстрактные или включенные в абстрактный класс, должны быть реализованы с помощью классов, производных от абстрактных классов.

Абстрактные классы в действии

Итак, попробуем создать абстрактный класс:

using System;

namespace InheritanceAndPolymorphism
{
    public abstract class ClassA
    {

    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassA classA = new ClassA();
            Console.ReadKey();
        }
    }
}

Попытаемся скомпилировать этот код:

Compile time error: Cannot create an instance of the abstract class or interface ‘InheritanceAndPolymorphism.ClassA’

Что нужно запомнить: Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new.

Описание методов в абстрактном классе

Попробуем добавить в наш абстрактный класс немного кода:

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassA classA = new ClassA();
        Console.ReadKey();
    }
}

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

Использование абстрактного класса в качестве базового

Давайте попробуем создать ещё один класс:

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA
/// </summary>
public class ClassB:ClassA
{

}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

Вау. Теперь всё спокойно компилируется.

Что нужно запомнить: Мы можем унаследовать обычный класс от абстрактного.

Что нужно запомнить: Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new.

Декларация методов в абстрактном классе

Теперь попробуем сделать вот так:

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }

    public void YYY();
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{

}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

И… мы получаем ошибку компиляции:

Compile time error: ‘InheritanceAndPolymorphism.ClassA.YYY()’
must declare a body because it is not marked abstract, extern, or partial

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

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }

   abstract public void YYY();
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{

}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

…и снова получим ошибку компиляции:

Compiler error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’

Что нужно запомнить: Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract.

Что нужно запомнить: Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса.

Реализация абстрактного метода в производном классе

Так, давайте тогда попробуем реализовать метод YYY() в классе ClassB:

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }

   abstract public void YYY();
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
    public void YYY()
    {

    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

На первый взгляд всё отлично, правда? Но на этот раз мы получим сразу две ошибки компиляции:

Compile time error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’

Compile time warning: ‘InheritanceAndPolymorphism.ClassB.YYY()’ hides
inherited member ‘InheritanceAndPolymorphism.ClassA.YYY()’.

Дело в том, что в C# нужно явно объявить, что мы реализуем абстрактный метод класса-родителя с помощью ключевого слова override:

/// <summary>
    /// Abstract class ClassA
    /// </summary>
    public abstract class ClassA
    {
        public int a;
        public void XXX()
        {
            
        }

       abstract public void YYY();
    }

    /// <summary>
    /// Derived class.
    /// Class derived from abstract class ClassA.
    /// </summary>
    public class ClassB:ClassA
    {
        public override void YYY()
        {
             
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassB classB = new ClassB();
            Console.ReadKey();
        }
    }

Ура! ^_^ У нас наконец-то нет никаких ошибок!

Абстрактный метод базового класса и метод с override класса-наследника должны быть одинаковы

Это значит, что мы не можем менять тип возвращаемого значения или аргументы, которые передаются в метод. Например, если мы напишем такое:

/// <summary>
  /// Abstract class ClassA
  /// </summary>
  public abstract class ClassA
  {
      public int a;
      public void XXX()
      {

      }

     abstract public void YYY();
  }

  /// <summary>
  /// Derived class.
  /// Class derived from abstract class ClassA.
  /// </summary>
  public class ClassB:ClassA
  {
      public override int YYY()
      {

      }
  }

  /// <summary>
  /// Program: used to execute the method.
  /// Contains Main method.
  /// </summary>
  public class Program
  {
      private static void Main(string[] args)
      {
          ClassB classB = new ClassB();
          Console.ReadKey();
      }
  }

То в консоли увидим следующую ошибку:

Compile time error: ‘InheritanceAndPolymorphism.ClassB.YYY()’: return type must be ‘void’
to match overridden member ‘InheritanceAndPolymorphism.ClassA.YYY()’

Инициализация переменных в абстрактных классах

В примерах выше, переменная int a будет обладать значением по умолчанию (0). Мы можем изменить его на нужное нам прямо в абстрактном классе — с этим не связано никаких особенностей.

Абстрактные методы в неабстрактных классах

/// <summary>
/// Abstract class ClassA
/// </summary>
public class ClassA
{
    public int a;
    public void XXX()
    {

    }

   abstract public void YYY();
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
    public override void YYY()
    {

    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

Такой код не скомпилируется:

Compiler error: ‘InheritanceAndPolymorphism.ClassA.YYY()’ is abstract
but it is contained in non-abstract class ‘InheritanceAndPolymorphism.ClassA’

Что нужно запомнить: Абстрактные методы могут быть объявлены только в абстрактных классах.

Вызов абстрактного метода родителя

/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
    public int a;
    public void XXX()
    {

    }

   abstract public void YYY();
}

/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
    public override void YYY()
    {
         base.YYY();
    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassB classB = new ClassB();
        Console.ReadKey();
    }
}

Вывод:

Compile time error : Cannot call an abstract base member:
‘InheritanceAndPolymorphism.ClassA.YYY()’

Разумеется, мы не можем исполнить код, которого не существует.

Абстрактный класс, который наследуется от другого абстрактного класса

/// <summary>
/// Base class ClassA
/// </summary>
public class ClassA
{
    public virtual void XXX()
    {
        Console.WriteLine("ClassA XXX");
    }
}

/// <summary>
/// Derived abstract class.
/// Class derived from base class ClassA.
/// </summary>
public abstract class ClassB:ClassA
{
    public new abstract void XXX();
}

public class ClassC:ClassB
{
    public override void XXX()
    {
        System.Console.WriteLine("ClassC XXX");
    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
        ClassA classA = new ClassC();
        ClassB classB = new ClassC();
        classA.XXX(); classB.XXX();
    }
}

Вывод:

ClassA XXX
ClassC XXX

Почему вывод именно такой, вы должны понимать из материалов третьей статьи этой серии.

Может ли абстрактный класс быть sealed

Проверим:

/// <summary>
/// sealed abstract class ClassA
/// </summary>
public sealed abstract class ClassA
{
    public abstract void XXX()
    {
        Console.WriteLine("ClassA XXX");
    }
}

/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
    private static void Main(string[] args)
    {
    }
}

И получим ошибку:

Compile time error: ‘InheritanceAndPolymorphism.ClassA’:
an abstract class cannot be sealed or static

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

Что нужно запомнить: Абстрактный класс не может иметь модификатор sealed.

Что нужно запомнить: Абстрактный класс не может иметь модификатор static.

Что мы узнали сегодня:

Что нужно запомнить

  • Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new;
  • Мы можем унаследовать обычный класс от абстрактного;
  • Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new;
  • Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract;
  • Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса;
  • Абстрактные методы могут быть объявлены только в абстрактных классах;
  • Абстрактный класс не может иметь модификатор sealed;
  • Абстрактный класс не может иметь модификатор static.

Источник: «Diving in OOP (Day 4): Polymorphism and Inheritance (All About Abstract Classes in C#)»