Mac用のTrue Imageの設計および作成方法

みなさんこんにちは。 Mac OS向けにTrue Imageを実行するために必要なことを学習したら、 それが通常起こるように、あなたはそれを迅速かつ効率的に行う必要があります、ええ。 すぐに合理的な疑問が生じました。Qtで書かれたインターフェイスを含め、ほとんどのコードがすでにクロスプラットフォームであるため、True Image for Windows for Macをコンパイルするだけではどうでしょうか。 しかし、フレームはすぐにマークされました:



インターフェイスを完全に新しいものにすることが決定され、兄のそれよりも何倍も簡単になりました。 また、Macでの経験が重要であるため、ParallelsのスタッフはQtの代わりにネイティブのCocoaをGUIフレームワークとして使用することをアドバイスし、別の有名な会社の人々がこのソリューションの正確さを確認しました。 彼らは自分の経験に疑問を持たないことに決めました。



最終的に、Cocoaのフロントエンドを既存のコードに書き込もうとすることになりました。 製品をリリースし、すでにHabréそれについて書いています。今日、このプロセスのアーキテクチャと技術の詳細を共有したいと思います。






パッシブビュー



彼らはパッシブビューパターンを新しいアーキテクチャの中心に置くことにしました。その初期の説明はFowlerから読むことができます。

パターン自体はいシンプルです。 MVC / MVPの古典的なトライアドのように、ビュー、モデル、およびプレゼンター(他の用語ではコントローラー)があります。 他の同様のパターンとの違いは、名前が示すように、ビューは「受動的」または単純な方法では「ダム」であるということです。モデルについては何も知らず、プレゼンターはモデルとタイプのすべての調整を担当します。







なぜまさにこのアプローチなのか?

  1. テスト可能性は、このパターンの最大のプラスです。 ビューとモデルは分離されており、変更に署名したオブザーバーに関するものを除き、外部の世界については何も知りません。 プレゼンターは、依存症の注入を通じて、外部からほぼすべての知識を受け取ります。 フォームの実装用のテストを記述したり、モデルの実装用のテストを記述したり、プレゼンターのロジックの正しい動作に関するテストを記述したりできます。
  2. 明快さ-特定の作品のすべてのロジックが1か所-プレゼンターに集中しており、タイプごとに分散されていません。
  3. 再利用可能性と構成可能性-プレゼンターはインターフェースを介してビューとモデルを操作するため、プレゼンターで設計された同じロジックを使用して、プログラムのさまざまな場所で使用できます。


コンポーネントは次のように相互作用します。プレゼンターはビューを調整し、ビューとモデルのイベントをサブスクライブし、ビューを表示し、モデルとビューのイベントを処理します。







このパターン自体は、最も自己のふりをするものではありません。データがビュー自体によって描画される場合、MVCアプローチなどを使用すると、特定のことを行う方が何倍も便利です。 たとえば、この方法では、回復ダイアログでファイルブラウザが作成されました。 パッシブビューは、ビューへのデータフローがあまりない場合に適しています。





コード!



階層で編成したタイプとプレゼンター。 メインウィンドウのプレゼンターは、イベントハンドラーで他​​のプレゼンターを生成し、その役割を果たします。 概念的には、すべて次のようになります。



struct ModelObserver { // various callbacks virtual void OnModelChanged() = 0; } struct Model : Observable<ModelObserver> { // virtual getters, setters, etc } struct ViewObserver { // various callbacks virtual void OnViewButtonClicked() = 0; } struct View : Observable<ViewObserver> { // virtual setters, etc virtual void Show() = 0; //     event loop,    QDialog::exec() } struct PresenterParent : ModelObserver, ViewObserver { Model M; // injected in ctor View V; // injected in ctor void Run() { M.AttachObserver(this); V.AttachObserver(this); V.Show(); } void OnModelChanged() { //    V.SetSomething(M.GetSomething()); } void OnViewButtonClicked() { //     //     V      //       ViewFactory      PresenterChild p(M, V.CreateChildView()); p.Run(); } } void main(argc, argv) { Model m(CreateModel()); // -  ,   View v(CreateParentView()); // -  ,  Qt  Cocoa PresenterParent p(m, v); p.Run(); }
      
      





締め切りがかなり厳しかったため、これらの注入はすべて最初から非常に役立ち、作業を並列化できました。モデルをスタブに置き換え、本格的なモデルが並列に実装されている間にインターフェイスの動作をテストするために最善を尽くしました。 スタブ自体は、単体テストに再利用できます。



ある時点で、モデルの抽象性は、いくつかのサブシステムのホイールを再発明するのではなく、True Image for Windowsのすべての低レベルおよび中レベルのロジックを使用するという(右の)決定が下されたときに、プロジェクトの期限を守ると言えます。 その結果、モデルはファサードまたは既存のロジックレイヤーへのアダプターによってさまざまな厚さで実装され、True Imageの両方のバージョンは、Macのみで発生した古代のバグの修正を含む、これに依存するすべてのボーナスを受け取りましたMSVCよりもGCCにあります)。





