Token导航 LogoToken导航TokenDH.com
效率external-serviceclawhub未标认证来源可访问clear审计通过

bookforge-observer-pattern-implementorBookforge 观察者模式实现者

Agent Skill

bookforge-observer-pattern-implementor 用于补充效率相关能力,适合在 OpenClaw 中需要让 Agent 承接效率相关任务时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

3,563

周安装

147

GitHub Stars

公开资料未说明

下载量

1,164
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:bookforge-observer-pattern-implementor(Bookforge 观察者模式实现者)
来源仓库:https://github.com/quochungto/bookforge-observer-pattern-implementor
安装命令:
openclaw skills install bookforge-observer-pattern-implementor
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install bookforge-observer-pattern-implementor

简介

实现观察者模式建立一对多依赖,自动通知状态变更。

  • 适合事件驱动架构、UI 更新和分布式通知等场景。
  • 输入主题类和观察者接口,输出完整实现代码。
  • 需确认是否修改现有文件,检查维护状态和测试覆盖。
  • 安装命令:openclaw skills install bookforge-observer-pattern-implementor

SKILL.md

name
observer-pattern-implementor
description
|
version
1.0.0
homepage
https://github.com/bookforge-ai/bookforge-skills/tree/main/books/design-patterns-gof/skills/observer-pattern-implementor
metadata
{"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status
draft
depends-on
source-books
title
Design Patterns: Elements of Reusable Object-Oriented Software
authors
["Erich Gamma", "Richard Helm", "Ralph Johnson", "John Vlissides"]
chapters
[5]
pages
[273, 282]
tags
[design-patterns, behavioral, gof, observer, publish-subscribe, event-system, mvc, change-manager, mediator, singleton]
execution
tier
2
mode
full
inputs
description
Existing code where one object's state changes need to propagate to dependents — tightly coupled notification logic, direct method calls between model and views, or a missing event/callback system
tools-required
[TodoWrite, Read, Write]
tools-optional
[Grep, Glob]
mcps-required
[]
environment
Requires a codebase or a sufficiently detailed design description. Code reading tools required to inspect existing structure before refactoring.

Observer Pattern Implementor

When to Use

A data source is directly calling update methods on its consumers, making it impossible to add or remove consumers without modifying the source. Apply this skill when:

  • An abstraction has two separable aspects — a subject (data) and observers (views/reactions) — and tightly coupling them limits reuse of each independently
  • A change to one object requires notifying an unknown or variable number of other objects, and you don't want to hard-code who those are
  • Objects should be able to notify others without making assumptions about who those objects are — you want zero coupling from subject to observer

Do not apply if:

  • Only one consumer will ever exist and that is certain by design (direct call is simpler)
  • Observers trigger further changes that trigger more observers — cascade control is complex; use ChangeManager (Step 9)
  • The language/framework already provides this mechanism natively (e.g., React state, RxJS, Qt signals) — use those instead

Before starting, verify you can answer: What is the subject (the thing that changes)? What are the observers (the things that react)? What state do observers need from the subject after a change?


Process

Step 1: Set Up Tracking and Audit Existing Coupling

ACTION: Use TodoWrite to track progress, then read the codebase to map current subject-observer coupling.

WHY: Observer refactoring touches at least three participants (Subject, ConcreteSubject, ConcreteObserver) and requires consistent interface changes across all of them. Without tracking, it is easy to partially refactor — adding the Observer interface while leaving direct calls in the subject — which produces a hybrid that is worse than either approach. The audit reveals whether existing coupling is simple (one direct call) or complex (multiple consumers with different update logic), which determines whether ChangeManager is needed.

TodoWrite:
- [ ] Step 1: Audit existing coupling — map subjects and consumers
- [ ] Step 2: Define Subject and Observer interfaces
- [ ] Step 3: Implement ConcreteSubject with Attach/Detach/Notify
- [ ] Step 4: Resolve trigger strategy (who calls Notify)
- [ ] Step 5: Ensure state consistency before Notify
- [ ] Step 6: Choose push vs pull model and implement Update
- [ ] Step 7: Apply aspect-based registration if multiple event types
- [ ] Step 8: Evaluate whether ChangeManager is needed
- [ ] Step 9: Implement ChangeManager if warranted
- [ ] Step 10: Wire up and verify — no dangling references

Use Read and Grep to answer:

  • Which class(es) change state and need to broadcast changes? (Subject candidates)
  • Which class(es) react to those changes? (Observer candidates)
  • How many consumers are there? Are they stable or do they change at runtime?
  • Are there multiple types of events, or one general "something changed" notification?

Step 2: Define the Subject and Observer Interfaces

ACTION: Create abstract Subject and Observer base classes (or interfaces). Do not write any concrete logic yet — define only the contracts.

WHY: The power of Observer comes from the subject knowing only the abstract Observer interface — not any concrete observer type. If you skip this and call concrete methods directly, you recreate the coupling you're trying to eliminate. Defining the interfaces first forces you to commit to the notification contract before any implementation details leak in.

Subject interface — three responsibilities:

class Subject {
public:
    virtual ~Subject();
    virtual void Attach(Observer*);   // register a new observer
    virtual void Detach(Observer*);   // deregister an observer
    virtual void Notify();            // broadcast change to all observers
protected:
    Subject();
private:
    List<Observer*> _observers;       // or a hash map — see Step 3
};

Observer interface — one responsibility:

class Observer {
public:
    virtual ~Observer();
    virtual void Update(Subject* theChangedSubject) = 0;
protected:
    Observer();
};

Note: Update receives the subject pointer (not state data). This is the pull model default — observers call back on the subject for what they need. See Step 6 for the push vs pull decision.


Step 3: Implement ConcreteSubject and the Observer List

ACTION: Implement the concrete subject with state, state accessors (GetState/SetState), and a working Notify. Choose the storage strategy for the observer list.

WHY: The naive approach — storing observers directly in every subject instance — works when subjects are few and observers are many per subject. When subjects are many and observers are few (e.g., many small model objects each observed by one dashboard), the per-subject list wastes memory. The hash map trade-off is real: choose based on the ratio of subjects to observers in your system.

Storage decision:

SituationStorageTrade-off
Few subjects, many observers eachList<Observer*> in SubjectSimple; O(1) attach; O(n) notify
Many subjects, few observers eachExternal hash map (subject → observer list)Eliminates per-subject overhead; increases access cost

Standard Subject implementation:

void Subject::Attach(Observer* o) { _observers->Append(o); }
void Subject::Detach(Observer* o) { _observers->Remove(o); }

void Subject::Notify() {
    ListIterator<Observer*> i(_observers);
    for (i.First(); !i.IsDone(); i.Next()) {
        i.CurrentItem()->Update(this);
    }
}

ConcreteSubject adds only state and accessors:

class ConcreteSubject : public Subject {
public:
    SubjectState GetState() const;
    void SetState(SubjectState s);
private:
    SubjectState _subjectState;
};

Step 4: Decide Who Triggers Notify

ACTION: Choose and implement one of two trigger strategies: automatic (subject triggers) or manual (client triggers).

WHY: This is the most common source of Observer bugs. Automatic triggering ensures observers are never out of sync but fires notifications on every state mutation — including intermediate states in a multi-step update. Manual triggering gives the client control to batch multiple mutations before broadcasting, but adds an obligation: every code path that mutates state must remember to call Notify. Forgetting one call produces stale observers that are hard to debug.

Option A — Automatic trigger (subject calls Notify in SetState):

void ConcreteSubject::SetState(SubjectState s) {
    _subjectState = s;
    Notify();              // automatic — client never calls Notify
}

Use when: mutations are typically single-step, or missing a notification is worse than extra notifications.

Option B — Manual trigger (client calls Notify):

// Client code
subject->SetState(s1);
subject->SetBoundary(b);  // multiple mutations
subject->Notify();         // one notification for both changes

Use when: performance matters and multiple state changes should batch into one notification round.

Recommendation: Default to Option A. Switch to Option B only if profiling shows notification overhead is significant, or if the use case explicitly requires batching (e.g., multi-step transaction commit).


Step 5: Ensure Subject State Is Self-Consistent Before Notify

ACTION: Audit every operation that calls Notify (directly or via SetState) and verify that all subject state is fully updated before Notify fires.

WHY: Observers call GetState on the subject inside their Update method to synchronize. If Notify fires while the subject is in a partially-updated state — for example, after a base class operation but before a subclass operation completes — observers will read inconsistent data and enter an inconsistent state of their own. This is a silent bug: observers receive a valid-looking notification but pull stale or partial data.

Common violation in inheritance:

// WRONG — notification fires before subclass state is updated
void MySubject::Operation(int newValue) {
    BaseClassSubject::Operation(newValue);  // triggers Notify here
    _myInstVar += newValue;                 // too late — observers already ran
}

Fix — use Template Method to control notification timing:

// In abstract Subject — Notify is last
void Text::Cut(TextRange r) {
    ReplaceRange(r);   // redefined in subclasses — all state updated here
    Notify();          // fires only after the complete operation
}

Always ensure Notify is the last operation in any state-mutating method. Document which operations trigger notifications in the class interface comment.


Step 6: Choose and Implement the Push or Pull Model

ACTION: Decide whether the subject sends state data to observers (push) or observers query the subject (pull). Implement Update accordingly.

WHY: This is the core design trade-off for observer usability versus coupling. The pull model makes the subject maximally ignorant of its observers (it sends no assumptions about what they need), but can be inefficient — each observer must figure out what changed on its own. The push model is efficient for observers with narrow needs but makes the subject assume it knows what observers care about, reducing reusability when different observers need different data.

Pull model — subject sends only its identity; observers pull what they need:

void DigitalClock::Update(Subject* theChangedSubject) {
    if (theChangedSubject == _subject) {  // guard: verify it's the right subject
        int hour = _subject->GetHour();
        int minute = _subject->GetMinute();
        Draw();  // re-render with pulled values
    }
}

Push model — subject sends relevant state as Update arguments:

// Subject
void Subject::Notify(int newValue) {
    for each observer: observer->Update(this, newValue);
}

// Observer
virtual void Update(Subject*, int newValue) = 0;

Decision:

Prefer pull when...Prefer push when...
Different observers need different parts of subject stateAll observers need the same data
Subject does not know what observers care aboutThe changed data is cheap to pass and expensive to re-query
Maximum observer reusability is importantObserver should not need to hold a reference to the subject

Pull is the default. Push reduces reusability because it encodes assumptions about observer needs into the Subject interface.


Step 7: Apply Aspect-Based Registration for Multiple Event Types

ACTION: If the subject produces multiple distinct event types and observers care about only some, extend Attach to accept an "aspect" (event category) parameter.

WHY: Without aspect filtering, every observer is notified of every change — even changes it does not care about. This wastes cycles and can cause incorrect behavior if an observer's Update logic assumes it is only called for its relevant event. Aspect-based registration lets observers declare their interest at subscription time, so Notify only calls the relevant observers.

Extended interfaces:

// Subject registration with aspect
void Subject::Attach(Observer* o, Aspect& interest);

// Observer Update with aspect parameter
void Observer::Update(Subject* s, Aspect& interest);

Notify implementation with aspect filtering:

void Subject::Notify(Aspect& changedAspect) {
    // only notify observers registered for this aspect
    for each (observer, registeredAspect) in _observerMap:
        if registeredAspect == changedAspect:
            observer->Update(this, changedAspect);
}

Use when: subjects produce heterogeneous events (e.g., a document that can have text changes, layout changes, and selection changes), and observers specialize in only one type.


Step 8: Evaluate Whether ChangeManager Is Needed

ACTION: Assess whether the subject-observer dependency graph is simple or complex. If complex, implement ChangeManager (Step 9). If simple, skip Step 9.

WHY: The standard Notify-loop works well when subjects and observers are independent. It breaks down in two scenarios: (1) a single operation modifies multiple interdependent subjects, causing observers to be notified multiple times for what is logically one change; (2) observers themselves observe multiple subjects, creating diamond-shaped dependency graphs where redundant updates proliferate. ChangeManager exists to handle these cases — it absorbs the complexity so subjects and observers don't have to.

Use ChangeManager if any of the following are true:

  • One high-level operation modifies two or more subjects whose observers overlap
  • Any observer observes more than one subject (creating potential for redundant updates)
  • Notification order matters and must be controlled globally
  • You need a specific update strategy (e.g., "update all after all subjects have changed")

If none of the above apply: ChangeManager adds complexity for no benefit. Keep the simple direct Notify loop from Step 3.


Step 9: Implement ChangeManager (Observer + Mediator + Singleton)

ACTION: If Step 8 determined ChangeManager is needed, implement it as a mediator that owns the subject-observer mapping, controls update strategy, and exists as a singleton.

WHY: ChangeManager combines three patterns with a specific purpose: it acts as a Mediator (centralizing the subject-observer communication logic), uses the Observer protocol (subjects and observers still implement the same interfaces), and is a Singleton (there is exactly one ChangeManager per subsystem — subjects and observers both need to find the same instance). This combination eliminates redundant updates when multiple subjects change in one operation, and allows the update strategy to be changed without touching Subject or Observer classes.

ChangeManager responsibilities:

  1. Maintains the subject-to-observer mapping (replaces per-subject observer lists)
  2. Defines the update strategy — when and in what order observers are notified
  3. Updates all dependent observers when a subject requests it

Two built-in strategies:

StrategyBehaviorUse when
SimpleChangeManagerFor each subject, notify all its observers immediatelyObservers observe only one subject; no redundant update risk
DAGChangeManagerMark all affected observers; update each marked observer exactly onceObservers observe multiple subjects (directed acyclic graph dependencies)

ChangeManager interface:

class ChangeManager {
public:
    static ChangeManager* Instance();   // Singleton access
    virtual void Register(Subject*, Observer*);
    virtual void Unregister(Subject*, Observer*);
    virtual void Notify() = 0;          // strategy-defined update dispatch
protected:
    ChangeManager();
private:
    static ChangeManager* _instance;
};

Subject delegates to ChangeManager:

// Subject no longer stores observer list — delegates to ChangeManager
void Subject::Attach(Observer* o) {
    ChangeManager::Instance()->Register(this, o);
}
void Subject::Notify() {
    ChangeManager::Instance()->Notify();
}

DAGChangeManager Notify — prevents redundant updates:

void DAGChangeManager::Notify() {
    // Pass 1: mark all observers that need updating
    for each subject s in _subjects:
        for each observer o of s:
            mark o as pending

    // Pass 2: update each marked observer exactly once
    for each marked observer o:
        o->Update(relevantSubject);
        unmark o;
}

Step 10: Wire Up and Prevent Dangling References

ACTION: Wire all observers to their subjects via Attach. Add lifecycle management to prevent dangling references when subjects are deleted. Verify the full notification chain manually or with a test.

WHY: Deleting a subject that observers still hold a reference to produces dangling pointers — observers will attempt to call GetState on freed memory. Simply deleting the observers is not a valid solution because they may be observing other subjects or be referenced by other objects. The subject must notify observers of its own deletion so they can nullify their references.

Deletion protocol in subject destructor:

Subject::~Subject() {
    // Notify observers that this subject is going away
    // so they can reset their stored reference
    ListIterator<Observer*> i(_observers);
    for (i.First(); !i.IsDone(); i.Next()) {
        i.CurrentItem()->SubjectDeleted(this);
    }
}

// Observer handles deletion
void ConcreteObserver::SubjectDeleted(Subject* s) {
    if (s == _subject) {
        _subject = nullptr;  // nullify — do not delete
    }
}

Wiring example:

ClockTimer* timer = new ClockTimer;
DigitalClock* digitalClock = new DigitalClock(timer);  // Attach in constructor
AnalogClock* analogClock = new AnalogClock(timer);     // Attach in constructor
// When timer ticks, both clocks update automatically

Verification checklist:

  • [ ] Attach is called before any state changes that should be observed
  • [ ] Detach is called in every observer destructor
  • [ ] Deleting a subject does not crash observers
  • [ ] Adding a new observer at runtime receives subsequent updates correctly
  • [ ] Notifications fire only when subject state is fully consistent (Step 5)

Pitfalls

1. Cascade updates. Observer A's Update changes subject B, which notifies observer C, which changes subject D... This is the most serious Observer failure mode, and it is difficult to detect because it appears during runtime. Prevention: avoid state changes inside Update that would notify other observers. If cascade is unavoidable, use DAGChangeManager to ensure each observer updates exactly once per logical change.

2. Dangling references to deleted subjects. Deleting a subject without notifying observers leaves them with stale pointers. The crash appears on the next notification cycle, not at the point of deletion, making it hard to locate. Prevention: always implement the deletion-notification protocol in Step 10.

3. Inconsistent state notification. Calling Notify before all subject state is updated (typically in inherited operations — see Step 5). Observers run during Update and pull stale data. Prevention: always use Template Method to put Notify last, and audit inheritance chains for calls to super.Operation() that trigger notifications mid-update.

4. Trigger ambiguity. Mixing automatic and manual trigger strategies across different subjects in the same system. Developers expect one behavior and get the other; observers are sometimes notified twice, sometimes not at all. Prevention: pick one strategy and apply it uniformly across the system. Document it in the Subject class comment.

5. Push overspecification. The push model embeds assumptions about what observers need directly into the Subject's Notify signature. When observer needs diverge (one needs the full new state, another only needs a delta, a third only needs to know that *something* changed), the push signature becomes a compromise that serves none of them well. Prevention: default to pull. Only add push parameters that are universally needed by all observers, and only after profiling shows that the extra pull calls are a measurable cost.


Examples

Example 1: Spreadsheet Model with Multiple Chart Views

Scenario: A spreadsheet has a data model that three chart widgets (bar chart, pie chart, line chart) read directly. Currently the model calls barChart.refresh(), pieChart.refresh(), and lineChart.refresh() in its SetCell method. Adding a fourth chart requires modifying the model.

Trigger: "Every new chart type requires touching the spreadsheet model."

Process:

  • Step 1: Audit reveals SpreadsheetModel directly references three concrete chart classes
  • Step 2: SpreadsheetModel becomes ConcreteSubject; each chart implements Observer
  • Step 3: List<Observer*> in Subject (few subjects, multiple observers)
  • Step 4: Automatic trigger — SetCell calls Notify() after updating data
  • Step 5: Notify is last in SetCell — state is fully updated first
  • Step 6: Pull model — charts call GetCellValue(row, col) in their Update
  • Step 8: No complex dependencies — skip ChangeManager

Output: SpreadsheetModel has no reference to any chart class. New charts attach themselves; model never changes.


Example 2: Clock with Multiple Display Observers

Scenario: A ClockTimer (concrete subject) notifies time displays. A DigitalClock and AnalogClock both observe the same timer. This is the canonical sample from the GoF text.

Trigger: "Both clock displays must always show the same time, but neither should know about the other."

Key implementation details:

  • DigitalClock inherits from both Widget (UI toolkit) and Observer (combining concern 10 — Subject/Observer in one class hierarchy via multiple inheritance)
  • Constructor calls _subject->Attach(this); destructor calls _subject->Detach(this) — full lifecycle management
  • Update guards on subject identity: if (theChangedSubject == _subject) — handles potential future case of observing multiple subjects
void DigitalClock::Update(Subject* theChangedSubject) {
    if (theChangedSubject == _subject) {
        Draw();
    }
}

void DigitalClock::Draw() {
    int hour   = _subject->GetHour();
    int minute = _subject->GetMinute();
    // render digital display
}

Output: ClockTimer::Tick() calls Notify(); both displays update independently without knowing each other exist.


Example 3: Multi-Subject Dashboard with ChangeManager

Scenario: A financial dashboard has three data subjects: PriceSubject, VolumeSubject, and PositionSubject. A RiskSummaryObserver observes all three. When a trade executes, all three subjects change in one operation. Without ChangeManager, RiskSummaryObserver.Update fires three times for one logical event.

Trigger: "Our risk view recalculates three times per trade because it observes three subjects."

Process:

  • Step 8: Observer observes multiple subjects — DAG dependency. ChangeManager required.
  • Step 9: Implement DAGChangeManager. Each subject calls ChangeManager::Instance()->Notify(). DAGChangeManager marks RiskSummaryObserver when PriceSubject changes, marks it again when VolumeSubject changes (already marked — no-op), then updates it once.

Output: One trade → three subject changes → RiskSummaryObserver.Update called exactly once.


Reference Files

FileContents
references/observer-implementation-guide.mdAll 10 implementation concerns in full detail, ChangeManager class diagrams, push/pull protocol variants, aspect-based registration patterns, and language-specific notes

License

This skill is licensed under CC-BY-SA-4.0. Source: BookForge — Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.

Related BookForge Skills

Install related skills from ClawhHub:

  • clawhub install bookforge-design-pattern-selector
  • clawhub install bookforge-behavioral-pattern-selector

Or install the full book set from GitHub: bookforge-skills

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

71.42%
按下载量换算831

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

通过

权限和风险

external-service

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills