毎朝のルーティンをObserverパターンにしてみた話

  1. ルーティンの“凝集度”に疑問を感じたある朝
      1. プログラマの朝は早い…わけではない
      2. Observerパターンとは何か、なぜ朝に必要なのか?
      3. 「変化に強い朝」への第一歩
  2. Observerパターンで朝を設計してみた
    1. 目覚ましは「Subject」、習慣は「Observer」
    2. インターフェース設計:IMorningObserverとIMorningSubject
    3. 主体となるクラス:AlarmClock(被通知者)
    4. 朝の習慣たち(Observer群)
      1. ストレッチクラス
      2. 歯磨きクラス
      3. 白湯ルーティン(健康志向版)
    5. メイン処理(朝の開始)
    6. ライフスタイルのDI化?IoCコンテナの導入まで考えた話
    7. 朝をコードで語れるという快感
  3. Observerパターン、生活の中でデッドロックする
    1. 朝は非同期イベントの塊だった
    2. 事件発生:例外で朝が止まる
    3. 対策:Try/Catch で Observer を隔離する
    4. タスクの依存関係問題:朝食が早すぎる
      1. 朝食前に歯磨きしてしまう事故
    5. 解決策①:ObserverにPriorityを導入
    6. 解決策②:状態遷移(State)を意識した制御
    7. 非同期朝:async/await で複数タスクを同時に開始
    8. 「通知」と「リクエスト」は別である
    9. 朝における仕様変更とバージョニング問題
    10. 挫折しかけた朝
  4. Observerパターンが導いた「習慣という名のアーキテクチャ」
    1. Observerパターンは「自分との契約」だった
    2. 最終形:モジュール化された朝
    3. 習慣とは「再利用可能な自己」の集合である
    4. バグのない朝はない。でも例外処理はできる
    5. 習慣化とは、通知ではなく「構成」の問題
    6. この発想は、仕事にも応用された
    7. 最後に:朝が「通知」であるということ

ルーティンの“凝集度”に疑問を感じたある朝

プログラマの朝は早い…わけではない

目覚ましが鳴る。時計の針は6:30を指している。
この時点で、私の身体はまだ布団の重力に逆らう気配を見せていない。右手が無意識にスマートフォンを探し、スヌーズを設定しようとする。だが、その瞬間、私はふと考えた。

「この一連の動作、全部1つのクラスに詰め込まれてるな」

脳内でソースコードが展開される。AlarmClock クラスが WakeUp() メソッドを呼び出し、その中で Stretch()BrushTeeth()MakeCoffee()…と朝のイベントが逐次直列に書かれている。すべてが、巨大な MainMorningRoutine() メソッドに集約されていた。

なにより気持ち悪かったのは、その朝のコードが「変更に弱い」ことだった。例えば、「今日はコーヒーの代わりに白湯を飲む」とか、「歯を磨く前にスクワットをする」なんて柔軟なロジックを差し込もうとするたびに、MainMorningRoutine() に if 文が増える。

「これ、レガシーコードの入り口だな…」

脳内に「凝集度」「結合度」という文字がちらつく。朝から設計の匂いがする。完全に職業病だ。
そして、私は閃いた。

「Observer パターン、使えるんじゃないか?」


Observerパターンとは何か、なぜ朝に必要なのか?

まず、ざっくりおさらいしておこう。Observer パターンは「あるオブジェクトの状態が変化したときに、依存する他のオブジェクトに通知する」デザインパターンだ。Subject が中心にいて、変化が起きるたびに登録された Observer にイベントを通知する。

では、これを朝にどう適用するのか?

  • Subject = 目覚まし時計(WakeUpEvent)
  • Observers = その後に行われる一連のルーティン(Stretch、BrushTeeth、MakeCoffee など)

このパターンを使えば、あるイベントが発火したときに、自動的に連動して複数のアクションが行われる。それぞれのアクションは独立したモジュール(クラス)として存在できるし、必要なものだけを購読(Subscribe)すればいい。

