這個設計並非武斷選擇。Canonical 在過去二十年大部分時間都在發布具有各種複雜應用程式的桌面。透過我們的研究,我們發現這五種主要類型在大多數應用程式中都是基礎,因此應該在此 API 中獲得一流的支援。最棒的是,作為開發人員,您可以放心地在所有主要桌面平台上發布這些視窗類型,因為您知道行為將始終一致。
// Create the class first...classMyWindowDelegatewithWindowControllerDelegate{@overridevoidonWindowDestroyed(){super.onWindowDestroyed)();ServicesBinding.instance.exitApplication)(AppExitType.required);}}// and then pass it to the controller constructor.finalcontroller=WindowController(title:'My Application',size:constSize)(800,600),delegate:MyWindowDelegate)(),);
classMyWidgetextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){finaltitle=WindowScope.titleOf)(context);// ... do something with the window title}}
每當視窗標題變更時,這個 Widget 現在都會重新渲染。
「Hello, Window」範例
現在我們了解了 API 及其背後的原理,以下這個獨立的「Hello, Window」範例應用程式應該完全可以理解。請隨意複製並貼上到您自己的 Flutter 專案中,自行嘗試:
dart
// ignore_for_file: invalid_use_of_internal_member// ignore_for_file: implementation_importsimport'dart:ui';import'package:flutter/services.dart';import'package:flutter/src/widgets/_window.dart';import'package:flutter/widgets.dart';/// Exits the application when the user closes the window.classExitOnCloseDelegatewithWindowControllerDelegate{@overridevoidonWindowCloseRequested(WindowControllercontroller){ServicesBinding.instance.exitApplication)(AppExitType.required);}}voidmain()下載{WidgetsFlutterBinding.ensureInitialized)();runWidget)(constHelloWindow)();}/// Displays a window and owns its [WindowController].classHelloWindowextendsStatefulWidget{constHelloWindow({super.key});@overrideState<HelloWindow>createState()=>_HelloWindowState)();}class_HelloWindowStateextendsState<HelloWindow>{finalWindowController_controller=WindowController(size:constSize)(600,400),title:'MyApp',delegate:ExitOnCloseDelegate)(),);@overridevoiddispose()>{_controller.dispose)();super.dispose)();}@overrideWidgetbuild(BuildContextcontext)>{returnWindow(controller:_controller,child:constDirectionality(textDirection:TextDirection.ltr,child:ColoredBox(color:Color(0xFFFFFFFF),child:Center(child:Text('Hello, Window下載,style:TextStyle(color:Color(0xFF000000),fontSize:24),),),),);}}
Dart 最初設計時,必須使用明確的 new 關鍵字來呼叫建構子。這是為了讓來自 C++、Java、JavaScript 和其他語言的使用者感到熟悉。其意圖是在程式碼中更清楚地標示何時呼叫會分配一個新物件。隨著垃圾收集器變得更好,使用者也更習慣於自動記憶體管理,大多數使用者發現 new 造成的噪音多於信號。
(老實說,Dart 一直透過支援工廠建構子來破壞這個信號。即使你用 new 調用工廠建構子,它也可以返回之前創建的物件。)
在 Dart 2.0 中,我們發布了一項語言變更,允許您在呼叫建構子時省略 new 關鍵字(以及許多地方的 const)。我們仍然支援舊的語法,所以這個語言變更本質上是語法糖,但我們真正保留舊語法只是為了向後相容性。
我們總是希望您使用新的簡短語法。我們提供了工具來自動移除不必要的 new 關鍵字,並且有 一個 lint 提醒您何時忘記。舊的語法實際上已經被棄用,隨著時間的推移,您會越來越少看到它。如果您是 Dart 的新手,您甚至可能沒有意識到我們支援在建構子呼叫中使用 new。
這意味著支援有 new 和沒有 new 的建構子呼叫的複雜性很低。現有使用者學習新語法需要一個過渡成本。但新使用者大多只會學習新方法,而不會遇到舊方法。在兩種語法之間選擇時幾乎沒有認知負擔,因為您只需始終使用新語法(如果沒有,我們的工具會輕輕地提醒您)。
除非有時光機可以回到過去並第一次就做對,否則這是我們為修復語言錯誤所能做的次優選擇。
對於常見的使用場景,語法可以好很多
在其公開存在的最初幾年,Dart 不支援 enum 宣告。該語言不允許你這樣寫:
1
enum Color { red, blue, yellow }
相反,您必須這樣寫:
1 2 3 4 5 6 7 8 9 10
classColor{ staticconst Color red = Color._(0, 'red'); staticconst Color blue = Color._(1, 'blue'); staticconst Color yellow = Color._(2, 'yellow');
上一節聽起來像是簡潔就是重點。我想在一個我們越來越多地按每個 token 的成本付費給 AI 代理來讀寫程式碼的世界中,這有直接的財務誘因。但這不僅僅是字元計數。考慮一下:
1 2 3 4 5 6 7 8 9 10
classColor{ staticconst Color red = Color('red', 0xff0000); staticconst Color blue = Color('blue', 0x0000ff); staticconst Color yellow = Color('yellow', 0xff00ff);
const Color(this.name, this.rgb);
finalString name; finalint rgb; }
這是一個列舉型別嗎?我的意思是「列舉型別」:使用這個類別的人是否應該假設他們唯一需要擔心的 Color 實例是 red、blue 或 yellow?
為了從建構子參數推斷實例欄位,使用者需要提供的唯一缺少的部分是該欄位是否應該是 final。允許在參數前加上 final 或 var 來控制這一點是相當自然的。兩者皆無修飾符則表示該參數根本不宣告實例欄位。這類似於 Scala 和 Kotlin 對 val 和 var 的處理方式。Dart 的結果如下:
classFormatterOptions{ finalint indent; finalint pageWidth; final Version? languageVersion; final TrailingCommas? trailingCommas; finalbool followLinks; final Show show; final Output output; final Summary summary; finalbool setExitIfChanged; finalList<String> experimentFlags;
然而,只有主要建構子才能存取參數上的 var 和 final 語法糖,該參數隱式宣告一個實例欄位並從該參數初始化它。這兩個功能——在類別標頭中宣告建構子和引導欄位的建構子參數——是捆綁在一起的。沒有前者就不能使用後者。
一般來說,我們在語言設計上盡力不讓使用者處於這樣一種境地:他們只想要兩種行為中的一種,但語言卻將它們綁定在一起,迫使他們接受兩種。例如,當我們添加類別修飾符時,我們特意添加了 final 和 sealed。它們非常相似,但 final 允許您防止子類化,而不必選擇啟用窮舉檢查。
決定捆綁什麼是一種平衡的行為。我相信,賦予每種程式語言其特性和適用於某些領域的很大一部分原因是它如何選擇將語義映射到語法。其中一部分是它如何將多種行為掛載到單個文本上。例如,在大多數物件導向語言中,使一個類別成為另一個類別的子類別,也使其成為靜態型別系統中的子型別。這並非絕對必要,正如 C++ 中的私有繼承所示。但對於大多數物件導向語言來說,這種耦合似乎是有意義的。
當談到將宣告參數與主要建構子結合時,這感覺是一個相對安全的捆綁。宣告參數本身就是語法糖,並且不能讓您表達任何您已經無法在普通類別宣告中表達的內容。如果您出於任何原因不想讓建構子成為主要建構子,您必須放棄宣告參數的語法便利性。但您放棄的只是簡潔性。您仍然可以按照您想要的方式定義您的類別,並具有完全符合您需求的 API 和語義。
這使我們得出結論,重複建構子的類別名稱在熟悉度或一致性方面並沒有為我們帶來太多好處。而且使用者必須付出代價。名稱通常很冗長,每個希望將建構子注入類別的人類、模板處理器、程式碼生成器、生成程式碼的 AI 代理或未來的元程式設計功能都需要知道要使用類別名稱。這幾乎就像每個類別都有自己特殊的小上下文關鍵字。
然而,使用 new 來定義建構子確實會導致一個奇怪的組合。如果您也希望該建構子是常量,則需要一個 const 修飾符:
1 2 3
classSomeClass{ constnew() { ... } }
我承認 const new 看起來自相矛盾。對於那些經歷過每個建構子調用都以 new 或 const 開頭的 Dart 使用者來說,更是如此。在那個世界裡,兩者是直接對立的。但現在你不再寫 new 來調用建構子,所以在我看來,new 關鍵字主要只是意味著「宣告一個建構子」,而 const 總是一個修飾符,意味著「使事物成為常量」。
總結
感謝您與我一起回顧這漫長的設計過程。我們花了這麼長時間仔細思考建構子的語義和語法的每一個細節,為 Dart 3.13 做準備,我還可以再寫五千字來討論它。我們曾考慮將主要建構子參數列表放在類別標頭中的 extends、implements 和 with 子句之後。我們就類別主體的每個角落進行了辯論,以決定主要建構子參數應該在哪個範圍內。我們是否應該允許在類別標頭中調用超類?這對 with 子句意味著什麼?如果您有一個名為 factory 的工廠建構子會發生什麼?
classTrafficLightMachineextendsStateMachine<TrafficLightModel> { final green = State('green', initial: true); final yellow = State('yellow'); final red = State('red');