🐦 Pattern Pigeon

A conversational assistant built with Dialogflow CX to teach the Strategy, Composite, and Observer design patterns.

View the Project on GitHub

Coo coo! (Salutations!)

Table of Contents
A pigeon, looking to the right.

🐦 Coo coo! I’m Pattern Pigeon–your feathered friend here to help you understand software architecture patterns like Strategy, Composite, and Observer. I might ruffle in a few pigeon facts too. What are you curious about today?

“Pigeon” by Raghunath Thirumalaisamy licensed 🅭🅯 CC BY 2.0Creative Commons Attribution 2.0 Generic

🐦 Pattern Pigeon is a deterministicAll responses are generated by the agent, without generative input from a large language model (LLM). However, Flows use language models for understanding end-user intention, which may not be completely deterministic. conversational assistant built with Google Cloud’s Dialogflow CX to teach the Strategy, Composite, and Observer design patterns.

Two ways to squack a “Hello!”

  1. Why not say hello using the messaging client on the right of your screen?
  2. Call (361) 360-5763 and you can squack wire to wire!

Demo video

Learn the Strategy, Composite, and Observer Patterns

Strategy

What is the Strategy pattern?

The Strategy pattern in a behavioral design pattern that defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable. This allows the behavior of a class to be selected at runtime without modifying its code.

What are examples of the Strategy pattern?

A common example is a navigation app. Suppose we have a class called Navigator. Instead of implementing a large buildRoute(A, B) that handles every routing logic internally, we delegate the route calculation to separate strategy classes–like DrivingRouteStrategy, WalkingRouteStrategy, or CyclingRouteStrategy. The Navigator simply delegates work to the current strategy, making it easy to switch behaviors without rewriting the logic.

Fighting video game

Another example is a fighting video game, where each character has a unique moveset. Rather than hardcoding each fighter’s behavior into one giant Fighter class, we can use the Strategy pattern to separate the moveset logic.

For example, we might define a MovesetStrategy interface with methods like lightAttack(), heavyAttack(), and specialMove(). Then we create different strategy classes like KarateMoveset, BoxingMoveset, or MagicMoveset, each implementing those methods in their own way.

The Fighter class holds a reference to a MovesetStrategy, allowing you to assign or change fighting styles dynamically—like equipping a new skill set mid-game or supporting custom characters.

Why use the Strategy pattern?

The Strategy pattern encourages software maintainability and extensibility by decoupling algorithms from the classes that use them. This makes software easier to test, more flexible, and open to extension.

What disadvantages are there with the Strategy pattern?

While the Strategy pattern offers flexibility, there are also a few trade-offs. There are more classes to manage, increasing code complexity if not organized well. For simple behavior, it might be unnecessarily complicate the design.

Composite

What is the Composite pattern?

The Composite pattern is a structural design pattern used to represent tree-like hierarchies of objects. It allows individual objects (leaves) and groups of objects (composites) to be treated uniformly through a shared interface.

In this pattern, there is a Component interface that both individual elements and composite groups implement. A Composite class contains child components—both leaves and other composites—and delegates all world to them.

What are examples of the Composite pattern?

Image editor

Imagine an image editor that defines a Graphic interface. The smallest drawable element could be a Dot, which implements this interface. More complex shapes like Circle or CompoundGraphic (which may group multiple Graphic objects) also implement the same interface. This way, the editor can handle single elements and groups of elements in the same way, such as drawing, moving, or deleting them.

SEC

The SEC is a great real-world example of the Composite pattern. The conference as a whole can be thought of as a composite object that contains multiple components. These components can be either teams (composite) or players (leaves).

Each player represents a leaf node, an individual unit that does not contain other components. A team represents a composite node containing multiple players and can perform operations like listing player stats, or calculating team rankings. The entire conference could be a composite that contains all teams. Operations like “print all team rosters” or “compute average team performance” can be delegated down through the structure.

Because both teams and players adhere to a common interface (such as displayStats() or getRanking()), clients can treat them uniformly. Whether the component is a single player or an entire team, the operation is invoked the same way.

Why use the Composite pattern?

The Composite pattern is useful when you need to work with tree-like structures where individual objects and groups of objects should be treated the same way. The uniformity of the components and compositions make things simple for clients. It also makes it really easy to scale the tree structure by adding new leaves without changing existing code. It also decouples the client from differentiating between dealing with a single object or a composite, making things simpler!

What disadvantages are there with the Composite pattern?

While the Composite pattern is powerful, there are some disadvantages. It can be complex to design, as a component that works for both simple and composite elements can be challenging to make. It’s also hard to restrict behavior because all components share the same interface.