「朝のルーティンをモジュール化しよう。そうすれば、週末や出張の日も簡単に差し替えられる」

私は意気揚々と Visual Studio を開き、MorningRoutine ソリューションを新規作成した。
朝のルーティンを、Observerパターンで再設計する一日の始まりだった。


「変化に強い朝」への第一歩

この構想は、単なるコード実験にとどまらない。
Observer パターンの根底には「関心の分離(Separation of Concerns)」という思想がある。これは、私たちの生活や思考そのものにも応用可能だと私は思っている。

朝、何をするかをあらかじめ決めておくのではなく、「起きた」ことをトリガーにして、必要なアクションだけを呼び出す。これが、Observerパターンを日常に持ち込む真髄だ。

この仕組みによって、以下のような変化が期待できる:

  • 朝の流れを柔軟にカスタマイズできる
  • 日によって行動を変えられる(週末だけ特殊なObserverを登録する)
  • 新しい習慣をコードのように追加・削除できる

これはプログラマにとって、日常をデザインパターンで制御するというロマンでもあった。

Observerパターンで朝を設計してみた

目覚ましは「Subject」、習慣は「Observer」

まず、Observerパターンを生活に持ち込むにあたって大前提としたのが、「目覚まし(アラーム)」が1つのトリガーであり、それを契機としてさまざまな行動(ストレッチ、歯磨き、瞑想、朝食など)が起こる、というモデルです。

生活の中に「イベント駆動」を持ち込む――。そう、朝はデリゲートで始まるのです。


インターフェース設計:IMorningObserverとIMorningSubject

最初に登場するのは、Observerパターンの典型的なインターフェース構成です。

// Observerインターフェース
public interface IMorningObserver
{
void OnWakeUp();
}

// Subjectインターフェース
public interface IMorningSubject
{
void Register(IMorningObserver observer);
void Unregister(IMorningObserver observer);
void NotifyObservers();
}

この設計で重要なのは、Observer(朝の各タスク)がSubject(目覚まし)の存在に依存せずに動作できることです。
つまり、「何時に起きるか」や「アラームの種類」は各Observerには関係ありません。


主体となるクラス:AlarmClock(被通知者)

次に、実際のSubjectを実装します。

public class AlarmClock : IMorningSubject
{
private readonly List<IMorningObserver> _observers = new();

public void Register(IMorningObserver observer)
{
_observers.Add(observer);
}

public void Unregister(IMorningObserver observer)
{
_observers.Remove(observer);
}

public void NotifyObservers()
{
Console.WriteLine("[AlarmClock] 朝が来た。通知を開始する!");
foreach (var observer in _observers)
{
observer.OnWakeUp();
}
}

public void Ring()
{
Console.WriteLine("[AlarmClock] ピピピピ… 6:30になりました!");
NotifyObservers();
}
}

ここでのRing()が目覚ましの鳴動です。NotifyObservers()によって登録されたタスクに一斉通知が飛びます。


朝の習慣たち(Observer群)

次は、朝の各ルーティンをObserverとして実装していきます。

ストレッチクラス

public class StretchRoutine : IMorningObserver
{
public void OnWakeUp()
{
Console.WriteLine("→ ストレッチ開始。肩をぐるぐる、背筋をぐーっと。");
}
}

歯磨きクラス

public class BrushTeethRoutine : IMorningObserver
{
public void OnWakeUp()
{
Console.WriteLine("→ 洗面所へ直行。歯をシャカシャカ。");
}
}

白湯ルーティン(健康志向版)

public class HotWaterRoutine : IMorningObserver
{
public void OnWakeUp()
{
Console.WriteLine("→ 白湯をコップ1杯。内臓が喜ぶ。");
}
}

このように、Observerとしての朝のルーチンは1つ1つが独立しており、必要に応じて登録・解除が可能です。


メイン処理(朝の開始)

Observerを登録し、朝のトリガーを発動してみます。

