introduce
this Hesitation design pattern Is a behavioral design pattern for managing state-related operations in a system. It ensures that operations are performed only when the system is in an appropriate state. If the required prerequisites are not met, the operation aborts or the system “hesitates.” For those like me who don’t know what Balking is, here’s what Google has to say about it: “Hesitation or unwillingness to accept an idea or commitment”. This mode is particularly useful in multi-threaded environments or systems where invalid operations may cause conflicts or errors.
Some in the community also believe that the hesitation pattern is more of an anti-pattern than a design pattern. If an object cannot support its API, it should restrict the API so that the call in question is unavailable, or so that the call can be made without restriction. This is an old pattern that seems to have emerged when the JVM was slower and synchronization was not as well understood and implemented as it is today. Regardless, it’s worth discussing and it’s up to the developer whether to use it or not.
The hesitation model relies on three basic concepts
- Guard status: Conditions that must be met to continue the operation.
- Dependence on state action: Operations that depend on the current state of the system.
- Thread safety: This pattern often uses locks or other synchronization mechanisms to ensure safety in concurrent environments.
Let us understand this through an example:
The printing system demonstrates hesitation mode:
- Imagine: A printer can only handle one print request at a time. Even multiple processes can issue print requests.
- Guard status: Print must not actively “Print” to process new print requests.
- Behavior: If the printer is busy, the system will stop processing new print requests.
notes: Yes, we could use queues to handle this, but for now let’s assume we don’t know that such an elegant data structure exists.
import threading
import time
class Printer:
def __init__(self):
self.state = "idle"
self.lock = threading.Lock()
def start_printing(self, job_id):
print(f"Attempting to start Print Job {job_id}...")
with self.lock: # Ensure thread safety
if self.state == "printing":
print(f"Balking: Print Job {job_id} cannot start. Printer is busy.")
return
self.state = "printing"
# Simulate the printing process
print(f"Print Job {job_id} started.")
time.sleep(3)
print(f"Print Job {job_id} completed.")
with self.lock:
self.printing = "idle"
# Multiple threads attempting to start print jobs
printer = Printer()
threads = [
threading.Thread(target=printer.start_printing, args=(1,)),
threading.Thread(target=printer.start_printing, args=(2,))
]
for t in threads:
t.start()
for t in threads:
t.join()
Looking at the code we can see that if we send a print request start_printing
arrive printer
and printer
Busy it checks its current status self.state
If the status is “printing” it will return without doing anything. Otherwise, it will accept the request and adjust its status accordingly.
When to use hesitation mode
- multi-threaded system: Prevent race conditions or invalid operations.
- Status related workflow: When the operation is only allowed in certain states.
- Resource management: Prevent improper use of shared resources. Objects using this mode are usually in a state of temporary indecision for an unknown duration. If the object is to remain in a hesitation-prone state for a known, limited time, protective suspension mode may be preferred.
Advantages of hesitation mode
- Prevent invalid operations: Guard ensures that operations only occur under valid conditions.
- Thread safety: Particularly useful in multi-threaded systems.
- Simplify the logic: Encapsulate state-related operations into clear, reusable patterns.
shortcoming
- Limited applicability: Most useful when the action is binary (allow or disallow).
- potential overhead: Protection checks and synchronization mechanisms come with performance costs.
in conclusion
The Balking design pattern provides an efficient way to manage state-related operations and prevent invalid operations in software systems. The reliability and maintainability of the system are enhanced by introducing explicit protection conditions and ensuring thread safety. Whether preventing multiple trips in a taxi reservation system or managing concurrent print jobs, the Balking pattern provides a structured approach to avoiding conflicts and maintaining operational integrity. Ultimately, the choice to use hesitant mode depends on the specific requirements of your application and its concurrency needs.