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

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


Содержание


1. Какие существуют типы отношений между классами?

Между классами возможны два типа отношений:

  • 1. Отношение типа is-a (есть, является, is-a relationship). В этом случае один класс есть подвидом другого класса. Иными словами один класс расширяет возможности другого класса. Этот тип базируется на использовании механизма наследования.
  • 2. Отношение, при котором существует взаимосвязь между двумя классами. Это отношение делится на два подтипа:
    • 2.1. Отношение типа has-a (класс содержит другой класс, has-a relationship). В этом случае в классе объявляется один или несколько объектов другого класса. Здесь также существует разделение: агрегация, композиция. Если вложенные объекты могут существовать независимо от класса (не являются составной частью класса), то это есть агрегация (aggregation). Если вложенные объекты (объект) дополняют класс таким образом, что существование класса невозможно без этих объектов, то это есть композиция (composition) или объединение;
    • 2.2. Отношение типа 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;
}

...

 


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