C#. Типи відношень між класами: is-a, has-a, uses. Приклади. Агрегація. Композиція

Типи відношень між класами: is-a, has-a, uses. Приклади. Агрегація. Композиція


Зміст


1. Які існують типи відношень між класами?

Між класами можливі два типи відношень:

  • Відношення типу is-a (є, являється, is-a relationship). У цьому випадку один клас є підвидом іншого класу. Іншими словами один клас розширює можливості іншого класу. Цей тип базується на використанні механізму спадковості.
  • Відношення, при якому існує взаємозв’язок між двома класами. Це відношення поділяється на два підтипи:
    • відношення типу has-a (клас містить інший клас, has-a relationship). У цьому випадку в класі оголошується один або декілька об’єктів іншого класу. Тут також існує свій поділ: агрегація, композиція. Якщо вкладені об’єкти можуть існувати незалежно від класу (не є складовою частиною класу), то це є агрегація (aggregation). Якщо вкладені об’єкти (об’єкт) доповнюють клас таким чином, що існування класу неможливе без цих об’єктів, то це є композиція (composition) або об’єднання;
    • відношення типу uses (клас “використовує інший клас). Це узагальнене відношення при якому можливі різні форми використання одного класу іншим (дивіться тут). Якщо в програмі оголошено два класи, то необов’язково один клас повинен містити екземпляр іншого класу. Клас може використовувати тільки деякий метод іншого класу, клас може звертатись до імені іншого класу (використовувати ім’я), клас може використовувати поле даних іншого класу і т.д. Більш детально про використання типу відношення uses можна прочитати тут.

 

2. Приклад найпростішого типу відношення is-a (спадковість)

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

Нехай задано клас Point, що описує точку на координатній площині. У класі реалізовано наступні елементи:

  • внутрішні поля класу x, y;
  • конструктор з двома параметрами;
  • конструктор без параметрів, який ініціалізує точку координатами (0; 0);
  • властивості X, Y для доступу до внутрішніх полів x, y класу;
  • метод LengthOrigin(), який визначає відстань від точки (x; y) до початку координат;
  • метод Print(), який виводить значення полів x; y.

На наступному кроці виникає необхідність розширити клас Point новим елементом color, який задає колір точки на координатній площині. Щоб не поправляти код (інколи це неможливо) класу Point достатньо реалізувати новий клас ColorPoint, який успадковує (розширює) клас Point і додає в нього колір.

У даному прикладі, в успадкованому класі ColorPoint реалізовано елементи, які доповнюють (розширюють) можливості класу Point:

  • внутрішнє приховане поле color – колір точки, який отримується зі зчислення Colors;
  • конструктор з 3 параметрами, який ініціалізує значення точки координатами (x; y) та кольору color;
  • властивість Color яка реалізує доступ до внутрішнього поля color;
  • метод Print(), який виводить значення кольору та координати (x, y) базового класу Color. У методі викликається однойменний метод Print() базового класу.

