Under Construction
Unity9 min88 views

Should the Observer Pattern Use a Regular Class or a Static Class?

Minh Khoa

Minh Khoa

Author

But the deeper question is actually:

Event này thuộc scope nào?
Nó sống trong bao lâu?
Ai sở hữu nó?
Ai chịu trách nhiệm cleanup?

image.pngBecause with the Observer Pattern, the hard part is not firing events.

Firing events is very easy:

SomethingChanged?.Invoke();

The hard part is managing the lifecycle.


Static class: convenient, but easily dangerous

A static event bus usually looks like this:

public static class GameEvents
{
    public static event Action<int> CoinChanged;

    public static void RaiseCoinChanged(int coin)
    {
        CoinChanged?.Invoke(coin);
    }
}

When used:

GameEvents.RaiseCoinChanged(currentCoin);

And the listener:

private void OnEnable()
{
    GameEvents.CoinChanged += UpdateCoinText;
}

private void OnDisable()
{
    GameEvents.CoinChanged -= UpdateCoinText;
}

The strengths of static are very clear:

  • No need to create an instance.
  • No need to pass a reference.
  • Can be called from many places.
  • Fast to set up.
  • Suitable for global events.

Example of a global event:

GamePaused
GameResumed
LanguageChanged
InternetConnectionChanged
PurchaseCompleted

These events do not belong to any particular scene or gameplay session. They are app-wide, so static can make sense.

But static has one big problem:

Static lives longer than most objects in a scene.

If an object subscribes to a static event but forgets to unsubscribe, the static event still keeps a reference to that object.

Example without OnDisable:

private void OnEnable()
{
    GameEvents.CoinChanged += UpdateCoinText;
}

When changing scenes, the old UI is destroyed, but the static event may still hold the old callback.

The result can be:

  • The callback is invoked multiple times.
  • The old UI still receives events.
  • Sound/VFX runs twice.
  • You encounter MissingReferenceException.
  • Bugs appear after changing scenes or replaying in the Editor.

So static is not wrong.

But static requires discipline:

Subscribe rõ.
Unsubscribe rõ.
Reset state khi cần.
Không biến GameEvents thành thùng rác global.

Regular class: more setup, but a clearer scope

If you use a regular class:

public sealed class GameplayEventBus
{
    public event Action<int> CoinChanged;

    public void RaiseCoinChanged(int coin)
    {
        CoinChanged?.Invoke(coin);
    }
}

The important difference is not simply removing the word static.

The important point is:

This event bus has an owner.

For example:

GameplaySession tạo GameplayEventBus
Các system trong session dùng event bus đó
Kết thúc session thì event bus cũng biến mất

A regular class is suitable when the event belongs only to a specific scope:

Một gameplay session
Một level
Một scene
Một popup UI
Một feature riêng

For example:

CustomerServed
DishCooked
LevelCompleted
ComboChanged
ShopItemSelected
TutorialStepCompleted

These events usually do not need to live for the whole game.

If you put them in a static GameEvents, it is very easy to get stuck with old listeners after restarting a level or changing scenes.

A regular class helps you control more clearly:

  • Where the instance is created.
  • Who owns the instance.
  • When the instance is dispose/reset..
  • Which session/scene the event belongs to.
  • Easier to inject/mock/test.

But a regular class is not automatically better.

If each system creates its own separate EventBus, the event will not reach the right place to listen.

So if you use a regular class, it also needs to be clear:

Ai tạo instance?
Ai truyền instance?
Ai hủy instance?

The right question is not “static or regular class?”

The better question is:

What scope should this event live in?

If the event lives app-wide:

LanguageChanged
PurchaseCompleted
AppPaused
AppResumed

A static class or a global service may be fine.

If the event lives per gameplay session:

CoinChangedInLevel
CustomerServed
DishCooked
LevelCompleted

You should prioritize a regular class or a session-based service instance.

If the event belongs only to a UI screen:

TabChanged
ShopItemSelected
PopupClosed
RewardButtonClicked

Do not push it up into a static global unless necessary.

Because when a local event is moved to global, the code may feel more convenient at first, but it becomes harder to control later.


A practical rule I often use

Event global, sống toàn game
→ Static class có thể dùng.

Event thuộc scene/session/screen/feature
→ Dùng class thường hoặc service instance.

Không chắc event có global không
→ Đừng vội static.

Static should be a deliberate choice, not a reflex just because it is convenient.


If answering in an interview

I would answer like this:

“I wouldn’t choose static or a regular class immediately. I’d look at the event’s scope and lifecycle first. If the event is truly global and does not depend on a scene or gameplay session, a static class can make sense. But if the event belongs to a level, a gameplay session, a UI screen, or a specific feature, I’d prioritize a regular class or service instance so ownership, cleanup, and testability are clearer.”

And I would add:

“Static events are dangerous if you forget to unsubscribe, because they can keep references to old objects after a scene change. So static is not automatically safer. If you use static, you have to be very disciplined about subscribe/unsubscribe and resetting state.”

This answer is much better than:

Em chọn static vì tiện.

Or:

Em chọn class thường vì đúng OOP hơn.

Because it shows that you understand the real issue: the event’s lifecycle.


Quick checklist

Before choosing static or a regular class, ask:

Event này có thật sự global không?
Nó có cần reset khi đổi scene/session không?
Subscriber sống ngắn hay dài?
Ai unsubscribe?
Có cần test/mock event bus không?
Nếu dùng static, nó có bị biến thành thùng rác global không?
Nếu dùng class thường, ai sở hữu instance?

If the answer is unclear, then the event design is still not clear.


Conclusion

static class is not wrong.

class thường is also not automatically correct.

What is wrong is choosing one of the two only because it is convenient or because it sounds like “the right architecture.”

For me:

Static class hợp với event global.
Class thường hợp với event có scope cụ thể.

A good Observer system does not only help objects communicate more loosely.

It also needs:

  • Clear scope.
  • Clear owner.
  • Clear lifecycle.
  • Clear cleanup.

In short:

Don’t ask “Should the Observer use static or a regular class?” first.

Ask “Where should this event live and die?” first.