public static class MorningApp
{
public static void Main()
{
AlarmClock alarm = new AlarmClock();

// ルーティンの登録
alarm.Register(new StretchRoutine());
alarm.Register(new BrushTeethRoutine());
alarm.Register(new HotWaterRoutine());

// 起床イベントを発火
alarm.Ring();

// Console:
// [AlarmClock] ピピピピ… 6:30になりました!
// [AlarmClock] 朝が来た。通知を開始する!
// → ストレッチ開始。肩をぐるぐる、背筋をぐーっと。
// → 洗面所へ直行。歯をシャカシャカ。
// → 白湯をコップ1杯。内臓が喜ぶ。
}
}

このシンプルな構造で、朝のルーティンは次のように進化します:

  • 新しい習慣の追加がたった1行(Register())で完了
  • 一時的にやめたいルーチンも Unregister() するだけ
  • 順序が厳密でないルーチンは独立していて影響を与えない
  • 毎朝の構成を環境変数・設定ファイルで柔軟に切り替え可能

ライフスタイルのDI化?IoCコンテナの導入まで考えた話

ここまで来ると、「もはやこの構造、IoCコンテナで依存性注入したいな」と思うのがプログラマの性。

// たとえば、週末だけ実行されるObserver
if (DateTime.Today.DayOfWeek == DayOfWeek.Sunday)
{
alarm.Register(new MeditationRoutine());
}

というように、曜日や季節、体調などの条件に応じてObserverの構成を切り替える、つまり「朝の構成ファイル化」「DIコンテナによる注入」「ルーティンのA/Bテスト」すら可能になります。

Observer パターンが持つ「構成の柔軟さ」は、プログラマの生活設計にも非常にフィットするのです。


朝をコードで語れるという快感

ここで強く感じたのは、「生活習慣をコードで設計・制御できる」という驚きと快感です。
Observerパターンを実生活に応用することで、習慣という曖昧で時に惰性的なものを、再現性と柔軟性のある構造体として捉えられるようになりました。

コードのように「どのタスクがいつ実行され、どれをスキップできるか」が明確になると、朝のストレスが減り、意思決定のコストも下がるのです。

次章「転」では、このObserver設計が予期せぬ問題や混乱、バグ(!)を引き起こした話を赤裸々に語ります。

Observerパターン、生活の中でデッドロックする

朝は非同期イベントの塊だった

Observerパターンで“通知駆動の朝”が実現したと喜んだのも束の間。
現実世界は完全な同期環境ではありません。

例えば:

  • ストレッチ中にスマホ通知が来て中断される
  • 歯磨きの途中で宅配が来る
  • 白湯を沸かし忘れてタスクが例外終了

そう、現実世界はマルチスレッドだったのです。
そして、Observerたちは順序制御や例外処理を全くしていなかった


事件発生:例外で朝が止まる

ある冬の朝、白湯のルーチンが次のような状態になりました。

public class HotWaterRoutine : IMorningObserver
{
public void OnWakeUp()
{
if (!Kettle.IsPluggedIn())
{
throw new InvalidOperationException("ケトルが接続されていません!");
}

Console.WriteLine("→ 白湯をコップ1杯。内臓が喜ぶ。");
}
}

これがアラーム通知の中で実行されると…

public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.OnWakeUp(); // ← ここで例外が発生して後続処理が止まる!
}
}

全タスクが停止し、朝の始動に失敗したのです。
私はズボンすら履かず、うなだれてベッドに戻りました。


対策:Try/Catch で Observer を隔離する

それ以降、すべてのObserver呼び出しに try/catch を入れ、1つのタスクの失敗が全体を止めない構造に変更しました。

public void NotifyObservers()
{
foreach (var observer in _observers)
{
try
{
observer.OnWakeUp();
}
catch (Exception ex)
{
Console.WriteLine($"[Error] {observer.GetType().Name} が失敗: {ex.Message}");
}
}
}

結果:

  • 白湯がなくても、ストレッチは続行
  • 歯磨きも正常に開始される
  • エラーはログに残る