Текст програми наступний.

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Зчислення, яке визначає перелік кольорів,
  // необхідне для використання у класі ColorPoint
  enum Colors { Black = 0, Green = 1, Yellow = 2, Red = 3, Blue = 4 };

  // Базовий клас Point
  class Point
  {
    // 1. Внутрішні поля класу - координати x, y
    private double x, y;

    // 2. Конструктори класу
    // 2.1. Конструктор з 2 параметрами - головний конструктор
    public Point(double x, double y)
    {
      this.x = x; this.y = y;
    }

    // 2.2. Конструктор без параметрів
    public Point() : this(0, 0)
    {
    }

    // 3. Властивості доступу до полів X, Y
    public double X
    {
      get { return x; }
      set { x = value; }
    }

    public double Y
    {
      get { return y; }
      set { y = value; }
    }

    // 4. Метод LengthOrigin(), який повертає відстань
    //   від початку координат до точки (x; y)
    public double LengthOrigin()
    {
      // Теорема Піфагора
      return Math.Sqrt(x * x + y * y);
    }

    // 5. Метод Print() - виводить значення полів x, y
    public void Print()
    {
      WriteLine($"x = {x}, y = {y}");
    }
  }

  // Успадкований клас ColorPoint
  class ColorPoint : Point
  {
    // 1. Приховане поле - колір точки
    private Colors color;

    // 2. Конструктор з 3 параметрами
    public ColorPoint(double x, double y, Colors color) : base(x, y)
    {
      this.color = color;
    }

    // 3. Властивість Color
    public Colors Color
    {
      get { return color; }
      set
      {
        if (color >= 0)
          color = value;
        else
          color = 0;
      }
    }

    // 4. Метод Print()
    void Print()
    {
      // Виклик методу базового класу
      base.Print();

      // Вивести колір
      Write("color = ");
      switch(color)
      {
        case Colors.Black:
          WriteLine("Black");
          break;
        case Colors.Blue:
          WriteLine("Blue");
          break;
        case Colors.Green:
          WriteLine("Green");
          break;
        case Colors.Red:
          WriteLine("Red");
          break;
        case Colors.Yellow:
          WriteLine("Yellow");
          break;
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Демонстрація роботи класу Point
      WriteLine("Demo Point:");
      Point pt = new Point(3, 5);
      pt.Print();
      double len = pt.LengthOrigin();
      WriteLine("LengthOrigin = {0:f2}", len);

      // Демонстрація роботи класу ColorPoint
      WriteLine("Demo ColorPoint");
      ColorPoint cp = new ColorPoint(1, 3, Colors.Green);
      cp.Print();
    }
  }
}

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

Demo Point:
x = 3, y = 5
LengthOrigin = 5.83
Demo ColorPoint
x = 1, y = 3

 

3. Відношення типу has-a між класами

При відношенні типу has-a клас містить один або декілька об’єктів (екземплярів) іншого класу. Існує два різновиди відношення типу has-a:

  • агрегація. Це випадок, коли один або декілька вкладених об’єктів не є частиною класу, тобто клас може логічно існувати без цих об’єктів. Клас може містити будь-яку кількість таких об’єктів (навіть 0). Дивіться наведені нижче приклади;
  • композиція. У цьому випадку один або декілька вкладених об’єктів є частиною класу, тобто без цих об’єктів неможливе логічне існування самого класу.

Приклади класів, в яких реалізований підхід агрегації:

  • клас Автостоянка може містити масиви (списки) екземплярів класів Автомобіль, Мотоцикл, Автобус. Якщо в якийсь момент часу на автостоянці не буде жодного автомобіля, автостоянка буде продовжувати функціонувати. В той же час Автомобіль, Мотоцикл, Автобус є незалежні і самостійні екземпляри;
  • клас Figures може містити масиви екземплярів класів Rectangle (прямокутник), Triangle (трикутник), Circle (Коло);
  • клас House (будинок) може містити різну кількість об’єктів класів Table (стіл), TVSet (телевізор), Bed (ліжко) і т.д.

Приклади взаємодії між класами, що відносяться до композиції:

  • клас Автомобіль обов’язково містить один екземпляр класу Двигун та чотири екземпляри класу Колесо. Екземпляри класів Двигун та Колесо є складовою Автомобіля. Якщо забрати один з цих екземплярів, то Автомобіль функціонувати не буде;
  • клас House (будинок) обов’язково повинен містити екземпляр класу Roof (криша) та чотири екземпляри класу Wall (стіна);
  • клас Triangle (трикутник на координатній площині) містить три екземпляри класу Point (точка).


 

3.1. Приклад агрегації для типу відношення has-a

У випадку агрегації клас містить множини (один або декілька) об’єктів інших класів, які не є частиною цього класу.

Приклад.

Клас Figures містить масив класів Point (точка) та масив класів Line (лінія). Кількість елементів у масивах може бути довільною, навіть рівною 0. Це означає, що клас Figures може існувати без наявних екземплярів класів Point чи Line. Такий вид взаємодії між класами називається агрегацією (aggregation).

