| ## 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| class StringKVStore { |
| public: |
| ... |
| void AddObserver(Observer* observer); |
| void RemoveObserver(Observer* observer); |
| } |
| ``` |
| |
| So an example of a `StringKVStore::Observer` might be: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| 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: |
| |
| ```cpp |
| class StringKVStore::Delegate { |
| public: |
| virtual unique_ptr<StringKVStoreBackend> |
| CreateBackend(StringKVStore* store); |
| } |
| ``` |
| |
| And then these might exist: |
| |
| ```cpp |
| 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`: |
| |
| * [base::ScopedObservation] |
| * [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: |
| |
| ```cpp |
| 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 |
| [base::ScopedObservation]: ../../base/scoped_observation.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 |