まるでアプリケーションの部分障害に強いマイクロサービス構成のようになりました。


タスクの依存関係問題:朝食が早すぎる

別の問題として、「朝食のタスクが他のタスクより早く実行されてしまう」という依存順序問題が発生。

Observerは通知順に動作するが、並列にした場合など、依存関係を無視した順序で動く危険性があります。

朝食前に歯磨きしてしまう事故

// 登録順ミス
alarm.Register(new BrushTeethRoutine());
alarm.Register(new BreakfastRoutine());

→ 結果:

→ 歯をシャカシャカ。
→ 朝食をもぐもぐ。

これは事故である。


解決策①:ObserverにPriorityを導入

public interface IPrioritizedObserver : IMorningObserver
{
int Priority { get; }
}

AlarmClock側で優先度順にソートすることで、実行順序の保証を実現:

public void NotifyObservers()
{
var sorted = _observers.OfType<IPrioritizedObserver>()
.OrderBy(o => o.Priority);

foreach (var observer in sorted)
{
try
{
observer.OnWakeUp();
}
catch (Exception ex)
{
Console.WriteLine($"[Error] {observer.GetType().Name} が失敗: {ex.Message}");
}
}
}

解決策②:状態遷移(State)を意識した制御

Observerは通知されるだけの受動的な存在であり、その前提条件(前の処理の完了など)を知らない
そこで、「朝の状態」を持つクラス MorningContext を用意:

public class MorningContext
{
public bool HasBrushedTeeth { get; set; }
public bool HasEaten { get; set; }
public bool HasStretched { get; set; }
}

Observerはこのコンテキストを参照して判断:

public class BreakfastRoutine : IMorningObserver
{
private readonly MorningContext _context;

public BreakfastRoutine(MorningContext context)
{
_context = context;
}

public void OnWakeUp()
{
if (!_context.HasBrushedTeeth)
{
Console.WriteLine("→ 朝食はまだ。歯を磨いてからにしよう。");
return;
}

Console.WriteLine("→ 朝食をもぐもぐ。");
_context.HasEaten = true;
}
}

非同期朝:async/await で複数タスクを同時に開始

さらに、「白湯を沸かしている間にストレッチをする」といった並列的朝活も導入したくなり、次のような非同期通知も考えました。

public interface IAsyncMorningObserver
{
Task OnWakeUpAsync();
}

この発想は、Rx(Reactive Extensions)やUniRx的な設計に繋がります。


「通知」と「リクエスト」は別である

最終的な気づきとして重要だったのは:

Observerパターンは「命令」ではなく「通知」である。
実行するかどうか、どう処理するかはObserverが決める。

つまり、Observerに「歯を磨け」と命じているのではなく、「朝になった」という情報をイベントとして渡しているだけ

この気づきは、ルールベースの生活からイベントベースの生活への転換でもありました。


朝における仕様変更とバージョニング問題

Observerパターンは柔軟な一方で、以下のような問題も表面化します。

  • あるObserverが仕様変更されると他に副作用が出る(コンテキスト依存)
  • Observerの構成順や内容を朝ごとに変更したくなる(「平日モード」「週末モード」)
  • 「条件によって分岐するObserver」の設計が複雑になる

これはつまり、生活がバージョニングと分岐に悩む大規模システム化してきた証です。


挫折しかけた朝

一時期、「Observerパターンの導入で、朝がかえって複雑になった」と悩みました。
設計の見直し、状態管理、非同期処理、テストのしづらさ――。

でも、それはまさにソフトウェア開発で感じる問題そのものだったのです。

Observerパターンが導いた「習慣という名のアーキテクチャ」

Observerパターンは「自分との契約」だった

最初は、単に「通知されて動くモジュール」を生活に適用しようとしていただけでした。
しかし試行錯誤を経て気づいたのは――Observerパターンとは、自分自身との責任分離・契約の体系だったということです。

  • 朝が来た(=通知)
  • それにどう反応するか(=Observerの責務)
  • 条件によってスキップも例外も許す(=柔軟性)

