Паттерн Стратегия

Последнее обновление: 16.09.2023

Иногда бывает необходимо предоставить клиенту интерфейс, реализация которого может отличаться и определяться после определения интерфейса в будущем, в том числе самим клиентом. То есть реализация интерфейса должна быть гибкой и при необходимости изменяться без изменения исходного кода. Распространенным решением в этом случае является применение паттерна Стратегия (некоторые также называют данный паттерн динамический интерфейс или Dynamic Interface)

Реализация паттерна состоит в определении на стороне модуля функции, которая принимает некоторый коллбек - указатель на другую функции. А внутри функции модуля вызываем через указатель этот колбек. В простейшем случае это выглядит следующим образом:

/* API модуля */
typedef void (*action)();
void some_function(action callback);

/* реализация на стороне модуля */
void some_function(action callback)
{
    // вызывем колбек
    callback();
}

/* применение на стороне клиента */
// реализация коллбека
void printMessage()
{
    printf("Hello METANIT.COM\n");
}

int main(void)
{
    // вызываем API
    some_function(printMessage);
}

В API модуля помещаем определение функции some_function, которая принимает указатель на другую функцию. В реализации функции API в модуле вызываем переданную через указатель функцию. А на стороне клиента мы вызываем функцию API, передая ей через параметр указатель на другую функцию. Таким образом, конкретная реализация функции API зависит от передаваемого в нее коллбека и фактически может определяться в будущем и многократно изменяться.

Рассмотрим небольшую программку. Пусть в файле sort.h у нас определеен следующий интерфейс модуля:

// тип указателя на функцию сравнения - принимает два параметра типа int и возвращает число int
typedef int (*comparator)(int x, int y);
// функция сортировки, принимает функцию  сравнения, массив и длину массива
void sort(comparator compare, int* array, size_t length);

API модуля состоит из одной функции sort, которая предназначена для сортировки массива чисел и которая принимает в качестве первого параметра функцию сравнения или функцию типа comparator. Этот тип определен строкой выше и представляет указатель на функцию, которая принимает два сравниваемых числа и возвращает число. Причем на момент определения файла мы не знаем, что это будет за функция. Мы только знаем, что она определяет логику сравнения двух чисел.

В файле sort.c, который представляет модуль функционала сортировки, определим реализацию функции sort:

#include <stdio.h>
#include "sort.h"

// реализация функции сортировки
void sort(comparator compare, int* array, size_t length)
{
  for(size_t i=0; i<length-1; i++)
  {
    for(size_t j=i+1; j<length; j++)
    {
      // выполняем переданную функцию compare
      if(compare(array[i], array[j]))
      {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
      }
    }
  }
}

Здесь определена простейшая функция сортировки, которая сравнивает одно из чисел массива со всеми последующими и в зависимости от результата сравнения меняет сравниваемые числа местами. Причем результат сравнения определяет передаваемая в качестве первого параметра функция

if(compare(array[i], array[j]))
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

Если функция возвращает ненулевое значение (условно говоря истину), то первое число должно стоять после второго. И опять же подчеркиваю на момент определения данного кода мы не знаем, что это будет за функция compare.

Далее определим файл app.c, в котором используем функцию sort:

#include <stdio.h>
#include "sort.h"

#define ARRAY_SIZE 4

// сортировка по возрастанию
int compareAsc(int x, int y)
{
  return x>y;
}
// сортировка по убыванию 
int compareDesc(int x, int y)
{
  return x<y;
}
int main(void)
{
    // массив для сортировки
    int array[ARRAY_SIZE] = {3, 5, 6, 1};
    // сортируем массив по возрастанию
    sort(compareAsc, array, ARRAY_SIZE);

    printf("Ascending: ");
    
    for(size_t i=0; i<ARRAY_SIZE; i++)
    {
        printf("%d ", array[i]);
    }

    // сортируем массив по убыванию
    sort(compareDesc, array, ARRAY_SIZE);

    printf("\n\nDescending: ");
    
    for(size_t i=0; i<ARRAY_SIZE; i++)
    {
        printf("%d ", array[i]);
    }
}

Здесь определены две функции сравнения, которые соответствуют определению указателя на функцию comparator. Функция compareAsc возвращает 1, если первое число больше второго. То есть первое число (большее число) должно располагаться после второго. Таким образом, речь идет о сортировке по возрастанию. Другая функция - compareDesc возвращает 1, если первое число меньше второго. То есть первое число (меньшее число) должно располагаться после второго. Соответственно речь идет о сортировке по убыванию.

В функции main вызываем функцию sort и передаем в нее одну из этих двух функций сравнения. И таким образом, мы можем динамически изменить поведение функции (в данном случае механизм сортировки). Пример компиляции и работы программы:

c:\C>gcc -Wall -pedantic app.c sort.c -o app & app
Ascending: 1 3 5 6

Descending: 6 5 3 1
c:\C>
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850