【Chromium中文文档】跨平台开发的约定与模式

跨平台开发的约定与模式

转载请注明出处:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/Conventions_and_patterns_for_multi-platform_development.htmlhtml

全书地址前端

Chromium中文文档 for https://www.chromium.org/developers/design-documents
持续更新ing,欢迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zhlinux

Chromium是一个巨大而复杂的跨平台产品。咱们试图在不一样平台间共享尽量多的代码,同时为每一个平台用最合适的方式实现UI和操做系统集成。这提供了一个更好的用户体验,但它给代码增长了额外的复杂度。这个文档描述了保持这种跨平台代码简洁性的推荐实践。ios

咱们使用大量不一样带后缀的文件来表示一个文件应该被使用的时机:git

  • Mac文件中,低层级文件使用_mac后缀,Cocoa(Mac UI)文件使用_cocoa后缀。
  • iOS文件使用_ios后缀(尽快iOS使用一些特定的_mac文件)。
  • Linux文件中,低层级文件使用_linux后缀,GTK相关文件使用_gtk后缀,X Windows(不使用GTK)特定文件使用_x后缀。
  • Windows文件使用_win后缀。
  • Mac,iOS和Linux共享的Posix文件使用_posix后缀。
  • Chrome view UI相关布局系统文件(在Windows和实验室环境GTK上)使用_views后缀。

独立的浏览器后端文件放在他们本身的目录里:github

  • Mac Cocoa: chrome/browser/ui/cocoa
  • Linux GTK: chrome/browser/ui/gtk
  • Windows Views (和实验室GTK-views): chrome/browser/ui/views

编码风格 页面列出一些风格上影响平台相关定义的规则。chrome

如何隔离平台相关代码

小的平台差别: #ifdefs

当你有一个有着许多共享函数或数据成员和些许不一样之处的类,在平台相关的部分使用#ifdefs。若是没有显著的差别,这会让每一个人将每件事隔离开更加容易。后端

小的平台差别在头文件处理,大的差别在实现中处理:片断实现

可能有这样的状况,头文件几乎没有差异,部分实现有巨大的实现差别。例如base/waitable_event.h定义了一个通用的有着大量平台差别的API。浏览器

有着显著的实现差别,实现文件能够被隔离出来。这能够避免你陷入一个必须在include必要文件中为每一个平台写一大堆#ifdef,而且使得追踪源码更容易(三个版本的函数集的代码放在同一个文件里可能使人困惑)。每一个平台能够有不一样的.cc文件,正如base/waitable_event_posix.cc中实现posix相关函数。若是在这个类里有跨平台的函数,他们应该被丢到一个名为base/waitable_event.cc的函数。框架

全平台实现和调用器:隔离实现

当抽象层面没有东西实现,就要在每一个单独的文件里分别实现类。

若是全部的实现都在跨平台目录中,好比base,他们应该用平台的名字命名,好比base/foo_bar_win.h中的FooBarWin。这种例子一般不多,由于这些跨平台的文件一般设计用于跨平台代码,独立的头文件使得这种例子变得不可能。在一些地方,咱们已经在不一样的文件里定义了一个普通命名的类,因此PlatformDevice定义在skia/ext/platform_device_win.h, skia/ext/platform_device_linux.h, and skia/ext/platform_device_mac.h。若是你真的须要在跨平台代码里引用这个类,这是OK的。但一般,这种例子会变得遵循下面的这种规则。

若是实现存在于平台相关目录,好比chrome/browser/ui/cocoa或chrome/browser/ui/views,这个类就没有机会用于跨平台代码了。这种状况下,这个类和文件名应该忽略平台的名字,由于它是多余的。因此FooBar是在chrome/browser/ui/cocoa/foo_bar.h中实现的。

不要为每一个平台建立不一样的类,又把它们用typedef定义为同一个名字。咱们曾经在PlatformCanvas上使用这种套路,根据平台,它被typedef为PlatformCanvasMac, PlatformCanvasLinux, 或 PlatformCanvasWin。这样就不可能提早声明这个类,而这是一个减少依赖的重要工具。