ココアを留める



ネイティブのココアをこの構造にどのようにねじ込んだかを言及する価値があります。誰かが役に立つかもしれません。 Objective-C ++およびARCを使用し、Interface Builderでペイントされたウィンドウ。 プロセスは次のとおりです。



  1. xibウィンドウとそのobj-c ++コントローラーを作成します。ほとんどの場合、バインダーを使用してウィンドウの状態を制御します



     @interface ViewCocoa : NSWindowController { Observable<ViewObservable>* Callbacks; } @property NSNumber* Something; - (id)initWithObservable:(Observable<ViewObservable>*)callbacks; - (IBAction)OnButtonClicked:(id)sender; @end @implementation ViewCocoa { - (id)initWithObservable:(Observable<ViewObservable>*)callbacks { if (self = [super initWithWindowNibName:@"ViewCocoa"]) { Callbacks = callbacks; } return self; } } - (IBAction)OnButtonClicked:(id)sender { //       Callbacks->NotifyObservers(bind(&ViewObserver::OnViewButtonClicked, _1)); } @end
          
          





  2. obj-c ++アダプターを作成します。これは、プレゼンターに既に挿入できます。



     struct ViewCocoaAdapter : View { ViewCocoa* Adaptee = [[ViewCocoa alloc] initWithObservable:this]; virtufal void Show() { //            [NSApp runModalForWindow:Adaptee.window]; } //          Adaptee virtual void SetContent(int something) { //   ,    performSelectorOnMainThread,      [Adaptee performSelectorOnMainThread:@selector(setSomething:) withObject:[NSNumber numberWithInt:something] waitUntilDone:NO]; } }
          
          







ボーナスコマンドラインインターフェース



種の抽象性と受動性により、代替CLIインターフェースを作成することが可能になりました。これは、Mac用の自動テストに積極的に使用されています。 タイプごとにビジネスロジックなしで1つのクラスのみを実装すれば十分なので、メンテナンスは非常に簡単です。



 struct ViewCli : View { virtual void Show() { for (;;) { //  ,  -  std::string cmd; std::cin >> cmd; if (cmd == "ls") { std::cout << "  ,       ..." << std::endl; } else if (cmd == "x") { break; } else if (cmd == "click") { NotifyObservers(bind(&ViewObserver::OnViewButtonClicked, _1)); } } } //   ,   ,           "ls" }
      
      







マルチスレッド



当初から、1つの重要な仮定を立てました。すべての型をスレッドセーフと見なすことです。 これにより、プレゼンターのコードが大幅に簡素化されました。 トリックは、ほとんどすべてのGUIフレームワークがメインGUIスレッドで非同期操作を実行する能力を持ち、これを利用したことです。





長所







短所







一般的な印象



Freestuff CLIはクールです! 自動テストの実行を開始した場合。 しかし、調整すれば、本当にクールです。



設計者がインターフェイスの大部分を徹底的に再描画することを決めたときなど、選択されたアプローチにより、何度もコードを書き直す必要がなくなりました。 コードの変更は、ほとんどの場合ビュークラスの実装のみに限定され、ほとんどすべてのビジネスロジックは変更されませんでした。 同時に、私の意見によると、パッシブビューは中小規模のアプリケーションに適しています-大規模なアプリケーションでは、柔軟性の利点は、ユーザーインターフェイス自体を拡張するための高コスト/複雑さの不足を上回らないように思われます。



プロジェクトでどのようなアプローチを使用していますか?



All Articles