2.1.15 Transactional Setting
Motivation
Your Model has multiple settable attributes, each one listened independently by Views. The business logic requires setting two or more attributes to reach the desired final Model state. Each set operation triggers a notification, as shown:
- Set A attribute on the Model
- Views are notified of change
- Set B attribute on the Model
- Views are notified of change
During step 2, listeners will be notified of the change and sync against a Model where only one of the attributes has been changed. Depending on the specific details of your Model and Views, this state may be inconsistent or not representable by the Views. However, setting and notification of the individual attributes may still be needed for specific use cases.
The desired goal is to trigger notification only when all set operations have been performed, as follows:
- Set A attribute without notification
- Set B attribute without notification
- Views are notified of change
Design
Transactional setting is the most trivial strategy to achieve the result outlined in the Motivation, the others being using a Lazy Model or an Accumulator, examined later.
Transactional setting implements a setter function accepting both attributes. This setter function alters the object state and then issues the change notification.
Practical Example
A Model representing a network connection to a remote host might want to present functionality to change the host, the port, and both simultaneously. If any listener is in charge of initiating a network connection, the notification should be delivered only when the Model contains correct host information.
class Connection(Model):
def set_host(self, host):
self._host = host
self.notify_listeners()
def set_port(self, port):
self._port = port
self.notify_listeners()
def set_host_and_port(self, host, port):
self._host = host
self._port = port
self.notify_listeners()
Without the set_host_and_port()
method, reconnecting from http://one.example.com:80
to http://two.example.com:8080
would either trigger a rogue connection to
http://one.example.com:8080
(when set_port(8080)
is called) or to
http://two.example.com:80/
(when set_host("two.example.com")
is called).
For obvious reasons, the set_host_and_port()
cannot reuse the independent setters, and must
therefore reimplement the setting logic. If this logic is complex, it might be good practice to
factor it out in a separate “silent setter” method.
Variation 1: qualified notification
If the Model performs qualified notification, the behavior in absence of transactional setting will be
- Set A attribute on the Model
- Views are notified of A change
- Set B attribute on the Model
- Views are notified of B change
Transactional setting will provide a method performing the following sequence:
- Set A attribute on the Model
- Set B attribute on the Model
- Views are notified of A change
- Views are notified of B change
or if the qualification protocol allows to deliver information about multiple changes at once
- Set A attribute on the Model
- Set B attribute on the Model
- Views are notified of A+B change
Variation 2: set/add multiple items
Transactional setting can also be used to add multiple elements at once, generally for a Model behaving as a list. The Model might want to implement an interface for adding both one or multiple elements. The implementation normally acts as a syntactic sugar to generate a list with one item.
class BookCollection(Model):
def add_book(self, book):
self.add_books([book])
def add_books(books):
self._books.extend(books)
self.notify_listeners()