C#. Спадковість. Основні поняття. Переваги та недоліки. Загальна форма. Найпростіші приклади. Модифікатор доступу protected

Спадковість. Основні поняття. Переваги та недоліки. Загальна форма. Найпростіші приклади. Модифікатор доступу protected


Зміст


1. Що таке спадковість у програмуванні?

Спадковість – це один з принципів об’єктно-орієнтованого програмування, який дає можливість класу використовувати програмний код іншого (базового) класу, доповнюючи його своїми власними деталями реалізації. Іншими словами, при спадковості відбувається отримання нового (похідного) класу, який містить програмний код базового класу з вказанням власних особливостей використання. Спадковість належить до типу is-a відношення між класами. При спадковості створюється спеціалізована версія вже існуючого класу.

 

2. Переваги використання спадковості у програмах

Правильне використання механізму спадковості дає наступні взаємозв’язані переваги:

  • ефективна побудова складних ієрархій класів з можливістю їх зручної модифікації. Роботу класів в ієрархії можна змінювати шляхом додавання нових успадкованих класів у потрібному місці ієрархії;
  • повторне використання раніше написаного коду з подальшою його модифікацією під виконувану задачу. У свою чергу, новостворений код також може використовуватись на нижчих ієрархіях класів;
  • зручність у супроводі (доповненні) програмного коду шляхом введення нових класів з новими можливостями;
  • зменшення кількості логічних помилок при розробці складних програмних систем. Повторно використовуваний код частіше тестується, а, отже, менша ймовірність наявності в ньому помилок;
  • полегшення в узгодженні різних частин програмного коду шляхом використання інтерфейсів. Якщо два класи успадковані від спільного нащадка, поведінка цих класів буде однаковою у всіх випадках. Це твердження виходить з вимоги, що схожі об’єкти повинні мати схожу поведінку. Саме використання інтерфейсів зумовлює схожу поведінку об’єктів;
  • створення бібліотек коду, які можна використовувати та доповнювати власними розробками;
  • можливість реалізовувати відомі шаблони проектування для побудови гнучкого коду, який не змінює попередні розробки;
  • використання переваг поліморфізму неможливе без спадковості. Завдяки поліморфізму забезпечується принцип: один інтерфейс – декілька реалізацій;
  • забезпечення дослідного програмування (швидкого макетування). Таке програмування застосовується у випадках, коли цілі та вимоги до програмної системи на початку є розпливчасті. Спочатку створюється макет структури, потім цей макет поетапно удосконалюється завдяки успадкуванню попереднього. Процес триває до отримання бажаного кінцевого результату;
  • краще розуміння структури програмної системи програмістом завдяки природному представленню механізму спадковості. Якщо при побудові складних ієрархій пробувати використовувати інші принципи, то це може значно ускладнити розуміння всієї задачі і призведе до збільшення кількості помилок.

 

3. Недоліки спадковості

При використанні спадковості у програмах було помічено наступні недоліки:

  • неможливо змінити успадковану реалізацію під час виконання;
  • базовий клас частково визначає фізичне представлення своїх підкласів. Між реалізаціями базового та похідного класів існує сильний зв’язок, який при розв’язку деяких задачах небажаний;
  • низька швидкість виконання. Швидкість виконання програмного коду загального призначення нижча ніж у випадку використання спеціалізованого коду, який написаний саме для цієї задачі. Однак, цей недолік можна виправляти завдяки проведенню дослідження та оптимізації коду, який займає значний час виконання;
  • велика розмірність програм завдяки використанню бібліотек загального призначення. Якщо для деякої задачі розробляти вузькоспеціалізований програмний код, то цей код буде займати менше пам’яті ніж код загального призначення;
  • зростання складності програми у випадку неправильного або недолугого використання спадковості. Програміст повинен вміти правильно використовувати спадковість при побудові ієрархій класів. В іншому випадку це призведе до більшого заплутування програмного коду, і, як результат, збільшення помилок;
  • важкість засвоювання програмістами-початківцями основ побудови програм, що використовують спадковість. Потрібно вміти виділяти щось спільне, потім це спільне деталізувати і правильно кодувати. Однак, цей недолік умовний, тому що залежить від досвіду програміста.

 



4. Що таке базовий клас? Що таке похідний клас?

Базовий клас (base class) – це клас, програмний код якого використовується в успадкованих (похідних) класах. Похідний клас (derived class) – це клас, який використовує програмний код базового класу і змінює (розширює) його під свої потреби.

В інших мовах програмування (наприклад, Java) базовий клас ще називається суперклас (superclass), а похідний клас називається підклас (subclass).

 

5. Синтаксис спадковості у випадку двох класів. Загальна форма

Якщо один клас успадковує інший базовий клас, то загальна форма оголошення такого класу наступна:

class derived_class : base_class
{
    // тіло класу
    // ...
}

де

  • derived_class – ім’я похідного класу;
  • base_class – ім’я базового класу.

Наприклад.

// базовий клас
class Base
{
  // поля, методи класу
  // ...
}

// клас, успадкований від класу Base
class Derived : Base
{
  // поля, методи класу
  // ...
}