Текст демонстраційного прикладу наступний.

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Агрегація
  // 1. Клас, що описує точку
  class Point
  {
    // Внутрішні поля класу - координати точки
    public double x;
    public double y;
  }

  // 2. Клас, що описує лінію
  class Line
  {
    public Point pt1 = null;
    public Point pt2 = null;
  }

  // 3. Клас, що описує масив фігур
  class Figures
  {
    // 1. Внутрішні поля класу
    public Point[] points; // масив точок
    public Line[] lines; // масив ліній

    // 2. Конструктор класу
    public Figures()
    {
      points = null;
      lines = null;
    }

    // 3. Метод виведення елементів масиву на екран
    public void Print()
    {
      WriteLine("Array points:");
      for (int i = 0; i < points.Length; i++)
      {
        WriteLine("x = {0}, y = {1}", points[i].x, points[i].y);
      }

      WriteLine("Array lines:");
      for (int i=0;i<lines.Length; i++)
      {
        WriteLine("pt1.x = {0}, pt1.y = {1}", lines[i].pt1.x, lines[i].pt1.y);
        WriteLine("pt2.x = {0}, pt2.y = {1}", lines[i].pt2.x, lines[i].pt2.y);
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Демонстрація агрегації на прикладі класів Figures, Point, Line
      // 1. Створити екземпляр класу Figures
      Figures fg = new Figures();

      // 2. Створити масив з 5 точок, що є об'єктами класу Point
      //    об'єкті (екземплярі) класу Figures
      // 2.1. Виділити пам'ять для 5 елементів масиву
      fg.points = new Point[5];

      // 2.2. Виділити пам'ять для кожного елементу масиву
      //       та заповнити їх значеннями
      for (int i = 0; i < fg.points.Length; i++)
      {
        fg.points[i] = new Point();
        fg.points[i].x = i * i;
        fg.points[i].y = i * i * i;
      }

      // 3. Створити масив з 3 ліній
      // 3.1. Виділити пам'ять для 3 елементів масиву
      fg.lines = new Line[3];

      // 3.2. Виділити пам'ять для кожного елементу масиву
      //      та заповнити їх значеннями
      for (int i = 0; i < fg.lines.Length; i++)
      {
        fg.lines[i] = new Line();
        fg.lines[i].pt1 = new Point();
        fg.lines[i].pt2 = new Point();
        fg.lines[i].pt1.x = i;
        fg.lines[i].pt1.y = i * 2;
        fg.lines[i].pt2.x = i * 3;
        fg.lines[i].pt2.y = i * i;
      }

      // 4. Вивести масив точок та ліній на екран
      fg.Print();
    }
  }
}

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

Array points:
x = 0, y = 0
x = 1, y = 1
x = 4, y = 8
x = 9, y = 27
x = 16, y = 64

Array lines:
pt1.x = 0, pt1.y = 0
pt2.x = 0, pt2.y = 0
pt1.x = 1, pt1.y = 2
pt2.x = 3, pt2.y = 1
pt1.x = 2, pt1.y = 4
pt2.x = 6, pt2.y = 4

 

3.2. Приклад композиції для типу відношення has-a

Розглянемо клас Line, що описує лінію на основі двох точок. Точки описуються класом Point. Клас Line містить 2 екземпляри класів Point. Без цих екземплярів (об’єктів) клас Line існувати не може, оскільки обидва екземпляри складають частину лінії (крайні точки лінії). Таким чином, обидва екземпляри класу Point є частиною класу Line. Такий вид взаємодії називається композицією (composition) або об’єднанням.

Фрагмент прикладу наступний.

...

// Композиція
// 1. Клас, що описує точку
class Point
{
  // Внутрішні поля класу - координати точки
  public double x;
  public double y;
}

// 2. Клас, що описує лінію
class Line
{
  // Внутрішні поля класу є екземплярами (об'єктами) класу Point.
  // Без цих полів клас Line не має сенсу, отже,
  // поля pt1, pt2 доповнюють клас Line (є частиною класу Line),
  // це є композиція.
  public Point pt1 = null;
  public Point pt2 = null;
}

...

 


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