Observer

What is the Observer pattern?

The Observer Pattern is a behavioral design pattern that establishes a subscription system, where a “Publisher” (or “Subject”) can notify multiple “Observers” of events or changes in state.

What are examples of the Observer pattern?

One is the multiplayer game Pokémon Go. Imagine there’s a wild Pigeon…errr…a wild Pidgeot…, and four players are nearby trying to catch it. When one player successfully captures it, the game needs to notify the other players that the Pidgeot is no longer available. The game server (the “Publisher”) sends updates to all connected clients (the “Observers”) to reflect this change, ensuring consistency across the game world.

Another is a weather app. When an event occurs—like a temperature change or an updated rain forecast—the server (acting as the “Publisher”) sends out the new data. All subscribed client apps (the “Observers”) receive the update and refresh the information displayed to the user.

What is the difference between the Publisher/Subject and the Observer?

In this pattern, the Publisher/Subject maintains a list of all the registered subscribers, then when an event occurs that requires a notification, calls a method to each subscriber to let them know that the event occurred. Thus, the observer is the class that responds to the event.

Why use the Observer pattern?

There are many advantages. The subject and the observer do not require tight coupling, which makes their connection very flexible and extensible. You can add new observers very easily without modifying the subject. It is also a very efficient way to get a message out from one server to many clients in a real-time scenario.

What disadvantages are there with the Observer pattern?

With many observers, there could be performance implications of notifying all of them if the operation is costly or takes a long time. Also, the pattern relies on the reliability and format of the notification from the subject, so any updates to the publisher requires updates to the observers.

Sample Conversation

Below is a sample conversation that demonstrates how the bot interacts.

Expand conversation

Hi!

🐦 Hey there! I’m Pattern Pigeon. I’ll help you learn about common software design patterns like Strategy, Composite, and Observer—step by step. I might also chirp in with some pigeon trivia. What would you like to start with?

Can I learn about the Observer pattern?

Coo coo! Observer it is! What would you like to learn about?

What do you know?

About the observer pattern, why not ask for some good examples? I can answer the difference between the Publisher/Subject and the Observer, why use the Observer pattern, and any disadvantages!

First, what is the observer pattern?

Alright, here’s the deal, coo-coo! The Observer Pattern is a behavioral design pattern that establishes a structured subscription mechanism, wherein a ‘Publisher’ (or ‘Subject’) is capable of notifying multiple ‘Observers’ about specific events or changes in state as they occur. Just like that, when the crumbs hit the ground, everybirdy’s in the loop, ya feel me?

Okay, let’s do something else

Enough Observer for now, back to the coop! What to dive into next, Composite, Observer, or Strategy?

Before we continue on the next topic, could you tell me a fun fact about pigeons?

Oooh, ooh, ooh! Here’s a good one. Did you know that pigeons were “probably one of the first animals to be domesticated by humans?” According to Atlas Obscura, images and carvings of the pigeons first appear in the Middle East and North Africa around 3,000 years ago (https://www.atlasobscura.com/articles/ancient-egypt-pigeon-problem). Want to hear another?

Nah.

Enough facts for one day, I get it. Let’s fly back to the coop! What to dive into next, Composite, Observer, or Strategy?

[End]

For developers

Source code

You can find the source code repository for Pattern Pigeon at thatrobotdev/Pattern-Pigeon.

Contributing

Is our feathered friend not squacking right? Noticed a typo? Want to add new content? Well you’re in luck! Follow these instructions to warp Pattern Pigeon into your Dialogflow CX dashboard so you can contribute changes!

Follow the instructions from Google Cloud to set up GitHub integration:

  1. If you’re contributing: fork this repository.
  2. Create a fine-grained personal access token from GitHub. (Read-only is fine if you’re not contributing.)
  3. Create a secret for the token in the Dialogflow CX console.
  4. Configure Git export/restore integration for your Dialogflow CX agent.
  5. Pull the agent from GitHub!
  6. If contributing, push your changes and rebase or amend any unintended edits.
    ⚠️ Note: Dialogflow CX overwrites the entire branch on push from the console, so make sure to:
    • Commit only intended changes.
    • Compare changes made in the dashboard vs. those you want to keep.
    • Optionally, make edits in a local text editor and manage them manually.

License

A pigeon standing in a flock alongside an image of a bird, a heart emoji, and the Apache software foundation feather

The pigeon photo used was taken by Muhammad Mahdi Karim/https://micro2macro.net. The image above is licensed under the 🄯 Free Art License. (Learn more). This project is not endorsed or connected to the Apache Software Foundation.

This project is licensed under the Apache License, Version 2.0, which you can read here.