У вищенаведеному прикладі Base – базовий клас, Derived – клас, що успадковує можливості класу Base. У класі Derived є безпосередньо доступні усі елементи (поля, властивості, методи, індексатори та інше) які описуються з модифікаторами доступу protected, public та internal. В свою чергу, клас Derived може бути базовим для іншого класу нижчого рівня.

 

6. Доступ до елементів базового класу які оголошені з модифікаторами доступу private, protected, public, internal, protected internal

У похідному класі є доступні елементи базового класу, які оголошені з модифікаторами доступу protected, public, internal та protected internal.

Усі елементи базового класу, які оголошені з модифікатором private недоступні в похідному класі.

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

using System;

namespace ConsoleApp1
{
  // Базовий клас
  class Base
  {
    // внутрішні поля класу Base
    private int private_Item;
    protected int protected_Item;
    internal int internal_Item;
    protected internal int protIntern_Item;
    public int public_Item;
  }

  // Похідний клас від класу Base
  class Derived : Base
  {
    // метод, який змінює поля класу Base
    void Method()
    {
      protected_Item = 10;
      internal_Item = 20;
      protIntern_Item = 30;
      public_Item = 40;
      // private_item = 50; - помилка, елемент недоступний
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Доступ з екземпляру класу
      // 1. Базовий клас Base
      // 1.1. Створити екземпляр базового класу Base
      Base bs = new Base();

      // 1.2. Доступ до полів екземпляру bs
      // bs.private_Item = 10; - доступ заборонено
      // bs.protected_Item = 20; - доступ заборонено
      bs.internal_Item = 30; // internal доступ дозволено
      bs.public_Item = 40; // public - дозволено
      bs.protIntern_Item = 50; // protected internal - дозволено

      // 2. Похідний клас Derived - доступ до полів базового класу Base
      // 2.1. Створити екземпляр похідного класу Derived
      Derived dr = new Derived();
      // dr.private_Item - private-доступ заборонено
      // dr.protected_Item - protected-доступ заборонено
      dr.internal_Item = 30; // internal - дозволено
      dr.public_Item = 40; // public - дозволено
      dr.protIntern_Item = 50; // protected internal - дозволено
    }
  }
}

 

7. Скільки класів одночасно може бути успадковано від базового класу?

Мова програмування C# не підтримує множинної спадковості (на відміну від мови C++). З заданого класу одночасно може бути успадковано тільки один клас. Проте клас може успадковувати будь-яку кількість інтерфейсів. Більш детально про інтерфейси можна прочитати тут.

 

8. Особливості застосування модифікаторів доступу protected та protected internal. Приклад

Кожен елемент класу може мати різні рівні доступу: private, public, protected, internal, protected internal. Якщо в програмі використовується механізм спадковості, то особливу увагу заслуговують модифікатори доступу protected та protected internal.

Якщо елемент класу (поле, метод, властивість тощо) реалізований з модифікатором доступу protected, то для нього виконуються наступні правила:

  • елемент доступний в межах класу, в якому він оголошений, а також в успадкованих класах;
  • елемент недоступний з екземпляру класу.

Модифікатор доступу protected internal поєднує обмеження модифікатору protected та модифікатору internal (див. приклад). Тут можливі два випадки:

  1. Ситуація, коли клас з protected internal елементом і створюваний екземпляр цього класу розміщуються в одній збірці. У цьому випадку є доступ з екземпляру класу до protected internal елементу (розширення ключового слова internal). Також є доступ з похідного класу (розширення ключового слова protected).
  2. Ситуація, коли клас з protected internal елементом оголошено в одній збірці, а екземпляр цього класса створюється в іншій збірці. У цьому випадку екземпляр не має доступу до protected internal елементу (обмеження internal). Але можна створити похідний клас и з цього класу отримати доступ до protected internal елементу (розширення protected).

Приклад. Демонстрація використання модифікаторів доступу на прикладі трьох класів A, B, C.

using System;

namespace ConsoleApp1
{
  // Базовий клас
  class A
  {
    // Захищені поля класу A
    protected int a;
    protected internal int aa;
  }

  // Похідний клас B
  class B : A
  {
    // Захищені поля класу B
    protected int b;
    protected internal int bb;

    public void MethodB()
    {
      // є доступ до protected та
      // protected internal елементів класу A
      a = 25;
      aa = 30;
    }
  }

  // Похідний клас C
  class C : B
  {
    // Захищені поля класу C
    protected int c;
    protected internal int cc;

    public void MethodC()
    {
      // є доступ до protected та
      // protected internal елементів класів A, B
      a = 10;
      aa = 20;
      b = 30;
      bb = 40;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Доступу з екземплярів класів до
      // protected-елементів немає
      A objA = new A(); // екземпляр класу A
      B objB = new B();
      C objC = new C();

      // objA.a = 30; - доступу немає
      // objB.a = 20; - доступу немає
      // objB.b = 20; - доступу немає
      // objC.a = 20; - доступу немає
      // objC.b = 20; - доступу немає
      // objC.c = 20; - доступу немає

      // Однак, є доступ до protected internal
      // екземплярів класів A, B, C,
      // оскільки в даному прикладі класи A, B, C та їх
      // екземпляри objA, objB, objC оголошені в одній збірці
      objA.aa = 40;
      objB.aa = 50;
      objB.bb = 50;
      objC.aa = 10;
      objC.bb = 20;
      objC.cc = 30;
    }
  }
}

 


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