В основу категоризації каталогу патернів лягли три найпростіші об’єктно-орієнтовані техніки. Це техніка використання об’єктів фабрик, які породжують об’єкти-продукти, техніка використання об’єкта фасаду та техніка диспетчеризації.
Техніка використання диспетчеризації має дві форми: «Ланцюжок об’єктів» і «Видавець-Підписник». Ці техніки були покладені в основу поведінкових патернів.
При використанні техніки «Видавець-Підписник» — об’єкт видавець викликає метод на об’єкті-підписнику, а об’єкт-підписник після цього викликає метод на об’єкті-видавці. Таким чином об’єкт видавець повідомляє об’єкт-підписник про настання певної події.
OBSERVER/СПОСТЕРІГАЧ — ВИЗНАЧАЄ ЗАЛЕЖНІСТЬ «ОДИН ДО БАГАТЬОХ» МІЖ ОБ’ЄКТАМИ ТАК, ЩО ПРИ ЗМІНАХ В ОДНОМУ ОБ’ЄКТІ ВСІ ПОВ’ЯЗАНІ З НИМ ОБ’ЄКТИ ДІЗНАЮТЬСЯ ПРО ЦЮ ЗМІНУ І ОНОВЛЮЮТЬСЯ АВТОМАТИЧНО.
Використовуйте патерн Спостерігач у наступних випадках:
Формально патерн Спостерігач можна виразити наступною схемою UML:
При цьому спостережуваному об’єкту не потрібно нічого знати про спостерігача, крім того, що він реалізує метод Update(). За допомогою відношення агрегації реалізується слабке зв’язування обох компонентів. Зміни в спостережуваному об’єкті не впливають на спостерігача і навпаки.
В певний момент спостерігач може припинити спостереження. І після цього обидва об’єкти — спостерігач і спостережуваний — можуть існувати в системі незалежно один від одного.
Створити консольний додаток.
Припустимо, у нас є перехрестя, на якому висить світлофор, і є пішоходи та автомобілі, які стежать за інформацією від світлофора (колір змінюється з червоного на зелений), і залежно від отриманої інформації виконують певні дії:
Тут спостережуваний об’єкт представлений інтерфейсом IObservable, а спостерігач — інтерфейсом IObserver.
interface IObservable
{
// Метод для реєстрації нового спостерігача
void RegisterObserver(IObserver o);
// Метод для видалення спостерігача зі списку
void RemoveObserver(IObserver o);
// Метод для повідомлення всіх зареєстрованих спостерігачів про зміну стану
void NotifyObservers();
}
///
/// Інтерфейс спостерігача (Observer)
///
public interface IObserver
{
///
/// Метод оновлення стану спостерігача
///
/// Об'єкт, що містить дані для оновлення
void Update(object ob);
}
Реалізацією інтерфейсу IObservable є клас TrafficLightSingleton, який символізує світлофор. У цьому класі визначити метод ChangeLight(int i), який імітує зміну світла. Після зміни світла відбувається повідомлення всіх спостерігачів (пішоходів та водіїв транспортних засобів).
///
/// Клас-одинак (Singleton) для управління світлофором з реалізацією патерну Спостерігач
///
public class TrafficLightSingleton : IObservable
{
private TrafficLightInfo _trafficLightInfo; // Поточна інформація про стан світлофора
private List _observers; // Список підписаних спостерігачів
private static TrafficLightSingleton _instance; // Єдиний екземпляр класу
// Приватний конструктор (частина патерну Singleton)
private TrafficLightSingleton()
{
_observers = new List();
}
///
/// Метод для отримання єдиного екземпляру класу
///
public static TrafficLightSingleton GetInstance()
{
if (_instance == null)
{
_instance = new TrafficLightSingleton();
}
return _instance;
}
///
/// Реєстрація нового спостерігача
///
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
///
/// Видалення спостерігача зі списку
///
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
///
/// Сповіщення всіх спостерігачів про зміни
///
public void NotifyObservers()
{
foreach (IObserver observer in _observers)
{
observer.Update(_trafficLightInfo);
}
}
///
/// Зміна стану світлофора для пішоходів
///
public void ChangeLightForPedestrian()
{
// Зміна кольорів консолі для наочності
Console.ForegroundColor = ConsoleColor.Black;
Console.BackgroundColor = ConsoleColor.White;
_trafficLightInfo.ChangeLight(); // Оновлення стану світлофора
NotifyObservers(); // Сповіщення підписаних спостерігачів
}
}
Реалізаціями інтерфейсу IObserver є класи Driver (водій), Pedestrian (пішохід). При цьому метод Update() інтерфейсу IObserver приймає як параметр деякий об’єкт TrafficLightInfo, який зберігає інформацію про поточний колір світлофора.
namespace ObserverSvetophor
{
///
/// Клас, що містить інформацію про стан світлофора
///
public class TrafficLightInfo
{
private Random _random = new Random(); // Генератор випадкових чисел
///
/// Змінює стан світлофора випадковим чином
///
/// Рядок, що вказує на поточний колір ("red" або "green")
public string ChangeLight()
{
double randomValue = _random.NextDouble();
if (Math.Round(randomValue) == 0)
{
return RedLightIsOn(); // Увімкнути червоний
}
else
{
return GreenLightIsOn(); // Увімкнути зелений
}
}
///
/// Встановлює червоний колір світлофора
///
/// Рядок "red"
public string RedLightIsOn()
{
Console.ForegroundColor = ConsoleColor.Blue; // Колір тексту
Console.BackgroundColor = ConsoleColor.Red; // Колір фону (червоний)
return "red";
}
///
/// Встановлює зелений колір світлофора
///
/// Рядок "green"
public string GreenLightIsOn()
{
Console.ForegroundColor = ConsoleColor.Black; // Колір тексту
Console.BackgroundColor = ConsoleColor.Green; // Колір фону (зелений)
return "green";
}
}
}
///
/// Клас водія, який реалізує інтерфейс спостерігача (IObserver)
///
public class Driver : IObserver
{
private string _number; // Номер транспортного засобу
private IObservable _trafficLight; // Суб'єкт спостереження (світлофор)
///
/// Конструктор класу водія
///
/// Номер транспортного засобу
/// Світлофор, за яким спостерігає водій
public Driver(string vehicleNumber, IObservable trafficLight)
{
this._number = vehicleNumber;
_trafficLight = trafficLight;
_trafficLight.RegisterObserver(this); // Реєстрація спостерігача
}
///
/// Метод оновлення стану (викликається при зміні світлофора)
///
/// Об'єкт із інформацією про стан світлофора
public void Update(object ob)
{
Random random = new Random();
TrafficLightInfo trafficInfo = (TrafficLightInfo)ob;
int randomValue = random.Next(2); // Генерує 0 або 1
if (trafficInfo.ChangeLight(randomValue) == "red")
VehicleMoved(); // Транспорт рухається
else
VehicleStopped(); // Транспорт зупинився
}
///
/// Транспортний засіб рухається (червоне світло)
///
private void VehicleMoved()
{
Console.WriteLine("{0} їде, пішоходи стоять.", this._number);
}
///
/// Транспортний засіб зупинився (зелене світло)
///
private void VehicleStopped()
{
Console.WriteLine("{0} зупинився, пішоходи йдуть.", this._number);
}
}
///
/// Клас пішохода, що реалізує інтерфейс спостерігача (IObserver)
///
public class Pedestrian : IObserver
{
private string _name; // Ім'я пішохода
private IObservable _trafficLight; // Об'єкт світлофора, за яким спостерігаємо
///
/// Конструктор класу пішохода
///
/// Ім'я пішохода
/// Об'єкт світлофора
public Pedestrian(string name, IObservable trafficLight)
{
this._name = name;
_trafficLight = trafficLight;
_trafficLight.RegisterObserver(this); // Реєстрація як спостерігача
}
///
/// Метод оновлення стану (викликається при зміні світлофора)
///
/// Об'єкт із інформацією про стан світлофора
public void Update(object ob)
{
Random random = new Random();
TrafficLightInfo trafficInfo = (TrafficLightInfo)ob;
int randomValue = random.Next(2); // Генерує 0 або 1
if (trafficInfo.ChangeLight(randomValue) == "red")
PedestrianStopped(); // Пішоход зупиняється
else
PedestrianCrossed(); // Пішоход переходить дорогу
}
///
/// Метод для зупинки спостереження за світлофором
///
public void StopTracking()
{
_trafficLight.RemoveObserver(this); // Видалення спостерігача
_trafficLight = null; // Очищення посилання
}
///
/// Дія пішохода при зеленому світлі
///
public void PedestrianCrossed()
{
Console.WriteLine("{0} переходить дорогу.", this._name);
}
///
/// Дія пішохода при червоному світлі
///
public void PedestrianStopped()
{
Console.WriteLine("{0} зупинився.", this._name);
}
}
// Головний клас програми
class Program
{
static void Main(string[] args)
{
// Отримуємо єдиний екземпляр світлофора (Singleton)
TrafficLightSingleton trafficLight = TrafficLightSingleton.GetInstance();
// Створюємо першого водія (автобус) та пішохода (Петров)
Driver busDriver = new Driver("Автобус", trafficLight);
Pedestrian petrov = new Pedestrian("Петров", trafficLight);
// Імітуємо зміну світла світлофора
trafficLight.ChangeLightForPedestrian();
// Пішоход Петров припиняє спостерігати за світлофором
petrov.StopTracking();
// Знову імітуємо зміну світла
trafficLight.ChangeLightForPedestrian();
// Додаємо нові транспортні засоби та пішоходів
Driver tramDriver = new Driver("Трамвай", trafficLight);
Pedestrian ivanov = new Pedestrian("Іванов", trafficLight);
trafficLight.ChangeLightForPedestrian();
ivanov.StopTracking();
Driver trolleybusDriver = new Driver("Тролейбус", trafficLight);
Pedestrian sidorov = new Pedestrian("Сидоров", trafficLight);
trafficLight.ChangeLightForPedestrian();
sidorov.StopTracking();
// Фінальна зміна світла
trafficLight.ChangeLightForPedestrian();
Console.ReadKey(); // Чекаємо натискання клавіші перед закриттям
}
}

