<-Back

Singleton + Observer у прикладі «Двері та тварини» (C#)

Об’єкт спостереження: Door (двері в будинку). Спостерігачі: Dog (собака), Cat (кіт), Parrot (папуга). Двері мають три стани: «відчинені», «зачинені і тиша», «зачинені і хтось стукає».

Мета: показати, як двері у ролі Subject єдиного екземпляра (Singleton) сповіщають тварин-спостерігачів (Observer) про зміну стану.

1) Архітектура

Учасники

  • Door — Singleton + Subject. Зберігає список спостерігачів і сповіщає їх методом Notify().
  • IObserver — інтерфейс спостерігача з методом Update(state, door).
  • Dog, Cat, Parrot — конкретні спостерігачі, реагують на стани дверей; на «відчинені» — відписуються.
  • Program — тестувальник: підписує тварин і змінює стани дверей.

Стан дверей та реакції

Стан DoorDogCatParrot
зачинені і тиша «Я собака, яка чекає гостей.» «Я кіт, який нікого не чекає.» «Сиджу за ґратами в клітці дурній...»
зачинені і хтось стукає «Гав, гав, гав. Не бійтеся, я не кусаю гостей.» «Мяу, мяу, мяу. Сподіваюся, ви не до мене прийшли?» «Свободу папугам!»
відчинені «Гав, гав, гав. Заходьте, двері відкриті.» (відписується) «Мяу, мяу, мяу. Якщо вже приперлися — то принесли рибку?» (відписується) «Вилітаю на Гаїті!» (відписується)

2) Повний код C# із коментарями


// <summary>
// Демонстрація шаблонів Singleton (Door) і Observer (Dog, Cat, Parrot).
// Двері можуть бути в станах: "відчинені", "зачинені і тиша", "зачинені і хтось стукає".
// Зміна стану викликає сповіщення всіх підписаних спостерігачів.
// </summary>

using System;
using System.Collections.Generic;

namespace DoorObserverDemo
{
    /// <summary>
    /// Інтерфейс спостерігача. Будь-який спостерігач має вміти реагувати на зміну стану дверей.
    /// </summary>
    public interface IObserver
    {
        void Update(string doorState, Door door);
    }

    /// <summary>
    /// Door — об'єкт спостереження (Subject) і Singleton (єдиний екземпляр).
    /// </summary>
    public class Door
    {
        // Єдиний екземпляр створюється ліниво або завчасно. Тут — завчасно (eager singleton).
        private static readonly Door instance = new Door();

        // Колекція підписаних спостерігачів
        private readonly List<IObserver> observers = new List<IObserver>();

        // Поточний стан дверей
        private string state = "зачинені і тиша";

        // Приватний конструктор забороняє new Door() ззовні
        private Door() { }

        /// <summary>Публічний доступ до єдиного екземпляра Door.</summary>
        public static Door Instance => instance;

        /// <summary>Підписати спостерігача.</summary>
        public void Attach(IObserver observer)
        {
            if (observer != null && !observers.Contains(observer))
                observers.Add(observer);
        }

        /// <summary>Відписати спостерігача.</summary>
        public void Detach(IObserver observer)
        {
            if (observer != null)
                observers.Remove(observer);
        }

        /// <summary>
        /// Змінити стан дверей і сповістити всіх спостерігачів.
        /// </summary>
        public void ChangeDoor(string newState)
        {
            state = newState;
            Console.WriteLine($"\n=== Двері тепер у стані: {state} ===");
            Notify();
        }

        /// <summary>Сповістити всіх підписаних спостерігачів.</summary>
        private void Notify()
        {
            // Копія списку, щоб дозволити відписку під час ітерації (без винятків)
            foreach (var observer in new List<IObserver>(observers))
            {
                observer.Update(state, this);
            }
        }
    }

    /// <summary>Конкретний спостерігач — Собака.</summary>
    public class Dog : IObserver
    {
        public void Update(string doorState, Door door)
        {
            if (doorState == "зачинені і хтось стукає")
            {
                TukTuk();
            }
            else if (doorState == "зачинені і тиша")
            {
                Vois();
            }
            else if (doorState == "відчинені")
            {
                DoorOpen();
                door.Detach(this); // Відписується
            }
        }

        private void TukTuk()
        {
            Console.WriteLine("Собака: Гав, гав, гав. Не бійтеся, я не кусаю гостей.");
        }

        private void Vois()
        {
            Console.WriteLine("Собака: Я собака, яка чекає гостей.");
        }

        private void DoorOpen()
        {
            Console.WriteLine("Собака: Гав, гав, гав. Заходьте, двері відкриті.");
        }
    }

    /// <summary>Конкретний спостерігач — Кіт.</summary>
    public class Cat : IObserver
    {
        public void Update(string doorState, Door door)
        {
            if (doorState == "зачинені і хтось стукає")
            {
                TukTuk();
            }
            else if (doorState == "зачинені і тиша")
            {
                Vois();
            }
            else if (doorState == "відчинені")
            {
                DoorOpen();
                door.Detach(this); // Відписується
            }
        }

        private void TukTuk()
        {
            Console.WriteLine("Кіт: Мяу, мяу, мяу. Сподіваюся, ви не до мене прийшли?");
        }

        private void Vois()
        {
            Console.WriteLine("Кіт: Мяу, мяу, мяу. Я кіт, який нікого не чекає.");
        }

        private void DoorOpen()
        {
            Console.WriteLine("Кіт: Мяу, мяу, мяу. Якщо вже приперлися — то принесли рибку?");
        }
    }

    /// <summary>Конкретний спостерігач — Папуга.</summary>
    public class Parrot : IObserver
    {
        public void Update(string doorState, Door door)
        {
            if (doorState == "зачинені і хтось стукає")
            {
                TukTuk();
            }
            else if (doorState == "зачинені і тиша")
            {
                Vois();
            }
            else if (doorState == "відчинені")
            {
                DoorOpen();
                door.Detach(this); // Відписується
            }
        }

        private void TukTuk()
        {
            Console.WriteLine("Папуга: Свободу папугам!");
        }

        private void Vois()
        {
            Console.WriteLine("Папуга: Сиджу за ґратами в клітці дурній...");
        }

        private void DoorOpen()
        {
            Console.WriteLine("Папуга: Вилітаю на Гаїті!");
        }
    }

    /// <summary>Тестувальник — Program.</summary>
    class Program
    {
        static void Main(string[] args)
        {
            Door door = Door.Instance;

            Dog dog = new Dog();
            Cat cat = new Cat();
            Parrot parrot = new Parrot();

            // Підписуємо всіх тварин
            door.Attach(dog);
            door.Attach(cat);
            door.Attach(parrot);

            // Зміна станів дверей
            door.ChangeDoor("зачинені і тиша");
            door.ChangeDoor("зачинені і хтось стукає");
            door.ChangeDoor("відчинені");

            Console.WriteLine("\nДемонстрація завершена.");
        }
    }
}
      

3) Як запустити

.NET SDK (CLI)

  1. Встановіть .NET SDK.
  2. У терміналі:
    dotnet new console -n DoorObserverDemo
    cd DoorObserverDemo
    # замініть Program.cs вмістом зі вставки вище
    dotnet run

IDE (Visual Studio / Rider / VS Code)

  • Створіть консольний проєкт C#.
  • Додайте класи в один файл Program.cs або розділіть за файлами.
  • Запустіть (F5 / Run).

4) Ключові моменти та варіації