これはまるで、イベント駆動アーキテクチャそのもの。
**自分の中に設けた「ルールセット」ではなく、「契約とイベントフロー」で動く自己」**を再設計したのです。


最終形:モジュール化された朝

私が辿り着いたObserverベースの朝ルーチンは、次のような構成になりました。

// アラーム(Subject)
AlarmClock alarm = new AlarmClock();

// コンテキスト(状態保持)
MorningContext context = new MorningContext();

// Observerたち(各ルーティン)
alarm.Register(new WakeUpRoutine(context));
alarm.Register(new OpenCurtainsRoutine(context));
alarm.Register(new MakeHotWaterRoutine(context));
alarm.Register(new StretchingRoutine(context));
alarm.Register(new BrushTeethRoutine(context));
alarm.Register(new BreakfastRoutine(context));
alarm.Register(new MeditationRoutine(context));

// 朝が来た!
alarm.NotifyObservers();

それぞれのRoutineは独立した小さなクラスとして定義され、
状態(コンテキスト)に応じて自己判断で行動します。

public class BrushTeethRoutine : IMorningObserver
{
private MorningContext _context;

public BrushTeethRoutine(MorningContext context)
{
_context = context;
}

public void OnWakeUp()
{
if (_context.HasDrunkHotWater)
{
Console.WriteLine("→ 歯をシャカシャカ。さっぱり!");
_context.HasBrushedTeeth = true;
}
else
{
Console.WriteLine("→ まだ白湯を飲んでいない。保留。");
}
}
}

つまり、Observer=条件付き自己実行の小さな意思決定ユニットと定義できるのです。


習慣とは「再利用可能な自己」の集合である

この取り組みを通じて得た最大の学びは、
Observerパターンで生活を設計するということは、自己を構成する部品群の再定義に等しいということでした。

  • 「朝食を摂る自分」
  • 「ストレッチをする自分」
  • 「瞑想する自分」
  • 「失敗したときにリカバリする自分」

それぞれが明確なインターフェースと責務を持ち、
朝というトリガーに反応して動くのです。

まさに「再利用可能な自己」=習慣という名のソフトウェア構成でした。


バグのない朝はない。でも例外処理はできる

Observerパターンのように生活を構成しても、もちろん完璧な朝が毎日実現できるわけではありません。

  • 予定外の予定が入る(Observer未登録イベント)
  • タスクをスキップしたくなる(動的デタッチ)
  • タスクの優先度が変化する(Context変更)

でも、そのたびに:

  • try/catch で失敗をログし、
  • Context を介して他タスクに通知し、
  • 必要なら Dispose して次の日に再接続。

そう、私の朝は**「回復可能なアーキテクチャ」**に進化したのです。


習慣化とは、通知ではなく「構成」の問題

私がこの設計でようやく気づいたことがあります。
**朝の習慣化において最も大切なのは「意志」ではなく「構造」**であると。

  • 意志で起きるのではなく、
  • 起きるように組まれている構造の中に自分を置く。

Observerパターンはそのための優れた枠組みであり、
再現性・拡張性・依存制御を備えた生活設計が可能になります。


この発想は、仕事にも応用された

この経験は、私の開発者としての姿勢にも影響を与えました。

  • チーム内の連携をイベント駆動で見直すようになった
  • Slack通知やタスク自動化をObserver的に扱うようになった
  • 生活とコードの境界をデザインパターンで橋渡しできると感じた

「Observerパターンを生活に応用する」ことが、
逆にソフトウェア開発における“人間性”の理解を深めることに繋がったのです。


最後に:朝が「通知」であるということ

1日の始まりとは、人間のメインループへの最初のイベント通知です。

そして、それをどう受け取り、どう反応するかは――
Observerパターンの設計思想と、私たち自身の自己観察と再設計の技術に委ねられています。

// 明日もきっと
alarm.NotifyObservers();

あなたの毎朝にも、優しく正確な Notify() が訪れますように。

コメント

タイトルとURLをコピーしました