何时使用抽象的接口

一般,抽象接口与工厂不该该做为隔离平台差别的惟一目的。相反的,它应该用于将接口与优化代码设计的实现隔离开来。这最常常出如今从model中抽离view的实现中,好比TabContentsView或者RenderWidgetHostView。在这些例子里,model不依赖view的实现是有必要的。在许多状况下,多个平台的view只有一个实现,但为未来的开发提供了更干净的隔离与更多的可扩展性。

在有些地方,像TabContentsView,抽象层没有非抽象的、在平台间共享的函数。避免这种写法。若是不一样view之间的代码老是同样,它可能首先就不该该在view中。

实现平台相关的UI

一般,从已有的平台相关的用户界面元素构建其余平台相关的用户界面元素。例如,view相关的类BrowserView负责构建许多浏览器对话框盒子。一种方法是,在一个平台无关的接口里包装UI元素,而后经过一个工厂,从一个model构造出它来。这是至关没必要要的,由于它让迷乱了归属关系:大多数工厂构造的例子里,UI元素最后归属于建立它的model。然而在许多例子里,UI元素最容易由它所属的UI框架管理。例如,一个views::View归属于它的view层级,而且在包含它的window被销毁时,会自动被销毁。若是有一个对话框 views::View实现了一个平台无关的接口,而后被另外一个对象拥有,那么views::View实例如今须要显式地告诉它的view层级不要去干涉它的生命周期。

e.g. 推荐这种写法:

// browser.cc:

Browser::ExecuteCommand(..) {
  ...
  case IDC_COMMAND_EDIT_FOO:
    window()->ShowFooDialog();
    break;
  ...
}

// browser_window.h:

class BrowserWindow {
...
  virtual void ShowFooDialog() = 0;
...
};

// browser_view.cc:

BrowserView::ShowFooDialog() {
  views::Widget::CreateWindow(new FooDialogView)->Show();
}

// foo_dialog_view.cc:

// FooDialogView和FooDialogController在window被关闭的时候会被自动清理
class FooDialogView : public views::View {
  ...
 private:
  scoped_ptr<FooDialogController> controller_; // 跨平台状态控制逻辑
  ...
}

不推荐这种

// browser.cc:

Browser::ExecuteCommand(..) {
  ...
  case IDC_COMMAND_EDIT_FOO: {
    FooDialogController::instance()->ShowUI();
    break;
  }
  ...
}

// foo_dialog_controller.h:

class FooDialog {
 public:
  static FooDialog* CreateFooDialog(FooDialogController* controller);
  virtual void Show() = 0;
  virtual void Bar() = 0;
};

class FooDialogController {
 public:
  ...
  static FooDialogController* instance() {
    static FooDialogController* instance = NULL;
    if (!instance)
      instance = Singleton<FooDialogController>::get();
    return instance;
  }
  ...
 private:
  ...
  void ShowUI() {
    if (!dialog_.get())
      dialog_.reset(FooDialog::CreateFooDialog(this));
    dialog_->Show();
  }

  // 为何要把FooDialog或者甚至FooDialogController放在外面?
  // 大多数dialog起始不多被用到
  scoped_ptr<FooDialog> dialog_;
};

// foo_dialog_win.cc:

class FooDialogView : public views::View,
                      public FooDialogController {
 public:
  ...
  explicit FooDialogView(FooDialogController* controller) {
    set_parent_owned(false); // Now necessary due to scoped_ptr in FooDialogController.
  }
  ...
};

FooDialog* FooDialog::CreateFooDialog(FooDialogController* controller) {
  return new FooDialogView(controller);
}

有时候后一种模式是必要的,但这些状况很稀少,而且很是容易被前端的团队所理解。移植的时候,若是UI元素有时候像dialog box同样简单的话,考虑把后一种模式转为前一种。

相关文章
相关标签/搜索