using System;
// Singleton: єдиний екземпляр світлофора
public sealed class TrafficLight
{
private static readonly TrafficLight instance = new TrafficLight();
private string currentColor = "Red"; // Початковий стан
private TrafficLight() {} // Приватний конструктор
public static TrafficLight Instance => instance;
// Подія для Observer
public event Action<string> OnColorChanged;
public void ChangeColor(string newColor)
{
currentColor = newColor;
OnColorChanged?.Invoke(newColor);
}
public string GetColor() => currentColor;
}
using System;
// Observer: слухачі світлофора
public class Car
{
public void React(string color)
{
Console.ForegroundColor = ConsoleColor.White;
Console.Write("Автомобіль: ");
switch (color)
{
case "Green":
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Їхати");
break;
case "Yellow":
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Готуватись до зупинки");
break;
case "Red":
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Зупинитись");
break;
default:
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("Чекати");
break;
}
}
}
public class Pedestrian
{
public void React(string color)
{
Console.ForegroundColor = ConsoleColor.White;
Console.Write("Пішохід: ");
switch (color)
{
case "Green":
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Чекати");
break;
case "Yellow":
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Готуватись до переходу");
break;
case "Red":
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Можна переходити");
break;
default:
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine("Очікувати");
break;
}
}
}
class Program
{
static void Main()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
TrafficLight light = TrafficLight.Instance;
light.OnColorChanged += new Car().React;
light.OnColorChanged += new Pedestrian().React;
string[] colors = { "Green", "Yellow", "Red" };
foreach (var color in colors)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"\nСвітлофор змінюється на {color}:");
light.ChangeColor(color);
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Поточний колір: " + light.GetColor());
}
Console.ResetColor();
}
}
Світлофор змінюється на Green:
Автомобіль: Їхати
Пішохід: Чекати
Поточний колір: Green
Світлофор змінюється на Yellow:
Автомобіль: Готуватись до зупинки
Пішохід: Готуватись до переходу
Поточний колір: Yellow
Світлофор змінюється на Red:
Автомобіль: Зупинитись
Пішохід: Можна переходити
Поточний колір: Red
Створити консольний додаток.
Припустимо, у нас є біржа, де проходять торги, і є брокери та банки, які стежать за інформацією і в залежності від отриманої інформації виконують певні дії:
Тут спостережуваний об’єкт представлений інтерфейсом IObservable, а спостерігач — інтерфейсом IObserver.
// Інтерфейс IObservable визначає методи для роботи з спостерігачами (Observers)
interface IObservable
{
// Метод для реєстрації нового спостерігача
// Параметр: o - об'єкт, який реалізує інтерфейс IObserver
void RegisterObserver(IObserver o);
// Метод для видалення спостерігача зі списку
// Параметр: o - об'єкт, який потрібно видалити
void RemoveObserver(IObserver o);
// Метод для сповіщення всіх зареєстрованих спостерігачів про зміни
void NotifyObservers();
}
// Інтерфейс IObserver визначає метод для оновлення даних у спостерігача (Observer)
interface IObserver
{
// Метод, який викликається при отриманні сповіщення від об'єкта, за яким ведеться спостереження (Observable)
// Параметр: ob - об'єкт, що передає дані для оновлення (може бути будь-якого типу)
void Update(Object ob);
}
Реалізацією інтерфейсу IObservable є клас Stock, який символізує валютну біржу. У цьому класі визначений метод Market(), який імітує торги і інкапсулює всю інформацію про валютні курси в об’єкті StockInfo. Після проведення торгів відбувається повідомлення всіх спостерігачів.
Реалізаціями інтерфейсу IObserver є класи Broker, який представляє брокера, і Bank, який представляє банк. Метод Update() інтерфейсу IObserver приймає як параметр певний об’єкт.
// Клас Bank, який реалізує інтерфейс IObserver для реагування на зміни курсу валют
class Bank : IObserver
{
// Назва банку
public string Name { get; set; }
// Посилання на об'єкт, за яким ведеться спостереження (наприклад, біржа)
private IObservable stock;
// Конструктор класу Bank
// Параметри:
// - name: назва банку
// - obs: об'єкт, який реалізує інтерфейс IObservable (наприклад, біржа)
public Bank(string name, IObservable obs)
{
this.Name = name; // Ініціалізація назви банку
stock = obs; // Збереження посилання на об'єкт спостереження
stock.RegisterObserver(this); // Реєстрація банку як спостерігача
}
// Метод, який викликається при оновленні даних від об'єкта спостереження
// Параметр:
// - ob: об'єкт, що містить інформацію про зміни (у цьому випадку StockInfo)
public void Update(object ob)
{
// Приведення типу об'єкта до StockInfo
StockInfo sInfo = (StockInfo)ob;
// Логіка реагування на зміни курсу євро
if (sInfo.Euro > 40)
Console.WriteLine("Банк {0} продає євро; Курс євро: {1}", this.Name, sInfo.Euro);
else
Console.WriteLine("Банк {0} купує євро; Курс євро: {1}", this.Name, sInfo.Euro);
}
}
// Клас Broker, який реалізує інтерфейс IObserver для реагування на зміни курсу долара
class Broker : IObserver
{
// Ім'я брокера
public string Name { get; set; }
// Посилання на об'єкт, за яким ведеться спостереження (наприклад, біржа)
private IObservable stock;
// Конструктор класу Broker
// Параметри:
// - name: ім'я брокера
// - obs: об'єкт, який реалізує інтерфейс IObservable (наприклад, біржа)
public Broker(string name, IObservable obs)
{
this.Name = name; // Ініціалізація імені брокера
stock = obs; // Збереження посилання на об'єкт спостереження
stock.RegisterObserver(this); // Реєстрація брокера як спостерігача
}
// Метод, який викликається при оновленні даних від об'єкта спостереження
// Параметр:
// - ob: об'єкт, що містить інформацію про зміни (у цьому випадку StockInfo)
public void Update(object ob)
{
// Приведення типу об'єкта до StockInfo
StockInfo sInfo = (StockInfo)ob;
// Логіка реагування на зміни курсу долара
if (sInfo.USD > 30)
Console.WriteLine("Брокер {0} продає долари; Курс долара: {1}", this.Name, sInfo.USD);
else
Console.WriteLine("Брокер {0} купує долари; Курс долара: {1}", this.Name, sInfo.USD);
}
// Метод для припинення спостереження та видалення брокера зі списку спостерігачів
public void StopTrade()
{
if (stock != null)
{
stock.RemoveObserver(this); // Видалення брокера зі списку спостерігачів
stock = null; // Очищення посилання на об'єкт спостереження
}
}
}
Реалізація цього методу передбачає отримання через цей параметр об’єкта StockInfo з актуальною інформацією про торги і виконання деяких дій: купівля або продаж доларів і євро. Справа в тому, що часто потрібно інформувати спостерігача про зміну стану спостережуваного об’єкта.
У цьому випадку стан закладено в об’єкті StockInfo. Одним із варіантів інформування спостерігача про стан є push-модель, при якій спостережуваний об’єкт передає (іншими словами, «штовхає» — push) дані про свій стан, тобто передає їх у вигляді параметра методу Update().
// Клас Stock, який реалізує інтерфейс IObservable для керування спостерігачами (Observers)
class Stock : IObservable
{
// Інформація про торги (курси валют)
private StockInfo sInfo;
// Список спостерігачів, які слідкують за змінами
private List observers;
// Конструктор класу Stock
public Stock()
{
observers = new List(); // Ініціалізація списку спостерігачів
sInfo = new StockInfo(); // Створення об'єкта для зберігання даних про торги
}
// Метод для реєстрації нового спостерігача
// Параметр: o - об'єкт, який буде доданий до списку спостерігачів
public void RegisterObserver(IObserver o)
{
observers.Add(o);
}
// Метод для видалення спостерігача зі списку
// Параметр: o - об'єкт, який буде видалений зі списку спостерігачів
public void RemoveObserver(IObserver o)
{
observers.Remove(o);
}
// Метод для сповіщення всіх спостерігачів про зміни
public void NotifyObservers()
{
foreach (IObserver o in observers)
{
o.Update(sInfo); // Виклик методу Update у кожного спостерігача
}
}
// Метод для імітації торгів на біржі
public void Market()
{
Random rnd = new Random();
sInfo.USD = rnd.Next(20, 40); // Генерація випадкового курсу долара (від 20 до 40)
sInfo.Euro = rnd.Next(30, 50); // Генерація випадкового курсу євро (від 30 до 50)
NotifyObservers(); // Сповіщення всіх спостерігачів про зміни
}
}
// Клас StockInfo, який містить інформацію про курси валют
class StockInfo
{
// Властивість для зберігання курсу долара США
// Значення: ціле число, що представляє курс
public int USD { get; set; }
// Властивість для зберігання курсу євро
// Значення: ціле число, що представляє курс
public int Euro { get; set; }
}
// Головний метод програми, який демонструє роботу паттерна Спостерігач (Observer)
static void Main(string[] args)
{
// 1. Створення біржі (об'єкта, за яким будуть спостерігати)
Stock stock = new Stock();
// 2. Створення та підписка перших учасників ринку
Bank bank = new Bank("Oщagбанк", stock);
Broker broker = new Broker("Петров", stock);
// 3. Перша імітація торгів - всі підписники отримують сповіщення
stock.Market();
// 4. Брокер Петров припиняє спостереження
broker.StopTrade();
// 5. Друга імітація торгів - тепер лише банк отримує сповіщення
stock.Market();
Console.WriteLine(); // Роздільник у виводі
Console.WriteLine();
// 6. Додавання нових учасників ринку
Bank bank1 = new Bank("ПриватБанк", stock);
Broker broker1 = new Broker("Коротков", stock);
// 7. Третя імітація торгів - тепер сповіщення отримують:
// - Ouagбанк (який залишався підписаним)
// - ПриватБанк (новий)
// - Коротков (новий брокер)
stock.Market();
// 8. Брокер Коротков припиняє спостереження
broker1.StopTrade();
// 9. Четверта імітація торгів - тепер сповіщення отримують:
// - Ouagбанк
// - ПриватБанк
stock.Market();
// Очікування натискання клавіші для завершення програми
Console.ReadKey();
}

При створенні банку та брокера вони автоматично підписуються на сповіщення від біржі через конструктор.
Метод Market() виконує три дії:
Метод StopTrade() виконує:
Нові учасники автоматично підписуються на сповіщення при створенні.
[Біржа Stock]
│
├── [Bank "Oщagбанк"] (завжди підписаний)
├── [Bank "ПриватБанк"] (підписаний пізніше)
└── [Broker "Коротков"] (тимчасово підписаний)
Програма завершується очікуванням натискання клавіші (Console.ReadKey()), що дає змогу побачити всі результати в консолі.