وقتی اسم «دارت» (Dart) میاد، شاید اولین چیزی که به ذهنتون برسه، فریم‌ورک محبوب فلاتر (Flutter) باشه. اما حقیقت اینه که دارت خودش یک دنیای جداگانه از ویژگی‌ها و قابلیت‌های منحصربه‌فرد داره که فلاتر رو به این قدرت رسونده. دارت نه یک کپی از جاوا یا جاوااسکریپت، بلکه نتیجه تلاش مهندسان برجسته گوگل برای حل چالش‌های واقعی توسعه رابط کاربری بوده.

در این مقاله، قصد نداریم فقط از مزایای کلی دارت حرف بزنیم. می‌خواهیم ۱۰ ویژگی‌ فنی و عمیق رو کنار هم بررسی کنیم که دارت رو به یک زبان خاص و قدرتمند تبدیل کرده. اگر کنجکاوید بدونید چه چیزهایی دارت رو از بقیه متمایز می‌کنه، با همراه باشید!

10 ویژگی‌ عجیبِ زبانِ دارت

۱. Hot Reload: جادوی توسعه سریع:

یکی از محبوب‌ترین قابلیت‌های دارت (و فلاتر)، Hot Reload هست. اما این قابلیت چطور کار می‌کنه و چرا اینقدر سریع و کارآمده؟ راز این سرعت در مدل کامپایل دوگانه دارت نهفته.

کامپایل جاست-این-تایم (JIT):

هات ریلود فلاتر و دارت

در فاز توسعه، دارت از کامپایلر JIT استفاده می‌کنه. یعنی کد شما همزمان با اجرا، خط به خط ترجمه و اجرا می‌شه. این روش به شما اجازه می‌ده تغییراتی که در کد اعمال می‌کنید رو بلافاصله در اپلیکیشن در حال اجرا ببینید، بدون نیاز به ری‌استارت کامل. این دقیقا همون چیزیه که Hot Reload رو ممکن می‌کنه و تجربه توسعه رو فوق‌العاده لذت‌بخش می‌کنه.

کامپایل اهد-آف-تایم (AOT):

اما وقتی نوبت به انتشار نسخه نهایی اپلیکیشن می‌رسه، دارت از کامپایلر AOT استفاده می‌کنه. این کامپایلر، کل کد شما رو به کد ماشین نیتیو (مثل ARM یا x86) تبدیل می‌کنه. نتیجه؟ اپلیکیشنی با عملکرد بی‌نظیر، سرعت بالا و بدون هیچ سربار اضافی. این دوگانگی در مدل کامپایل، دارت رو به یک انتخاب ایده‌آل برای توسعه اپلیکیشن‌های مدرن تبدیل کرده.

۲. Sound Null Safety: پایانی بر خطاهای Null:

javascript Bridge Knowxis

یکی از بزرگترین کابوس‌های برنامه‌نویسان، خطاهای NullPointerException یا معادل‌های آن در زبان‌های دیگر است. دارت با معرفی «Sound Null Safety» این مشکل را به طور کامل حل کرده است.

Null Safety واقعی یعنی چه؟

برخلاف برخی زبان‌ها که Null Safety در آن‌ها اختیاری یا محدود است، در دارت از نسخه ۲.۱۲ به بعد، این یک قانون بنیادین است. وقتی شما متغیری را مثل String name; تعریف می‌کنید، کامپایلر دارت تضمین می‌کند که این متغیر هرگز null نخواهد بود. این تضمین نه تنها در زمان کامپایل، بلکه در زمان اجرا (Runtime) نیز پابرجا است.

این ویژگی به معنای پایان خطاهای رایج ناشی از null است. مگر اینکه خودتان صریحاً با علامت ! به کامپایلر اعلام کنید که از null نبودن متغیر مطمئنید، در غیر این صورت، دارت اجازه نمی‌دهد کدی بنویسید که منجر به null شدن ناخواسته متغیرها شود. این قابلیت پایداری و امنیت کد شما را به شدت افزایش می‌دهد و به کامپایلر اجازه می‌دهد کدهای بهینه‌تری تولید کند.

۳. Isolates: معماری بدون مشکلات Thread:

مدیریت همزمانی (Concurrency) در برنامه‌نویسی همیشه چالش‌برانگیز بوده. دارت با مفهوم «Isolates» رویکردی کاملاً متفاوت و کارآمد ارائه می‌دهد.

چرا Isolates بهتر از Threadها هستند؟

در زبان‌هایی مثل جاوا، Threadها حافظه مشترک دارند که منجر به مشکلاتی مانند Race Condition و نیاز به قفل‌گذاری (Mutexes, Semaphores) می‌شود. از طرف دیگر، زبان‌هایی مانند جاوااسکریپت تک‌رشته‌ای هستند و عملیات سنگین می‌توانند رابط کاربری را قفل کنند.

