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

Паттерн “Стратегия” позволяет выбирать тип алгоритма, который реализует общий интерфейс, прямо во время работы приложения.
Данный паттерн относится к поведенческим шаблонам проектирования.

Сунь Цзы

Допустим мы разрабатываем музыкальный плеер со встроенными кодеками. Под встроенными кодеками подразумеваяется чтение музыкальных форматов без использования внешних источников операционной системы (кодеков), плеер должен уметь читать треки разных форматов и воспроизводить их. Такими возможностями обладает плеер VLC, он поддерживает разные типы видео и аудио форматов, запускается на популярных и не очень операционных системах.

Представим как выглядит наивная имплементация плеера:

var player: MusicPlayer?

func play(filePath: String) {
    let extension = filePath.pathExtension

    if extension == "mp3" {
        playMp3(filePath)
    }
    else if extension == "ogg" {
        playOgg(filePath)
    }
}

func playMp3(_ filePath: String) {
    player = MpegPlayer()
    player?.playMp3(filePath)
}

func playOgg(_ filePath: String) {
    player = VorbisPlayer()
    player?.playMusic(filePath)
}

Далее мы добавляем несколько форматов, что приводит к необходимости написания дополнительных методов. Плюс плеер обязан поддерживать подключаемые библиотеки, с новыми форматами аудио, которые будут появляться в последствии. Налицо существует потребность в переключении алгоритма проигрывания музыки, для решения этой задачи используется паттерн Стратегия.

Создадим общий протокол MusicPlayerCodecAlgorithm, напишем реализацию протокола в двух классах MpegMusicPlayerCodecAlgorithm и VorbisMusicPlayerCodecAlgorithm, для проигрывания mp3 и ogg файлов со-но. Создадим класс MusicPlayer, который будет содержать референс на алгоритм который необходимо переключать, далее по расширению файла реализуем переключение типа кодека:

import Foundation

class MusicPlayer {
    var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
    
	func play(_ filePath: String) {
            playerCodecAlgorithm?.play(filePath)
	}
}

protocol MusicPlayerCodecAlgorithm {
    func play(_ filePath: String)
}

class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("mpeg codec - play")
	}
}

class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("vorbis codec - play")	
	}
}

func play(fileAtPath path: String) {
	guard let url = URL(string: path) else { return }
	let fileExtension = url.pathExtension
		
	let musicPlayer = MusicPlayer()
	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
		
	if fileExtension == "mp3" {
                playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
	}
	else if fileExtension == "ogg" {
                playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
	}
		
	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
	musicPlayer.playerCodecAlgorithm?.play(path)
}

play(fileAtPath: "Djentuggah.mp3")
play(fileAtPath: "Procrastinallica.ogg")

В приведенном выше примере также показан простейший пример фабрики (переключение типа кодека от расширения файла)
Важно отметить что паттерн Стратегия не создает объекты, только лишь описывает способ создания общего интерфейса для переключения семейства алгоритмов.

Источники

https://refactoring.guru/ru/design-patterns/strategy

Исходный код

https://gitlab.com/demensdeum/patterns/