## Views Dialog Class Splitting This document outlines how to refactor a common old Views design pattern into a cluster of smaller objects with less individual responsibility that are easier to test. The result is broadly similar to the model-view-controller paradigm but with some Views-specific differences. This document is specifically applicable to dialogs, bubbles, and secondary UI surfaces that have their own Widgets. The techniques described here work well for subclasses of: * `WidgetDelegate` * `DialogDelegate` * `BubbleDialogDelegate` This document generally uses `DialogDelegate` throughout. ### The Old Pattern: DialogDelegateView Controllers Legacy Views dialogs often have a class like this: ```c++ class MyDialogView : public views::DialogDelegateView, public SomeViewListener, public MyModelListener { public: MyDialogView(MyModel* model); ~MyDialogView() override; // DialogDelegate: std::u16string GetWindowTitle() const override; bool ShouldShowCloseButton() const override; ... // MyViewListener: void OnMyViewClicked(MyView* view) override; // MyModelListener: void OnMyModelChanged(MyModel* model) override; private: MyModel* model_; Label* status_; ... }; void MyDialogView::OnMyViewClicked(MyView* view) { // ... complex business logic ... model_->Update(new_state); } void MyDialogView::OnModelChanged(Model* model) { // ... complex presentation logic ... } ``` ### The Motivation & The Single Responsibility Principle The single responsibility principle is as follows: each class should have one responsibility. The class given above has several responsibilities: * It functions as a `DialogDelegate` for a given `Widget` * It functions as a `View` within that `Widget` * It handles translating user actions on the dialog to model updates * It handles translating model updates into visual changes in the dialog To make matters worse, classes of this pattern often do this in their constructor: ```c++ MyDialogView::MyDialogView(MyModel* model) { // ... lots of setup ... views::DialogDelegate::CreateDialogWidget(this, context, parent)->Show(); } ``` The last line of code of the constructor packs a lot of meaning: * `CreateDialogWidget` does or does not take ownership of `this`, depending on whether `MyDialogView` overrides `DeleteDelegate` * By default, `DialogDelegateView` uses itself as the contents view of the created widget * The created widget takes ownership of itself, and is shown on screen as a side-effect of the constructor Doing this makes classes of this pattern exceptionally difficult to test. ### The New Pattern: Decomposed Classes Here's how this class might look in "new style", using callbacks rather than observer/listener interfaces, and using composition instead of inheritance for both `View` and `DialogDelegate`: ```c++ class MyDialog { public: MyDialog(MyModel* model); ~MyDialog(); void Show(gfx::NativeWindow context, gfx::NativeView parent); private: void OnModelChanged(MyModel* model); void OnMyViewClicked(MyView* view); std::unique_ptr MakeContentsView(); std::unique_ptr MakeDialogDelegate(); base::CallbackListSubscription model_subscription_; std::unique_ptr delegate_; Widget* widget_ = nullptr; // if needed const Model* model_; // usually needed Label* status_ = nullptr; // or similar Views that are needed later }; MyDialog::MyDialog(MyModel* model) : model_(model) { model_subscription_ = model->RegisterUpdateCallback( base::BindRepeating(&MyDialog::OnModelChanged, base::Unretained(this))); delegate_ = MakeDialogDelegate(MakeContentsView()); } void MyDialog::Show(gfx::NativeWindow context, gfx::NativeView parent) { widget_ = views::DialogDelegate::CreateDialogWidget( std::move(delegate_), context, parent); widget_->Show(); // Or if we don't need to store widget_ we could just do: views::DialogDelegate::CreateDialogWidget( std::move(delegate_), context, parent)->Show(); } std::unique_ptr MyDialog::MakeContentsView() { // Create the contents view, set up its LayoutManager, create any needed // subviews, and store weak pointers to them. For example: auto contents = std::make_unique(); auto status = std::make_unique