Isolates در دارت، برخلاف Threadها، حافظه مشترکی ندارند. هر Isolate مانند یک فرایند مستقل عمل می‌کند که حافظه و منابع خاص خودش را دارد. این یعنی هیچ Isolateای نمی‌تواند به حافظه Isolate دیگر دسترسی مستقیم داشته باشد، که به طور کامل مشکلات مربوط به همزمانی و Race Condition را از بین می‌برد.

ارتباط بین Isolates‌ها:

اگر Isolates حافظه مشترک ندارند، پس چطور با هم ارتباط برقرار می‌کنند؟ از طریق «Message Passing». یک Isolate پیامی (معمولاً یک کپی از داده) را به Isolate دیگر ارسال می‌کند و Isolate دریافت‌کننده آن را در حافظه خود کپی و پردازش می‌کند. این مدل باعث می‌شود رابط کاربری همیشه پاسخگو باقی بماند، زیرا عملیات سنگین را می‌توان به Isolates جداگانه واگذار کرد و نتیجه را به صورت غیرهمزمان دریافت کرد.

۴. عملگر آبشاری (Cascade Notation): کدنویسی روان‌تر:

یکی از ویژگی‌های سینتکسی جذاب دارت، عملگر آبشاری یا .. است. این عملگر به شما اجازه می‌دهد چندین عملیات را روی یک شیء واحد، بدون نیاز به ارجاع مجدد به آن شیء، انجام دهید.

مثال عملی از عملگر آبشاری:

فرض کنید کلاسی به اسم Person دارید و می‌خواهید یک شیء از آن ساخته و چندین ویژگی آن را مقداردهی کنید. بدون عملگر آبشاری، کد شما ممکن است به این شکل باشد:

void main() {
  var p1 = Person();
  p1.name = "Ali";
  p1.age = 30;
  p1.sayHello();
}

با استفاده از عملگر آبشاری، می‌توانید همین عملیات را به شکلی بسیار کوتاه‌تر و خواناتر بنویسید:

void main() {
  var p2 = Person()
    ..name = "Ali"
    ..age = 30
    ..sayHello();
}

این قابلیت به خصوص در فلاتر هنگام ساخت ویجت‌ها بسیار کاربردی است و کد را تمیزتر و قابل فهم‌تر می‌کند.

۵. Extension Methods: افزودن قابلیت بدون وراثت:

Extension Methods به شما اجازه می‌دهند تا قابلیت‌های جدیدی را به کلاس‌های موجود اضافه کنید، حتی اگر به کد منبع آن کلاس دسترسی نداشته باشید یا نخواهید از وراثت استفاده کنید. این یک راهکار عالی برای تمیز نگه داشتن کد و افزایش قابلیت استفاده مجدد است.

چرا Extension Methods مهم هستند؟

فرض کنید می‌خواهید یک متد جدید به کلاس String اضافه کنید تا مثلاً اولین حرف یک رشته را بزرگ کند. بدون Extension Methods، باید یک تابع کمکی جداگانه می‌نوشتید یا از وراثت (که برای کلاس‌های نهایی مثل String ممکن نیست) استفاده می‌کردید.

با Extension Methods، می‌توانید این کار را به سادگی انجام دهید:

extension StringExtension on String {
  String capitalize() {
    if (this.isEmpty) return this;
    return "${this[0].toUpperCase()}${this.substring(1)}";
  }
}

void main() {
  String message = "hello world";
  print(message.capitalize()); // Output: Hello world
}

این ویژگی کد شما را خواناتر و ماژولارتر می‌کند.

۶. Generics: کدنویسی ایمن و قابل استفاده مجدد:

Generics در دارت به شما اجازه می‌دهند تا کدی بنویسید که با انواع داده‌ای مختلف کار کند، بدون اینکه ایمنی نوع (Type Safety) را از دست بدهید. این ویژگی برای ساخت کانتینرها و الگوریتم‌های عمومی ضروری است.

مزایای استفاده از Generics:

تصور کنید می‌خواهید یک لیست ایجاد کنید که فقط اعداد صحیح را بپذیرد. بدون Generics، ممکن است مجبور شوید لیست را از نوع Object تعریف کنید و در زمان اجرا بررسی کنید که آیا آیتم‌ها از نوع صحیح هستند یا خیر، که منجر به خطاهای زمان اجرا می‌شود.

با Generics، می‌توانید به راحتی یک لیست از نوع int تعریف کنید:

