Skip to content

Commit 4cdfca3

Browse files
committed
Sentinel Object Pattern
1 parent e432835 commit 4cdfca3

5 files changed

Lines changed: 268 additions & 0 deletions

File tree

chunks/behavioral_sentinel.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
file: behavioral/sentinel.py
3+
chunk: behavioral_sentinel.md
4+
---
5+
6+
```python
7+
"""
8+
Sentinel Object Pattern Example
9+
10+
The Sentinel Object Pattern uses unique, distinguishable objects (sentinels) to signal
11+
special conditions—like missing arguments or end-of-sequence markers—in a program. This
12+
allows you to differentiate between user-provided values (including None) and the absence
13+
of a value or a special control signal.
14+
15+
Pattern Type: Utility / Behavioral Micro‑pattern
16+
"""
17+
18+
class Sentinel:
19+
"""
20+
A simple sentinel class. Each instance is unique and identifiable by name.
21+
"""
22+
__slots__ = ("name",)
23+
24+
def __init__(self, name: str):
25+
self.name = name
26+
27+
def __repr__(self) -> str:
28+
return f"<Sentinel {self.name}>"
29+
30+
# Define sentinel instances for common use-cases
31+
MISSING = Sentinel("MISSING")
32+
END_OF_STREAM = Sentinel("END_OF_STREAM")
33+
34+
35+
def process_items(iterator, max_items=MISSING):
36+
"""
37+
Processes items from an iterator up to max_items.
38+
- If max_items is MISSING, processes all items.
39+
- If max_items is None, treats it as zero and does nothing.
40+
41+
This demonstrates using a sentinel to differentiate "no argument provided" from
42+
"argument explicitly set to None".
43+
"""
44+
if max_items is MISSING:
45+
print("Processing all items.")
46+
elif max_items is None:
47+
print("No items to process (max_items=None).")
48+
return
49+
else:
50+
print(f"Processing up to {max_items} items.")
51+
52+
count = 0
53+
for item in iterator:
54+
if max_items is not MISSING and max_items is not None and count >= max_items:
55+
break
56+
print(f"Item {count}: {item}")
57+
count += 1
58+
59+
60+
def stream_data(data):
61+
"""
62+
Simulate streaming data: yields each item, then an END_OF_STREAM sentinel.
63+
"""
64+
for item in data:
65+
yield item
66+
yield END_OF_STREAM
67+
68+
69+
def consume_stream(stream):
70+
"""
71+
Consumes a stream until the END_OF_STREAM sentinel is encountered.
72+
"""
73+
print("Starting stream consumption...")
74+
for value in stream:
75+
if value is END_OF_STREAM:
76+
print("Received END_OF_STREAM sentinel. Stopping.")
77+
break
78+
print(f"Consumed: {value}")
79+
80+
81+
def main():
82+
"""The main function to demonstrate the the sentinel pattern."""
83+
items = list(range(3))
84+
85+
print("--- Example 1: process_items (all) ---")
86+
process_items(items)
87+
88+
print("\n--- Example 2: process_items (limit=2) ---")
89+
process_items(items, max_items=2)
90+
91+
print("\n--- Example 3: process_items (max_items=None) ---")
92+
process_items(items, max_items=None)
93+
94+
print("\n--- Example 4: stream consumption with sentinel ---")
95+
data_stream = stream_data(["a", "b", "c"])
96+
consume_stream(data_stream)
97+
98+
99+
if __name__ == "__main__":
100+
main()
101+
102+
```
103+
104+
## Summary
105+
Demonstration of the Sentinel Object Pattern in Python.
106+
107+
## Docstrings
108+
- A simple sentinel class. Each instance is unique and identifiable by name.
109+
- Processes items from an iterator up to max_items. - If max_items is MISSING, processes all items. - If max_items is None, treats it as zero and does nothing. This demonstrates using a sentinel to differentiate "no argument provided" from "argument explicitly set to None".
110+
- Simulate streaming data: yields each item, then an END_OF_STREAM sentinel.
111+
- Consumes a stream until the END_OF_STREAM sentinel is encountered.
112+

