Blackboard Pattern

Коллективное решение сложных задач

Идея паттерна

Blackboard — архитектурный паттерн, где несколько независимых «знаний» (knowledge sources) совместно решают задачу, взаимодействуя через общее хранилище — «доску» (blackboard), а «контроллер» управляет итерациями.

Компоненты

  • Blackboard: общее состояние/рабочее пространство
  • Knowledge Sources: независимые модули знаний/эвристик
  • Controller: оркестрация шагов и остановки
┌──────────────┐     triggers     ┌──────────────────┐
│ Knowledge A  │ ───────────────▶ │                  │
└──────────────┘                  │                  │
┌──────────────┐   reads/writes   │    BLACKBOARD    │
│ Knowledge B  │ ◀──────────────▶ │   (Shared State) │
└──────────────┘                  │                  │
┌──────────────┐     triggers     │                  │
│ Knowledge C  │ ───────────────▶ └──────────────────┘
└──────────────┘                        ▲
                                        │ control
                                   ┌──────────────┐
                                   │  Controller  │
                                   └──────────────┘

Когда применять

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

Плюсы/минусы

  • ✅ Гибкое добавление новых источников знаний
  • ✅ Инкрементальное улучшение решения
  • ❌ Сложная координация и отладка
  • ❌ Требует аккуратного проектирования общей модели данных

Мини-пример (Python)

from dataclasses import dataclass, field
from typing import Any, Dict, List, Protocol

@dataclass
class Blackboard:
    state: Dict[str, Any] = field(default_factory=dict)
    log: List[str] = field(default_factory=list)

class KnowledgeSource(Protocol):
    def applicable(self, bb: Blackboard) -> bool: ...
    def apply(self, bb: Blackboard) -> None: ...

class Tokenize(KnowledgeSource):
    def applicable(self, bb):
        return 'text' in bb.state and 'tokens' not in bb.state
    def apply(self, bb):
        bb.state['tokens'] = bb.state['text'].split()
        bb.log.append('Tokenize')

class Lower(KnowledgeSource):
    def applicable(self, bb):
        return 'tokens' in bb.state and 'lower' not in bb.state
    def apply(self, bb):
        bb.state['lower'] = [t.lower() for t in bb.state['tokens']]
        bb.log.append('Lower')

class Unique(KnowledgeSource):
    def applicable(self, bb):
        return 'lower' in bb.state and 'unique' not in bb.state
    def apply(self, bb):
        bb.state['unique'] = sorted(set(bb.state['lower']))
        bb.log.append('Unique')

class Controller:
    def __init__(self, knowledge: List[KnowledgeSource]):
        self.knowledge = knowledge
    def run(self, bb: Blackboard, max_iters: int = 100):
        for _ in range(max_iters):
            fired = False
            for ks in self.knowledge:
                if ks.applicable(bb):
                    ks.apply(bb)
                    fired = True
            if not fired:
                break

bb = Blackboard(state={'text': 'Hello World Hello'})
Controller([Tokenize(), Lower(), Unique()]).run(bb)
print(bb.state['unique'])  # ['hello', 'world']
print(bb.log)              # ['Tokenize','Lower','Unique']

Практические советы

  • Определите формат данных на «доске» и инварианты
  • Делайте источники знаний независимыми и идемпотентными
  • Контроллер должен уметь определять критерии остановки
  • Логируйте срабатывания и конфликты