List<int> numbers = [1, 2, 3];
// numbers.add("four"); // این خط منجر به خطای کامپایل می‌شود

این کار نه تنها کد شما را ایمن‌تر می‌کند، بلکه خوانایی آن را نیز افزایش می‌دهد و اجازه می‌دهد تا کامپایلر بهینه‌سازی‌های بیشتری انجام دهد.

۷. Async/Await: مدیریت عملیات ناهمزمان آسان:

دارت با استفاده از کلمات کلیدی async و await، مدیریت عملیات ناهمزمان (مثل درخواست‌های شبکه یا خواندن فایل) را بسیار ساده کرده است. این ویژگی به شما اجازه می‌دهد کدی شبیه به کد همزمان بنویسید، در حالی که در پس‌زمینه عملیات به صورت ناهمزمان اجرا می‌شوند.

ساده‌سازی کد ناهمزمان:

به جای استفاده از Callbacks پیچیده یا Promise Chains طولانی، می‌توانید از async و await برای خوانایی بهتر و مدیریت خطای ساده‌تر استفاده کنید:

Future<String> fetchData() async {
  String data = await Future.delayed(Duration(seconds: 2), () => "Data fetched!");
  return data;
}

void main() async {
  print("Starting data fetch...");
  String result = await fetchData();
  print(result);
  print("Finished.");
}

این الگو به شما کمک می‌کند تا برنامه‌هایی بنویسید که در عین حال که عملیات سنگین را انجام می‌دهند، رابط کاربری آن‌ها قفل نشود و تجربه کاربری روان و دلپذیری ارائه دهند.

۸. Callable Classes: رفتار شبیه تابع:

در دارت، می‌توانید کلاس‌هایی ایجاد کنید که رفتارشان شبیه به یک تابع باشد. این کار با پیاده‌سازی متد call() در کلاس انجام می‌شود. این ویژگی برای سناریوهایی که می‌خواهید یک شیء را به صورت یک تابع فراخوانی کنید، بسیار مفید است.

کاربرد Callable Classes:

فرض کنید کلاسی دارید که وظیفه پردازش یک رشته را بر عهده دارد:

class StringProcessor {
  String call(String input) {
    return input.toUpperCase();
  }
}

void main() {
  var processor = StringProcessor();
  print(processor("hello")); // Output: HELLO
}

این قابلیت به شما انعطاف‌پذیری بیشتری در طراحی APIها و الگوهای برنامه‌نویسی می‌دهد.

۹. Typedefs: نام مستعار برای توابع:

Typedefs در دارت به شما اجازه می‌دهند تا یک نام مستعار (alias) برای انواع توابع (Function Types) ایجاد کنید. این کار به افزایش خوانایی کد و کاهش تکرار کمک می‌کند، به خصوص زمانی که با توابع Callback یا پارامترهای تابع پیچیده سروکار دارید.

چگونه از Typedefs استفاده کنیم؟

فرض کنید می‌خواهید یک تابع Callback تعریف کنید که یک int می‌گیرد و یک String برمی‌گرداند:

typedef Converter = String Function(int number);

String intToString(int n) => n.toString();
String intToHex(int n) => n.toRadixString(16);

void process(int value, Converter converter) {
  print(converter(value));
}

void main() {
  process(10, intToString);
  process(255, intToHex);
}

با استفاده از typedef، کد شما تمیزتر و درک آن آسان‌تر می‌شود.

۱۰. Mixin ها: به اشتراک گذاری کد بدون وراثت چندگانه:

دارت از وراثت چندگانه (Multiple Inheritance) پشتیبانی نمی‌کند، اما با استفاده از مفهوم Mixin ها، راهی قدرتمند برای به اشتراک گذاری کد بین کلاس‌ها فراهم می‌کند. Mixin ها مجموعه‌ای از متدها و متغیرها را تعریف می‌کنند که می‌توانند به کلاس‌ها «اضافه» شوند.

قدرت Mixin ها:

فرض کنید می‌خواهید قابلیت «لاگ کردن» (Logging) را به چندین کلاس مختلف اضافه کنید، بدون اینکه آن‌ها از یک کلاس پایه مشترک ارث ببرند:

mixin Logger {
  void log(String message) {
    print("[LOG] $message");
  }
}

class User with Logger {
  String name;
  User(this.name);
  void sayHello() {
    log("User $name said hello.");
  }
}

class Product with Logger {
  String title;
  Product(this.title);
  void displayInfo() {
    log("Displaying info for product $title.");
  }
}

void main() {
  var user = User("Alice");
  user.sayHello();

  var product = Product("Laptop");
  product.displayInfo();
}

