docs: document inversion of control pattern
Bug: None
Change-Id: Ie895d32cd402b8596ca833d8bd43f172947052de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2463556
Commit-Queue: Elly Fong-Jones <[email protected]>
Reviewed-by: Peter Kasting <[email protected]>
Cr-Commit-Position: refs/heads/master@{#817507}
diff --git a/docs/patterns/inversion-of-control.md b/docs/patterns/inversion-of-control.md
new file mode 100644
index 0000000..d9516f7
--- /dev/null
+++ b/docs/patterns/inversion-of-control.md
@@ -0,0 +1,376 @@
+## Inversion of Control
+
+"Inversion of control" is a design pattern used to allow users of a framework
+or library (often called clients) to customize the behavior of the framework.
+
+### Our Example
+
+Examples in this document will be given by extending or modifying this example
+API, which is hopefully self-explanatory:
+
+ class StringKVStore {
+ public:
+ StringKVStore();
+ virtual ~StringKVStore();
+
+ using KeyPredicate = base::RepeatingCallback<bool(const string&)>;
+
+ void Put(const string& key, const string& value);
+ void Remove(const string& key);
+ void Clear();
+
+ string Get(const string& key) const;
+ set<string> GetKeys() const;
+ set<string> GetKeysMatching(const KeyPredicate& predicate) const;
+
+ void SaveToPersistentStore();
+ };
+
+### What is inversion of control?
+
+Normally, client code calls into the library to do operations, so control flows
+from a high-level class to a low-level class:
+
+ void YourFunction() {
+ // GetKeys() calls into the StringKVStore library
+ for (const auto& key : kv_store_.GetKeys()) {
+ ...
+ }
+ }
+
+In "inverted" control flow, the library calls back into your code after you
+call into it, so control flows back from a low-level class to a high-level
+class:
+
+ bool IsKeyInteresting(const string& key) { ... }
+
+ void YourFunction() {
+ StringKVStore::KeyPredicate predicate =
+ base::BindRepeating(&IsKeyInteresting);
+ // GetKeysMatching() calls into the StringKVStore library, but it calls
+ // back into IsKeyInteresting defined in this file!
+ for (const auto& key : kv_store_.GetKeysMatching(predicate)) {
+ ...
+ }
+ }
+
+It is also often inverted in the Chromium dependency sense. For example, in
+Chromium, code in //content can't call, link against, or generally be aware of
+code in //chrome - the normal flow of data and control is only in one direction,
+from //chrome "down" to //content. When //content calls back into //chrome, that
+is an inversion of control.
+
+Abstractly, inversion of control is defined by a low-level class defining an
+interface that a high-level class supplies an implementation of. In the example
+fragment given above, `StringKVStore` defines an interface called
+`StringKVStore::KeyPredicate`, and `YourFunction` supplies an implementation of
+that interface - namely the bound instance of `IsKeyInteresting`. This allows
+the low-level class to use functionality of the high-level class without being
+aware of the specific high-level class's existence, or a high-level class to
+plug logic into a low-level class.
+
+There are a few main ways this is done in Chromium:
+
+* Callbacks
+* Observers
+* Listeners
+* Delegates
+
+**Inversion of control should not be your first resort. It is sometimes useful
+for solving specific problems, but in general it is overused in Chromium.**
+
+### Callbacks
+
+Callbacks are one of the simplest ways to do inversion of control, and often are
+all you need. Callbacks can be used to split out part of the framework's logic
+into the client, like so:
+
+ void StringKVStore::GetKeysMatching(const KeyPredicate& predicate) {
+ set<string> keys;
+ for (const auto& key : internal_keys()) {
+ if (predicate.Run(key))
+ keys.insert(key);
+ }
+ return keys;
+ }
+
+where `predicate` was supplied by the client of
+`StringKVStore::GetKeysMatching`. They can also be used for the framework
+library to notify clients of events, like so:
+
+ void StringKVStore::Put(const string& key, const string& value) {
+ ...
+ // In real code you would use CallbackList instead, but for explanatory
+ // purposes:
+ for (const auto& callback : key_changed_callbacks_)
+ callback.Run(...);
+ }
+
+making use of [Subscription].
+
+Callbacks can also be used to supply an implementation of something deliberately
+omitted, like so:
+
+ class StringKVStore {
+ using SaveCallback = base::RepeatingCallback<void(string, string)>;
+ void SaveToPersistentStore(const SaveCallback& callback);
+ };
+
+### Observers
+
+An "observer" receives notifications of events happening on an object. For
+example, an interface like this might exist:
+
+ class StringKVStore::Observer {
+ public:
+ virtual void OnKeyChanged(StringKVStore* store,
+ const string& key,
+ const string& from_value,
+ const string& to_value) {}
+ virtual void OnKeyRemoved(StringKVStore* store,
+ const string& key,
+ const string& old_value) {}
+ ...
+ }
+
+and then on the StringKVStore class:
+
+ class StringKVStore {
+ public:
+ ...
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+ }
+
+So an example of a `StringKVStore::Observer` might be:
+
+ class HelloKeyWatcher : public StringKVStore::Observer {
+ public:
+ void OnKeyChanged(StringKVStore* store,
+ const string& key,
+ const string& from_value,
+ const string& to_value) override {
+ if (key == "hello")
+ ++hello_changes_;
+ }
+ void OnKeyRemoved(StringKVStore* store,
+ const string& key,
+ const string& old_value) override {
+ if (key == "hello")
+ hello_changes_ = 0;
+ }
+ }
+
+where the `StringKVStore` arranges to call the relevant method on each
+`StringKVStore::Observer` that has been added to it whenever a matching event
+happens.
+
+Use an observer when:
+
+* More than one client may care to listen to events happening
+* Clients passively observe, but do not modify, the state of the framework
+ object being observed
+
+### Listeners
+
+A listener is an observer that only observes a single type of event. These were
+very common in C++ and Java before the introduction of lambdas, but these days
+are not as commonly seen, and you probably should not introduce new listeners -
+instead, use a plain [Callback].
+
+Here's an example:
+
+ class StringKVStore::ClearListener {
+ public:
+ virtual void OnCleared(StringKVStore* store) = 0;
+ }
+
+Use a listener when:
+
+* There is only a single client listener instance at most per framework object
+* There is only a single event being listened for
+
+### Delegates
+
+A delegate is responsible for implementing part of the framework that is
+deliberately missing. While observers and listeners are generally passive with
+respect to the framework object they are attached to, delegates are generally
+active.
+
+One very common use of delegates is to allow clients to make policy decisions,
+like so:
+
+ class StringKVStore::Delegate {
+ public:
+ virtual bool ShouldPersistKey(StringKVStore* store, const string& key);
+ virtual bool IsValidValueForKey(StringKVStore* store,
+ const string& key,
+ const string& proposed_value);
+ };
+
+Another common use is to allow clients to inject their own subclasses of
+framework objects that need to be constructed by the framework, by putting
+a factory method on the delegate:
+
+ class StringKVStore::Delegate {
+ public:
+ virtual unique_ptr<StringKVStoreBackend>
+ CreateBackend(StringKVStore* store);
+ }
+
+And then these might exist:
+
+ class MemoryBackedStringKVStoreDelegate : public StringKVStore::Delegate;
+ class DiskBackedStringKVStoreDelegate : public StringKVStore::Delegate;
+ ...
+
+Use a delegate when:
+
+* There needs to be logic that happens synchronously with what's happening in
+ the framework
+* It does not make sense to have a decision made statically per instance of a
+ framework object
+
+### Observer vs Listener vs Delegate
+
+If every call to the client could be made asynchronous and the API would still
+work fine for your use case, you have an observer or listener, not a delegate.
+
+If there might be multiple interested client objects instead of one, you have an
+observer, not a listener or delegate.
+
+If any method on your interface has any return type other than `void`, you have
+a delegate, not an observer or listener.
+
+You can think of it this way: an observer or listener interface *notifies* the
+observer or listener of a change to a framework object, while a delegate usually
+helps *cause* the change to the framework object.
+
+### Callbacks vs Observers/Listeners/Delegates
+
+Callbacks have advantages:
+
+* No separate interface is needed
+* Header files for client classes are not cluttered with the interfaces or
+ methods from them
+* Client methods don't need to use specific names, so the name-collision
+ problems above aren't present
+* Client methods can be bound (using [Bind]) with any needed state, including
+ which object they are attached to, so there is no need to pass the framework
+ object of interest back into them
+* The handler for an event is placed in object setup, rather than being implicit
+ in the presence of a separate method
+* They sometimes save creation of "trampoline" methods that simply discard or
+ add extra parameters before invoking the real handling logic for an event
+* Forwarding event handlers is a lot easier, since callbacks can easily be
+ passed around by themselves
+* They avoid multiple inheritance
+
+They also have disadvantages:
+
+* They can lead to deeply-nested setup code
+* Callback objects are heavyweight (performance and memory wise) compared to
+ virtual method calls
+
+### Design Tips
+
+1. Observers should have empty method bodies in the header, rather than having
+ their methods as pure virtuals. This has two benefits: client classes can
+ implement only the methods for events they care to observe, and it is
+ obvious from the header that the base observer methods do not need to be
+ called.
+
+2. Similarly, delegates should have sensible base implementations of every
+ method whenever this is feasible, so that client classes (subclasses of the
+ delegate class) can concern themselves only with the parts that are
+ relevant to their use case.
+
+3. When inverting control, always pass the framework object of interest back to
+ the observer/listener/delegate; that allows the client, if it wants to, to
+ reuse the same object as the observer/listener/delegate for multiple
+ framework objects. For example, if ButtonListener (given above) didn't pass
+ the button in, the same ButtonListener instance could not be used to listen
+ to two buttons simultaneously, since there would be no way to tell which
+ button received the click.
+
+4. Large inversion-of-control interfaces should be split into smaller
+ interfaces when it makes sense to do so. One notorious Chromium example
+ is [WebContentsObserver], which observes dozens of different events.
+ Whenever *any* of these events happens, *every* registered
+ WebContentsObserver has to be notified, even though virtually none of them
+ might care about this specific event. Using smaller interfaces helps with
+ this problem and makes the intent of installing a specific observer clearer.
+
+5. The framework class *should not* take ownership of observers or listeners.
+ For delegates the decision is less clear, but in general, err on the side of
+ not taking ownership of delegates either. It is common to hold raw pointers
+ to observers and listeners, and raw or weak pointers to delegates, with
+ lifetime issues managed via AddObserver/RemoveObserver or the helper classes
+ discussed below.
+
+6. Depending on your application and how widely-used you expect your observer,
+ listener, or delegate to be, you should probably use names that are longer
+ and more specific than you might otherwise. This is because client classes
+ may be implementing multiple inversion-of-control interfaces, so it is
+ important that their method names not collide with each other. For example,
+ instead of having `PageObserver::OnLoadStarted`, you might have
+ `PageObserver::OnPageLoadStarted` to reduce the odds of an unpleasant
+ collision with `NetworkRequestObserver::OnLoadStarted` (or similar). Note
+ that callbacks entirely avoid this problem.
+
+7. A callback is probably a better fit for what you're trying to do than one
+ of the other patterns given above!
+
+### Inversion of Control in Chromium
+
+Some key classes in `//base`:
+
+* [ScopedObserver]
+* [ObserverList] and [CheckedObserver]
+* [Subscription] and [CallbackList]
+
+And some production examples:
+
+* [WebContentsObserver] and [WebContentsDelegate]
+* [BrowserListObserver]
+* [URLRequestJobFactory::ProtocolHandler]
+* [WidgetObserver] and [ViewObserver]
+
+### When Not To Use This Pattern
+
+Inverted control can be harder to reason about, and more expensive at runtime,
+than other approaches. In particular, beware of using delegates when static data
+would be appropriate. For example, consider this hypothetical interface:
+
+ class StringKVStore::Delegate {
+ virtual bool ShouldSaveAtDestruction() { return true; }
+ }
+
+It should be clear from the naming that this method will only be called once per
+StringKVStore instance and that its value cannot meaningfully change within the
+lifetime of a given instance; in this case, "should save at destruction" should
+instead be a parameter given to StringKVStore directly.
+
+A good rule of thumb is that any method on a delegate that:
+
+* Will only be called once for a given framework object, or
+* Has a value that can't meaningfully change for a given framework object, and
+* Serves primarily to return that value, rather than doing some other work
+ like constructing a helper object
+
+should be a property on the framework object instead of a delegate method.
+
+[Bind]: ../../base/bind.h
+[BrowserListObserver]: ../../chrome/browser/ui/browser_list_observer.h
+[CallbackList]: ../../base/callback_list.h
+[Callback]: ../../base/callback.h
+[CheckedObserver]: ../../base/observer_list_types.h
+[ObserverList]: ../../base/observer_list.h
+[ScopedObserver]: ../../base/scoped_observer.h
+[Subscription]: ../../base/callback_list.h
+[URLRequestJobFactory::ProtocolHandler]: ../../net/url_request/url_request_job_factory.h
+[Unretained]: ../../base/bind.h
+[ViewObserver]: ../../ui/views/view_observer.h
+[WebContentsDelegate]: ../../content/public/browser/web_contents_delegate.h
+[WebContentsObserver]: ../../content/public/browser/web_contents_observer.h
+[WidgetObserver]: ../../ui/views/widget/widget_observer.h