blob: 8310f5b0ce922ca6ef39b3c4e9a188be0296b253 [file] [log] [blame] [view]
Evan Stade9446f0b2025-04-14 16:48:051# `//chrome/browser` design principles
2
Erik Chenf652e612024-07-17 21:38:563These design principles make it easier to write, debug, and maintain desktop
Evan Stade9446f0b2025-04-14 16:48:054code in `//chrome/browser`. Most, but not all code in `//chrome/browser` is
5desktop code. Some code is used on Android.
Erik Chen598410c2024-04-11 21:05:516
7## Caveats:
8* These are recommendations, not requirements.
9* These are not intended to be static. If you think a
Evan Stade9446f0b2025-04-14 16:48:0510 principle doesn't make sense, reach out to `//chrome/OWNERS`.
Erik Chen598410c2024-04-11 21:05:5111* These are intended to apply to new code and major refactors. We do not expect
12 existing features to be refactored, except as need arises.
13
14## Structure, modularity:
Erik Chen9c0281b2024-05-29 23:41:2015* Features should be modular.
Erik Chenf6c0a562024-08-23 03:54:5116 * For most features, all code should live in some combination of
Evan Stade9446f0b2025-04-14 16:48:0517 `//component/<feature>` and `//chrome/browser/<feature>` (or
18 `//chrome/browser/ui/<feature>`), and not in `//chrome/browser/ui/views`.
19 * The historical rule restricting access to views in `//chrome/browser`
20 and `//chrome/browser/ui` has been removed.
21 * The historical rule disallowing ui code in `//chrome/browser` has been
Erik Chenc8b83792024-08-21 23:32:4522 removed.
Erik Chen598410c2024-04-11 21:05:5123 * WebUI resources are the only exception. They will continue to live in
Evan Stade9446f0b2025-04-14 16:48:0524 `//chrome/browser/resources/<feature>` alongside standalone `BUILD.gn`
25 files.
26 * This directory should have a standalone `BUILD.gn` and `OWNERS` file.
27 * All files in the directory should belong to targets in the `BUILD.gn`.
Erik Chen288c96772024-08-12 21:14:4628 * Do NOT add to `//chrome/browser/BUILD.gn:browser`,
29 `//chrome/test/BUILD.gn` or `//chrome/browser/ui/BUILD.gn:ui`.
30 * gn circular dependencies are disallowed. Logical
31 circular dependencies are allowed (for legacy reasons) but discouraged.
Erik Chen288c96772024-08-12 21:14:4632 * [cookie
33 controls](https://chromium-review.googlesource.com/c/chromium/src/+/5771416/5/chrome/browser/ui/cookie_controls/BUILD.gn)
34 is an example of a feature with logical circular dependencies.
35 * The header files are moved into a "cookie_controls" target with no
36 circular dependencies.
37 * The cc files are moved into a "impl" target, with circular
38 dependencies allowed with `//chrome/browser:browser` and
39 `//chrome/browser/ui:ui`. These circular dependencies will
40 disappear when all sources are removed from `//chrome/browser:browser` and `//chrome/browser/ui:ui`.
41 * The separation between header and cc files is functionally
42 equivalent to creating abstract base classes in one target, with
43 h/cc files in a separate target. This just skips the boilerplate
44 of creating the abstract base classes.
45 * Even though there are no build circular dependencies, there are
46 still logical circular dependencies from the cc files. This
47 discrepancy is because C++ allows headers to forward declare
48 dependencies, which do not need to be reflected in gn.
Kaan Alsan7ac0eb22024-12-09 23:15:2649 * [Lens
50 overlay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/lens/BUILD.gn;drc=8e2c1c747f15a93c55ab2f10ebc8b32801ba129e)
51 is an example with *almost* no circular dependencies.
52 * It has a logical circular dependency on `//chrome/browser/browser/ui:ui`,
53 which will no longer be necessary once NTP is also modularized (crbug.com/382237520).
54 * The BUILD.gn should use public/sources separation.
55 * The main reason for this is to guard against future, unexpected usage
56 of parts of the code that were intended to be private. This makes it
57 difficult to change implementation details in the future.
58 * This directory may have a public/ subdirectory to enforce further
59 encapsulation, though this example does not use it.
Erik Chen598410c2024-04-11 21:05:5160 * This directory may have its own namespace.
Erik Chen598410c2024-04-11 21:05:5161 * Corollary: There are several global functions that facilitate dependency
62 inversion. It will not be possible to call them from modularized features
63 (no dependency cycles), and their usage in non-modularized features is
64 considered a red flag:
65 * `chrome::FindBrowserWithTab` (and everything in browser_finder.h)
66 * `GetBrowserViewForNativeWindow` (via browser_view.h)
67 * `FindBrowserWindowWithWebContents` (via browser_window.h)
Erik Chen63f6f1a2024-08-21 01:33:0468 * Corollary: Don't use `Browser*`. This is functionally a container of
69 hundreds of other pointers. It is impossible to specify dependencies,
70 since `Browser*` functionally depends on everything. Instead, pass in the
71 relevant pointers, e.g. `Profile*`, `FooFeatureController`, etc.
72 * Code that uses `Browser*` is also impossible to properly unit test.
Erik Chen9c0281b2024-05-29 23:41:2073 * Rationale: Modularity enforces the creation of API surfaces and explicit
74 dependencies. This has several positive externalities:
75 * Separation of interface from implementation prevents unnecessarly
76 tight coupling between features. This in turn reduces spooky action at
77 a distance, where seemingly innocuous changes break a distant,
78 supposedly unrelated feature.
79 * Explicit listing of circular dependencies exposes the likely fragile
80 areas of code.
81 * Alongside the later guidance of global functions must be pure,
82 modularity adds the requirement that test-code perform dependency
83 injection. This eliminates a common bug where test behavior diverges
84 from production behavior, and logic is added to production code to
85 work around test-only behaviors.
Erik Chen598410c2024-04-11 21:05:5186
Erik Chen2e1346e2024-08-22 07:21:3187```cpp
88// Do not do this:
89FooFeature(Browser* browser) : browser_(browser) {}
90FooFeature::DoStuff() { DoStuffWith(browser_->profile()->GetPrefs()); }
91
92// Do this:
93FooFeature(PrefService* prefs) : prefs_(prefs) {}
94FooFeature::DoStuff() { DoStuffWith(prefs_); }
95```
96
Erik Chen598410c2024-04-11 21:05:5197* Features should have a core controller with precise lifetime semantics. The
98 core controller for most desktop features should be owned and instantiated by
99 one of the following classes:
100 * `TabFeatures` (member of `TabModel`)
101 * This class should own all tab-centric features. e.g. print preview,
102 lens overlay, compose, find-in-page, etc.
103 * If the feature requires instantiation of
Erik Chena37ca3372024-08-29 00:26:32104 `content::WebContentsUserData`, it should be done in this class.
Erik Chen598410c2024-04-11 21:05:51105 * For desktop chrome, `TabHelpers::AttachTabHelpers` will become a
106 remove-only method. Clank/WebView may continue to use section 2 of
107 `TabHelpers::AttachTabHelpers` (Clank/WebView only).
108 * More complex features that also target mobile platforms or other
109 supported embedders (e.g. android webview) will continue to use the
110 layered components architecture.
111 * We defer to //components/OWNERS for expertise and feedback on the
112 architecture of these features, and encourage feature-owners to
113 proactively reach out to them.
Erik Chena37ca3372024-08-29 00:26:32114 * Lazy instantiation of `content::WebContentsUserData` is an
Erik Chen598410c2024-04-11 21:05:51115 anti-pattern.
116 * `BrowserWindowFeatures` (member of `Browser`)
117 * example: omnibox, security chip, bookmarks bar, side panel
118 * `BrowserContextKeyedServiceFactory` (functionally a member of `Profile`)
Erik Chend5431602024-05-23 20:20:58119 * Override `ServiceIsCreatedWithBrowserContext` to return `true`. This
120 guarantees precise lifetime semantics.
121 * Lazy instantiation is an anti-pattern.
122 * Production code is started via `content::ContentMain()`. Test
123 harnesses use a test-suite dependent start-point, e.g.
124 `base::LaunchUnitTests`. Tests typically instantiate a subset of
125 the lazily-instantiated factories instantiated by production code,
126 at a different time, with a different order. This results in
127 different, sometimes broken behavior in tests. This is typically
128 papered over by modifying the production logic to include
129 otherwise unnecessary conditionals, typically early-exits.
130 Overriding `ServiceIsCreatedWithBrowserContext` guarantees
131 identical behavior between test and production code.
132 * Use `TestingProfile::Builder::AddTestingFactory` to stub or fake
133 services.
Erik Chen16208722024-06-06 00:09:08134 * Separating the .h and .cc file into different build targets is
135 allowed.
136 * BrowserContextKeyedServiceFactory combines 3 pieces of
137 functionality:
138 * A getter to receive a service based on an instance of
139 `Profile`.
140 * A mechanism to establishing dependencies.
141 * A way to glue together //chrome layer logic with //components
142 layer logic.
143 * The latter two pieces of functionality are implemented in the
144 .cc file and typically result in dependencies on other parts
145 of //chrome. The first piece of functionality is implemented
146 in the .h file and does not necessarily introduce a dependency
147 on //chrome, since the returned service can be defined in
148 //components.
Erik Chenea810ac2024-07-29 23:44:32149 * GlobalFeatures.
150 * Features which are scoped to the entire process and span multiple
151 Profiles should be members of GlobalFeatures.
152 * GlobalFeatures is a member of BrowserProcess and they have similar
153 lifetime semantics. The main difference is that historically
154 BrowserProcess used the antipattern of lazy instantiation, and the
155 setup of TestingBrowserProcess encourages divergence between test
156 logic and production logic. On the other hand, GlobalFeatures is
157 always instantiated.
158 * This is not making any statements about initialization (e.g.
159 performing non-trivial setup).
Erik Chen598410c2024-04-11 21:05:51160 * The core controller should not be a `NoDestructor` singleton.
Erik Chen2e1346e2024-08-22 07:21:31161
162```cpp
163// Properly scoped state avoids categories of bugs and subtle issues. For
164// example: one common mistake is to store window-scoped state on a tab-scoped
165// object. This results in subtle bugs (or crashes) when tab is dragged into a
166// new window.
167
168// Do not do this:
169FooTabFeature {
170 // As per (1) above, we shouldn't be passing or storing Browser* anyways.
171 // Another common anti-pattern is to dynamically look up Browser* via
172 // browser_finder methods. These often result in the wrong Browser*
173 Browser* browser_;
174
175 // This will point to the wrong BrowserView if the tab is dragged into a new
176 // window. Furthermore, BrowserView* itself is a container of hundreds of
177 // other views. The tab-scoped feature typically wants something much more
178 // specific.
179 BrowserView* browser_view_;
180
181 // Extensions are profile-scoped, and thus any state/logic should be in a
182 // ProfileKeyedService. If the user has multiple tabs (possibly across
183 // multiple windows) simultaneously interacting with FooTabFeature, then it's
184 // likely that the extension will uninstall while it's still in use.
185 void InstallExtension();
186 void UninstallExtension();
187};
188
189// Instead do this:
190FooTabFeature {
191 // Guaranteed to remain valid for the lifetime of this class. This can be used
192 // to dynamically access relevant window state via
193 // tab_->GetBrowserWindowInterface()->GetFeatures().GetFooWindowFeature()
194 TabInterface* tab_;
195};
196
197FooService : public KeyedService {
198 void InstallExtension();
199 void UninstallExtension();
200};
201```
202
Erik Chen598410c2024-04-11 21:05:51203* Global functions should not access non-global state.
204 * Pure functions do not access global state and are allowed. e.g. `base::UTF8ToWide()`
205 * Global functions that wrap global state are allowed, e.g.
206 `IsFooFeatureEnabled()` wraps the global variable
207 `BASE_FEATURE(kFooFeature,...)`
208 * Global functions that access non-global state are disallowed. e.g.
209 static methods on `BrowserList`.
Erik Chen08053b12024-05-25 01:05:41210* No distinction between `//chrome/browser/BUILD.gn` and
211 `//chrome/browser/ui/BUILD.gn`
212 * There is plenty of UI code outside of the `ui` subdirectory, and plenty of
213 non-UI code inside of the `ui` subdirectory. Currently the two BUILD files
214 allow circular includes. We will continue to treat these directories and
215 BUILD files as interchangeable.
Erik Chen598410c2024-04-11 21:05:51216
217## UI
218* Features should use WebUI and Views toolkit, which are x-platform.
219 * Usage of underlying primitives is discouraged (aura, Cocoa, gtk, win32,
220 etc.). This is usually a sign that either the feature is misusing the UI
221 toolkits, or that the UI toolkits are missing important functionality.
222* Features should use "MVC"
223 * Clear separation between controller (control flow), view (presentation of
224 UI) and model (storage of data).
225 * For simple features that do not require data persistence, we only require
226 separation of controller from view.
227 * TODO: work with UI/toolkit team to come up with appropriate examples.
Erik Chen08053b12024-05-25 01:05:41228* Views:
229 * For simple configuration changes, prefer to use existing setter methods
230 and builder syntax.
231 * Feel free to create custom view subclasses to encapsulate logic or
232 override methods where doing so helps implement layout as the composition
233 of smaller standard layouts, or similar. Don't try to jam the kitchen sink
234 into a single giant view.
235 * However, avoid subclassing existing concrete view subclasses, e.g. to add
236 or tweak existing behavior. This risks violating the Google style guidance
237 on multiple inheritance and makes maintenance challenging. In particular
238 do not do this with core controls, as the behaviors of common controls
239 should not vary across the product.
240* Avoid subclassing Widgets.
Erik Chen598410c2024-04-11 21:05:51241* Avoid self-owned objects/classes for views or controllers.
242
243## General
244* Code unrelated to building a web-browser should not live in //chrome.
245 * See [chromeos/code.md](chromeos/code.md) for details on ChromeOS (non-browser) code.
246 * We may move some modularized x-platform code into //components. The main
247 distinction is that ChromeOS can depend on //components, but not on
248 //chrome. This will be evaluated on a case-by-case basis.
249* Avoid nested message loops.
250* Threaded code should have DCHECKs asserting correct sequence.
251 * Provides documentation and correctness checks.
252 * See base/sequence_checker.h.
Erik Chene651f9f2024-09-10 03:08:39253* Avoid tight coupling of unrelated features.
254 * This results in O(N^2) complexity, since every pair of features ends up
255 implicitly coupled.
256 * The proper solution is to work with UX to use consistent design language,
257 which in turn results in O(N) complexity.
258```cpp
259// Good, complexity is O(N) and no tight coupling using a well-understood and
260// common design pattern: modality. Similar logic will be needed in other modal
261// UIs. The logic in this class does not change regardless of how many other new
262// modal features are created.
263class Sparkles {
264 // Shows sparkles over the entire tab. Should not be shown over other Chrome
265 // contents (e.g. print preview)
266 void Show() {
267 if (tab_->CanShowModalUI()) {
268 MakeSparkles();
269 // Prevents other modals from showing until the member is reset.
270 modal_ui_ = tab_->ShowModalUI();
271 }
272 }
273
274 std::unique_ptr<ScopedTabModalUI> modal_ui_;
275 raw_ptr<TabInterface> tab_;
276};
277
278// Bad. Introduces tight coupling between unrelated features. Similar logic is
279// needed in PrintPreview and DevTools. Complexity will scale with O(N^2). When
280// a new modal feature is created, every modal feature will need to be updated.
281class Sparkles {
282 void Show() {
283 if (PrintPreview::Showing()) {
284 return;
285 }
286 if (DevTools::Showing()) {
287 return;
288 }
289 MakeSparkles();
290 }
291};
292```
Erik Chen598410c2024-04-11 21:05:51293* Most features should be gated by base::Feature, API keys and/or gn build
294 configuration, not C preprocessor conditionals e.g. `#if
295 BUILDFLAG(FEATURE_FOO)`.
296 * We ship a single product (Chrome) to multiple platforms. The purpose of
Solomon Kinard8bdf8562024-05-29 21:25:27297 preprocessor conditionals is:
Erik Chen598410c2024-04-11 21:05:51298 * (1) to allow platform-agnostic code to reference platform-specific
299 code.
300 * e.g. `#if BUILDFLAG(OS_WIN)`
301 * (2) to glue src-internal into public //chromium/src.
302 * e.g. `#if BUILDFLAG(GOOGLE_CHROME_BRANDING)`
303 * (3) To make behavior changes to non-production binaries
304 * e.g. `#if !defined(NDEBUG)`
305 * e.g. `#if defined(ADDRESS_SANITIZER)`
306 * (1) primarily serves as glue.
307 * (2) turns Chromium into Chrome. We want the two to be as similar as
308 possible. This preprocessor conditional should be used very sparingly.
309 Almost all our tests are run against Chromium, so any logic behind this
310 conditional will be mostly untested.
311 * (3) is a last resort. The point of DEBUG/ASAN/... builds is to provide
312 insight into problems that affect the production binaries we ship. By
313 changing the production logic to do something different, we are no longer
314 accomplishing this goal.
315 * In all cases, large segments of code should not be gated behind
316 preprocessor conditionals. Instead, they should be pulled into separate
317 files via gn.
318 * In the event that a feature does have large swathes of code in separate
319 build files/translation units (e.g. extensions), using a custom feature
320 flag (e.g. `BUILDFLAG(ENABLE_EXTENSIONS)`) to glue this into the main source
321 is allowed. The glue code should be kept to a minimum.
322* Avoid run-time channel checking.
Erik Chena64786d2024-09-03 19:43:00323* Macros are rarely appropriate. See google [style
324 guide](https://google.github.io/styleguide/cppguide.html#Preprocessor_Macros)
325 * As a rule of thumb, the macros themselves should not contain conditional
326 logic. Macros should not be triply (or more deeply) nested. When in doubt,
327 ask a member of //chrome/OWNERS.
Erik Chen598410c2024-04-11 21:05:51328* Avoid test only conditionals
329 * This was historically common in unit_tests, because it was not possible to
330 stub out dependencies due to lack of a clear API surface. By requiring
331 modular features with clear API surfaces, it also becomes easy to perform
332 dependency injection for tests, thereby removing the need for conditionals
333 that can be nullptr in tests.
334 * In the event that they are necessary, document and enforce via
335 `CHECK_IS_TEST()`.
Erik Chend086ae02024-08-20 22:53:33336 * As a corollary: do not use BrowserWithTestWindowTest. In production code,
337 there is a 1:1 correspondence between "class Browser" and "class
338 BrowserView". Features that span the two classes (which is most UI
339 features) should be able to unconditionally reference "class BrowserView".
340 The existence of this test suite forces features tested by these tests to
341 have "if (!browser_view)" test-only checks in production code. Either
Erik Chen49c77112024-09-10 23:08:16342 write a browser test (where both classes are provided by the test fixture)
343 or a unit test that requires neither.
Erik Chend086ae02024-08-20 22:53:33344 * This also comes from a corollary of don't use "class Browser".
345 Historically, features were written that take a "class Browser" as an
346 input parameter. "class Browser" cannot be stubbed/faked/mocked, and
347 BrowserWithTestWindowTest was written as a workaround as a way to
348 provide a "class Browser" without a "class BrowserView". New features
349 should not be taking "class Browser" as input, and should instead
350 perform dependency injection.
Erik Chen49c77112024-09-10 23:08:16351* Every UI feature should have at least 1 CUJ test.
352 * New UI features should write these tests using InteractiveBrowserTest.
353* Do not write change detector unit tests. The purpose of a unit test is to
354 validate behavior of common and edge cases for a block of code that has many
355 possible valid inputs.
356```cpp
357// Good. Depending on context, this can be broken into separate tests.
358bool IsPrime(int input);
359TEST(Math, CheckIsPrime) {
360 EXPECT_TRUE(IsPrime(2));
361 EXPECT_TRUE(IsPrime(3));
362 EXPECT_FALSE(IsPrime(99));
363 EXPECT_FALSE(IsPrime(-2));
364 EXPECT_FALSE(IsPrime(0));
365 EXPECT_FALSE(IsPrime(1));
366}
367
368// Bad. This is a change detector test. Change detector tests are easy to spot
369// because the test logic looks the same as the production logic.
370class ShowButtonOnBrowserActivation : public BrowserActivationListener {
371 void ShowButton();
372 bool DidShowButton();
373
374 // BrowserActivationListener overrides:
375 void BrowserDidActivate() override {
376 ShowButton();
377 }
378};
379
380Test(ShowButtonOnBrowserActivationTest, ShowButtonOnActivate) {
381 ShowButtonOnBrowserActivation listener;
382 listener.BrowserDidActivate();
383 EXPECT_TRUE(listener.DidShowButton());
384}
385
386```
Erik Chen598410c2024-04-11 21:05:51387* Avoid unreachable branches.
388 * We should have a semantic understanding of each path of control flow. How
389 is this reached? If we don't know, then we should not have a conditional.
390* For a given `base::Callback`, execution should either be always synchronous, or
391always asynchronous. Mixing the two means callers have to deal with two distinct
392control flows, which often leads to incorrect handling of one.
393Avoid the following:
394```cpp
395if (result.cached) {
396 RunCallbackSync())
397} else {
398 GetResultAndRunCallbackAsync()
399}
400```
401* Avoid re-entrancy. Control flow should remain as localized as possible.
402Bad (unnecessary delegation, re-entrancy)
403```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37404class CarFactory {
405 std::unique_ptr<Car> CreateCar() {
406 if (!CanCreateCar()) {
407 return nullptr;
408 }
409 if (FactoryIsBusy() && !delegate->ShouldShowCarIfFactoryIsBusy()) {
410 return nullptr;
411 }
412 return std::make_unique<Car>();
413 }
414
415 bool CanCreateCar();
416 bool FactoryIsBusy();
417
418 Delegate* delegate_ = nullptr;
419};
420
Erik Chen598410c2024-04-11 21:05:51421class CarSalesPerson : public Delegate {
Solomon Kinardc164d5a2024-05-28 21:38:37422 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51423 std::unique_ptr<Car> ShowCar() {
424 return car_factory_->CreateCar();
425 }
426
427 // Delegate overrides:
428 // Whether the car should be shown, even if the factory is busy.
429 bool ShouldShowCarIfFactoryIsBusy() override;
430
Solomon Kinardc164d5a2024-05-28 21:38:37431 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51432};
433```
434
435Good, version 1: Remove delegation. Pass all relevant state to CarFactory so that CreateCar() does not depend on non-local state.
436```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37437class CarFactory {
438 std::unique_ptr<Car> CreateCar(bool show_even_if_factory_is_busy) {
439 if (!CanCreateCar()) {
440 return nullptr;
441 }
442 if (FactoryIsBusy() && !show_even_if_factory_is_busy) {
443 return nullptr;
444 }
445 return std::make_unique<Car>();
446 }
447 bool CanCreateCar();
448 bool FactoryIsBusy();
449};
450
Erik Chen598410c2024-04-11 21:05:51451class CarSalesPerson {
Solomon Kinardc164d5a2024-05-28 21:38:37452 // Can return nullptr, in which case no car is shown.
Erik Chen598410c2024-04-11 21:05:51453 std::unique_ptr<Car> ShowCar() {
454 return car_factory_->CreateCar(ShouldShowCarIfFactoryIsBusy());
455 }
456
457 // Whether the car should be shown, even if the factory is busy.
458 bool ShouldShowCarIfFactoryIsBusy();
459
Solomon Kinardc164d5a2024-05-28 21:38:37460 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51461};
462```
463
Solomon Kinarddc9f9b022024-05-28 21:37:29464Good, version 2: Remove delegation. CreateCar always creates a car (fewer conditionals). State only flows from CarFactory to CarSalesPerson (and never backwards).
Erik Chen598410c2024-04-11 21:05:51465```cpp
Solomon Kinardc164d5a2024-05-28 21:38:37466class CarFactory {
467 bool CanCreateCar();
468 bool FactoryIsBusy();
469 // Never returns nullptr.
470 std::unique_ptr<Car> CreateCar();
471};
472
Erik Chen598410c2024-04-11 21:05:51473class CarSalesPerson {
474 // Can return nullptr, in which case no car is shown
475 std::unique_ptr<Car> ShowCar() {
Solomon Kinardc164d5a2024-05-28 21:38:37476 if (!car_factory_->CanCreateCar()) {
Erik Chen598410c2024-04-11 21:05:51477 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37478 }
479 if (car_factory_->FactoryIsBusy() && !ShouldShowCarIfFactoryIsBusy()) {
Erik Chen598410c2024-04-11 21:05:51480 return nullptr;
Solomon Kinardc164d5a2024-05-28 21:38:37481 }
Erik Chen598410c2024-04-11 21:05:51482 return car_factory_->CreateCar();
483 }
484
485 // Whether the car should be shown, even if the factory is busy.
486 bool ShouldShowCarIfFactoryIsBusy();
Solomon Kinardc164d5a2024-05-28 21:38:37487 CarFactory* car_factory_ = nullptr;
Erik Chen598410c2024-04-11 21:05:51488};
489```
490
Erik Chenf652e612024-07-17 21:38:56491* Circular dependencies are a symptom of problematic design.
Erik Chen598410c2024-04-11 21:05:51492
Erik Chenf652e612024-07-17 21:38:56493Bad. FeatureShowcase depends on FeatureA. FeatureA depends on FeatureB.
494FeatureB depends on FeatureShowcase. The root problem is that the design for
495FeatureShowcase uses both a pull and a push model for control flow.
496```cpp
497// Shows an icon per feature. Needs to know whether each icon is visible.
498class FeatureShowcase {
499 FeatureShowcase() {
500 // Checks whether A should be visible, and if so, shows A.
501 if (FeatureA::IsVisible())
502 ShowFeatureA();
503 }
504
505 // Called to make B visible.
506 void ShowFeatureB();
507
508};
509
510class FeatureA {
511 // Feature A depends on feature B.
512 FeatureA(FeatureB* b);
513
514 static bool IsVisible();
515};
516
517class FeatureB {
518 FeatureB(FeatureShowcase* showcase) {
519 if (IsVisible())
520 showcase->ShowFeatureB();
521 }
522 static bool IsVisible();
523};
524```
525
526Good, version 1. FeatureShowcase uses a pull model for control flow.
527FeatureShowcase depends on FeatureA and FeatureB. FeatureA depends on FeatureB.
528There is no circular dependency.
529```cpp
530// Shows an icon per feature. Needs to know whether each icon is visible.
531class FeatureShowcase {
532 FeatureShowcase() {
533 if (FeatureA::IsVisible())
534 ShowFeatureA();
535 if (FeatureB::IsVisible())
536 ShowFeatureB();
537 }
538};
539
540class FeatureA {
541 // Feature A depends on feature B.
542 FeatureA(FeatureB* b);
543
544 static bool IsVisible();
545};
546
547class FeatureB {
548 FeatureB();
549 static bool IsVisible();
550};
551```
552
553Good, version 2. FeatureShowcase uses a push model for control flow. FeatureA
554and FeatureB both depend on FeatureShowcase. There is no circular dependency.
555```cpp
556// Shows an icon per feature. Needs to know whether each icon is visible.
557class FeatureShowcase {
558 FeatureShowcase();
559
560 // Called to make A visible.
561 void ShowFeatureA();
562
563 // Called to make B visible.
564 void ShowFeatureB();
565};
566
567class FeatureA {
568 // Feature A depends on feature B.
569 FeatureA(FeatureB* b, FeatureShowcase* showcase) {
570 if (IsVisible())
571 showcase->ShowFeatureA();
572 }
573
574 static bool IsVisible();
575};
576
577class FeatureB {
578 FeatureB(FeatureShowcase* showcase) {
579 if (IsVisible())
580 showcase->ShowFeatureB();
581 }
582
583 static bool IsVisible();
584};
585```