Mixin ها راهی انعطاف‌پذیر برای افزودن رفتار به کلاس‌ها ارائه می‌دهند و به شما کمک می‌کنند تا کدی ماژولارتر و قابل نگهداری‌تر بنویسید.

سوالات متداول درباره دارت:

آیا یادگیری دارت سخت است؟

خیر، دارت یک زبان مدرن با سینتکسی شبیه به C++, Java و JavaScript است. اگر با این زبان‌ها آشنایی داشته باشید، یادگیری دارت برایتان بسیار آسان خواهد بود. همچنین، منابع آموزشی فراوانی برای دارت و فلاتر وجود دارد که می‌توانید از آن‌ها استفاده کنید. برای شروع می‌توانید به مراجعه کنید.

چرا باید از دارت استفاده کنیم؟

دارت به دلیل سرعت بالا (کامپایل AOT)، تجربه توسعه سریع (Hot Reload)، ایمنی بالا (Sound Null Safety) و معماری کارآمد برای رابط کاربری (Isolates)، انتخابی عالی برای توسعه اپلیکیشن‌های موبایل، وب و دسکتاپ با فلاتر است. این زبان به شما کمک می‌کند تا با یک کدبیس واحد، خروجی‌های مختلفی تولید کنید.

نتیجه‌گیری:

همانطور که دیدید، دارت فقط یک زبان برنامه‌نویسی ساده نیست؛ مجموعه‌ای از ویژگی‌های هوشمندانه و طراحی شده برای حل مشکلات واقعی توسعه نرم‌افزار است. از Hot Reload که سرعت توسعه را متحول می‌کند تا Sound Null Safety که پایداری کد را تضمین می‌کند و Isolates که مدیریت همزمانی را ساده می‌سازد، هر جنبه‌ای از دارت با دقت طراحی شده تا تجربه توسعه‌دهنده را بهبود بخشد.

اگر به دنبال زبانی هستید که هم قدرت و عملکرد بالا داشته باشد و هم تجربه توسعه لذت‌بخشی را ارائه دهد، دارت و فلاتر قطعاً ارزش کاوش را دارند. به سر بزنید و دانش برنامه‌نویسی خود را ارتقا دهید!


String name;
برنامه نویسی دارت
آموزش دارت

void main() {
  // ### مثال ۱: روش سنتی (بدون عملگر آبشاری)

  var p1 = Person();
  p1.name = "Ali";
  p1.age = 30;
  p1.sayHello();
}

void main() {
  // ### مثال ۲: روش دارت (با عملگر آبشاری ..)

  var p2 = Person()
    ..name = "Ali"
    ..age = 30
    ..sayHello();
}

Utils.parseInt(myString)

extension NumberPars on String { int toInt() { return int.parse(this); } }

void main() {
  var myString = "123";
  var myInt = myString.toInt();
}

// روش سنتی و تودرتو
Padding(
  padding: const EdgeInsets.all(10.0),
  child: MyWidget(),
)

import 'package:flutter/material.dart';

extension PaddingExtension on Widget {
  
  Widget withPadding(double padding) {
    return Padding(
      padding: EdgeInsets.all(padding),
      child: this,
    );
  }
}
برنامه نویسی دارت

Color.fromHex('#FF0000')

(String, String) getUserName() {
  return ('Ali', 'Rezayi');
}

void main() {
  var user = getUserName();

  print(user.$1); // Prints 'Ali'
  print(user.$2); // Prints 'Rezayi'
}

({String firstName, String lastName}) getUserName() {
  return (firstName: 'Ali', lastName: 'Rezayi');
}

void main() {
  var user = getUserName();

  print(user.firstName); // Prints 'Ali'
  print(user.lastName);  // Prints 'Rezayi'
}

void main() {
  var command = ['delete', 'user.txt'];

  switch (command) {
    case ['delete', var fileName]:
      print('Deleting $fileName...');
      break;

    case ['copy', var from, var to]:
      print('Copying $from to $to...');
      break;

    default:
      print('Unknown command');
  }
}

sealed class Shape {}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

class Square implements Shape {
  final double side;
  Square(this.side);
}

void main() {
  Shape shape = Circle(10.5);

  switch (shape) {
    case Circle(radius: var r):
      print('This is a circle with radius $r');
      break;

    case Square(side: var s):
      print('This is a square with side $s');
      break;
  }
}

// This code will cause an ERROR in Sound Null Safety:

class MyWidget {
  
  // ERROR: The non-nullable field 'name' must be initialized.
  String name; 
  MyWidget();
}

// THE SOLUTION: Using the 'late' keyword

class MyWidget {

  late String name; 

  MyWidget();

  void loadDataFromServer() {
    name = "Data loaded successfully!";
  }

  void displayName() {
    print(name);
  }
}