docs/behavioral_sentinel.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# The Sentinel Object Pattern (Behavioral)
2+
3+
## Intent
4+
5+
The Sentinel Object pattern signals special conditions or end-of-sequence markers in a program. It helps distinguish between user-provided values and missing values, which is useful for handling optional arguments or data stream termination.
6+
7+
## Problem It Solves
8+
9+
This Sentinel Object pattern solves the problem of distinguishing between user input (including `None`) and the absence of a value. It's especially useful with optional parameters, allowing you to tell whether a value was explicitly provided or left out.
10+
11+
## When to Use It
12+
13+
Use Sentinel Object pattern when you need to handle special cases in your code and clearly separate user-provided values, `None`, and control signals like end-of-sequence markers.
14+
15+
## When NOT to Use It
16+
17+
Avoid using the Sentinel Object pattern for required arguments. In those cases, inputs should be validated at the start of the function or method.
18+
19+
## How It Works
20+
21+
Create a unique, identifiable object—a "sentinel"—for each special condition. These are usually instances of a `Sentinel` class with a clear name. The `process_items` function in the example shows how sentinels can signal different conditions.
22+
23+
## Real-World Analogy
24+
25+
Imagine a party with two special guests: one always brings food and the other drinks. If a guest brings neither, it signals the party is over. These roles represent different sentinel values.
26+
27+
## Simplified Example
28+
29+
```python
30+
class Sentinel:
31+
def __init__(self, name):
32+
self.name = name
33+
34+
# Define sentinels for special conditions
35+
FOOD = Sentinel("Food")
36+
DRINK = Sentinel("Drink")
37+
END_OF_PARTY = Sentinel("End of Party")
38+
39+
def party_guest(item):
40+
if item is FOOD:
41+
print("This guest brought food.")
42+
elif item is DRINK:
43+
print("This guest brought drinks.")
44+
else:
45+
print("No more guests at the party.")
46+
47+
# Usage
48+
party_guest(FOOD)
49+
party_guest(DRINK)
50+
party_guest(END_OF_PARTY)
51+
```
52+
53+
## See Also
54+
55+
The Sentinel Object Pattern appears in many programming languages, including Python. A sample implementation can be found [here](https://github.com/taggedzi/python-design-pattern-rag/blob/main/patterns/behavioral/sentinel.py).

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Behavioral Interpreter](behavioral_interpreter.md)
66
- [Behavioral Memento](behavioral_memento.md)
77
- [Behavioral Observer](behavioral_observer.md)
8+
- [Behavioral Sentinel](behavioral_sentinel.md)
89
- [Behavioral State](behavioral_state.md)
910
- [Behavioral Strategy](behavioral_strategy.md)
1011
- [Behavioral Template Method](behavioral_template_method.md)

patterns/behavioral/sentinel.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
Sentinel Object Pattern Example
3+
4+
The Sentinel Object Pattern uses unique, distinguishable objects (sentinels) to signal
5+
special conditions—like missing arguments or end-of-sequence markers—in a program. This
6+
allows you to differentiate between user-provided values (including None) and the absence
7+
of a value or a special control signal.
8+
9+
Pattern Type: Utility / Behavioral Micro‑pattern
10+
"""
11+
12+
class Sentinel:
13+
"""
14+
A simple sentinel class. Each instance is unique and identifiable by name.
15+
"""
16+
__slots__ = ("name",)
17+
18+
def __init__(self, name: str):
19+
self.name = name
20+
21+
def __repr__(self) -> str:
22+
return f"<Sentinel {self.name}>"
23+
24+
# Define sentinel instances for common use-cases
25+
MISSING = Sentinel("MISSING")
26+
END_OF_STREAM = Sentinel("END_OF_STREAM")
27+
28+
29+
def process_items(iterator, max_items=MISSING):
30+
"""
31+
Processes items from an iterator up to max_items.
32+
- If max_items is MISSING, processes all items.
33+
- If max_items is None, treats it as zero and does nothing.
34+
35+
This demonstrates using a sentinel to differentiate "no argument provided" from
36+
"argument explicitly set to None".
37+
"""
38+
if max_items is MISSING:
39+
print("Processing all items.")
40+
elif max_items is None:
41+
print("No items to process (max_items=None).")
42+
return
43+
else:
44+
print(f"Processing up to {max_items} items.")
45+
46+
count = 0
47+
for item in iterator:
48+
if max_items is not MISSING and max_items is not None and count >= max_items:
49+
break
50+
print(f"Item {count}: {item}")
51+
count += 1
52+
53+
54+
def stream_data(data):
55+
"""
56+
Simulate streaming data: yields each item, then an END_OF_STREAM sentinel.
57+
"""
58+
for item in data:
59+
yield item
60+
yield END_OF_STREAM
61+
62+
63+
def consume_stream(stream):
64+
"""
65+
Consumes a stream until the END_OF_STREAM sentinel is encountered.
66+
"""
67+
print("Starting stream consumption...")
68+
for value in stream:
69+
if value is END_OF_STREAM:
70+
print("Received END_OF_STREAM sentinel. Stopping.")
71+
break
72+
print(f"Consumed: {value}")
73+
74+
75+
def main():
76+
"""The main function to demonstrate the the sentinel pattern."""
77+
items = list(range(3))
78+
79+
print("--- Example 1: process_items (all) ---")
80+
process_items(items)
81+
82+
print("\n--- Example 2: process_items (limit=2) ---")
83+
process_items(items, max_items=2)
84+
85+
print("\n--- Example 3: process_items (max_items=None) ---")
86+
process_items(items, max_items=None)
87+
88+
print("\n--- Example 4: stream consumption with sentinel ---")
89+
data_stream = stream_data(["a", "b", "c"])
90+
consume_stream(data_stream)
91+
92+
93+
if __name__ == "__main__":
94+
main()

summary_index.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
"pattern": "Observer",
3030
"summary": "This code implements the Observer design pattern using Python. It defines an interface for observing state changes, a subject that maintains a list of observers and notifies them of state changes, and concrete observers that implement the update method to react to notifications."
3131
},
32+
{
33+
"file": "behavioral/sentinel.py",
34+
"chunk": "behavioral_sentinel.md",
35+
"pattern": "Sentinel",
36+
"summary": "Demonstration of the Sentinel Object Pattern in Python."
37+
},
3238
{
3339
"file": "behavioral/state.py",
3440
"chunk": "behavioral_state.md",

0 commit comments

Comments
 (0)