لغة Dart: عندما لا تكون Java و C # شاربين بدرجة كافية

نشرت: 2022-03-11

بالعودة إلى عام 2013 ، حصل الإصدار 1.0 الرسمي من Dart على بعض الصحافة - كما هو الحال مع معظم عروض Google - ولكن لم يكن الجميع متحمسين مثل فرق Google الداخلية لإنشاء تطبيقات الأعمال المهمة باستخدام لغة Dart. مع إعادة بناء Dart 2 المدروسة جيدًا بعد خمس سنوات ، يبدو أن Google قد أثبتت التزامها باللغة. في الواقع ، تستمر اليوم في اكتساب قوة جذب بين المطورين - وخاصة قدامى المحترفين في Java و C #.

تعتبر لغة البرمجة Dart مهمة لعدة أسباب:

  • إنه يحتوي على أفضل ما في العالمين: إنها لغة مجمعة وآمنة من النوع (مثل C # و Java) ولغة برمجة نصية (مثل Python و JavaScript) في نفس الوقت.
  • ينتقل إلى JavaScript لاستخدامه كواجهة أمامية للويب.
  • يتم تشغيله على كل شيء ، ويتم تجميعه في تطبيقات الأجهزة المحمولة الأصلية ، بحيث يمكنك استخدامه لأي شيء تقريبًا.
  • تشبه Dart لغة C # و Java في بناء الجملة ، لذا فهي سريعة التعلم.

أولئك منا من عالم C # أو Java لأنظمة المؤسسات الأكبر يعرفون بالفعل سبب أهمية أمان الكتابة وأخطاء وقت التجميع والنُسَج. يتردد الكثير منا في تبني لغة "نصية" خوفًا من فقدان كل البنية والسرعة والدقة وإمكانية التصحيح التي اعتدنا عليها.

ولكن مع تطوير Dart ، لا يتعين علينا التخلي عن أي من ذلك. يمكننا كتابة تطبيق جوال وعميل ويب ونهاية خلفية بنفس اللغة - والحصول على كل الأشياء التي ما زلنا نحبها في Java و C #!

تحقيقًا لهذه الغاية ، دعنا نتصفح بعض الأمثلة الرئيسية للغة Dart والتي ستكون جديدة على مطور C # أو Java ، والتي سنلخصها في ملف PDF بلغة Dart في النهاية.

ملاحظة: تتناول هذه المقالة فقط Dart 2.x. لم يكن الإصدار 1.x "مطبوخًا بالكامل" - على وجه الخصوص ، كان نظام الكتابة استشاريًا (مثل TypeScript) بدلاً من المطلوب (مثل C # أو Java).

1. منظمة الكود

أولاً ، سوف ندخل في أحد أهم الاختلافات: كيفية تنظيم ملفات التعليمات البرمجية والإشارة إليها.

ملفات المصدر والنطاق ومساحات الأسماء والواردات

في C # ، يتم تجميع مجموعة من الفئات لتجميع. تحتوي كل فئة على مساحة اسم ، وغالبًا ما تعكس مساحات الأسماء تنظيم التعليمات البرمجية المصدر في نظام الملفات — ولكن في النهاية ، لا يحتفظ التجميع بأي معلومات حول موقع ملف التعليمات البرمجية المصدر.

في Java ، تعد الملفات المصدر جزءًا من حزمة وعادة ما تتوافق مساحات الأسماء مع موقع نظام الملفات ، ولكن في النهاية ، الحزمة هي مجرد مجموعة من الفئات.

لذلك كلتا اللغتين لديهما طريقة للحفاظ على شفرة المصدر مستقلة إلى حد ما عن نظام الملفات.

على النقيض من ذلك ، في لغة Dart ، يجب على كل ملف مصدر استيراد كل ما يشير إليه ، بما في ذلك ملفات المصدر الأخرى وحزم الجهات الخارجية. لا توجد مساحات أسماء بنفس الطريقة ، وغالبًا ما تشير إلى الملفات عبر موقع نظام الملفات الخاص بها. يمكن أن تكون المتغيرات والوظائف عالية المستوى ، وليس مجرد فئات. بهذه الطرق ، يكون Dart أشبه بالسيناريو.

لذلك سوف تحتاج إلى تغيير تفكيرك من "مجموعة من الفئات" إلى شيء يشبه "سلسلة من ملفات التعليمات البرمجية المضمنة".

تدعم Dart كلاً من تنظيم الحزم والتنظيم المخصص بدون حزم. لنبدأ بمثال بدون حزم لتوضيح تسلسل الملفات المضمنة:

 // file1.dart int alice = 1; // top level variable int barry() => 2; // top level function var student = Charlie(); // top level variable; Charlie is declared below but that's OK class Charlie { ... } // top level class // alice = 2; // top level statement not allowed // file2.dart import 'file1.dart'; // causes all of file1 to be in scope main() { print(alice); // 1 }

يجب التصريح عن كل ما تشير إليه في ملف المصدر أو استيراده داخل هذا الملف ، حيث لا يوجد مستوى "مشروع" ولا توجد طريقة أخرى لتضمين عناصر مصدر أخرى في النطاق.

الاستخدام الوحيد لمساحات الأسماء في Dart هو إعطاء اسم للواردات ، وهذا يؤثر على كيفية إحالتك إلى التعليمات البرمجية المستوردة من هذا الملف.

 // file2.dart import 'file1.dart' as wonderland; main() { print(wonderland.alice); // 1 }

الحزم

الأمثلة أعلاه تنظم التعليمات البرمجية بدون حزم. من أجل استخدام الحزم ، يتم تنظيم الكود بطريقة أكثر تحديدًا. فيما يلي مثال على تخطيط حزمة لحزمة تسمى apples :

  • apples/
    • pubspec.yaml —يعرِّف اسم الحزمة والتبعيات وبعض الأشياء الأخرى
    • lib/
      • apples.dart - الواردات والصادرات. هذا هو الملف الذي تم استيراده من قبل أي مستهلك للحزمة
      • src/
        • seeds.dart —جميع التعليمات البرمجية الأخرى هنا
    • bin/
      • runapples.dart على الوظيفة الرئيسية ، وهي نقطة الدخول (إذا كانت هذه حزمة قابلة للتشغيل أو تتضمن أدوات قابلة للتشغيل)

ثم يمكنك استيراد الحزم الكاملة بدلاً من الملفات الفردية:

 import 'package:apples';

يجب دائمًا تنظيم التطبيقات غير التافهة كحزم. هذا يخفف الكثير من الاضطرار إلى تكرار مسارات نظام الملفات في كل ملف مرجعي ؛ بالإضافة إلى أنها تعمل بشكل أسرع. كما أنه يجعل من السهل مشاركة الحزمة الخاصة بك على pub.dev ، حيث يمكن للمطورين الآخرين الحصول عليها بسهولة لاستخدامهم الخاص. ستتسبب الحزم التي يستخدمها تطبيقك في نسخ الكود المصدري إلى نظام الملفات الخاص بك ، بحيث يمكنك تصحيح الأخطاء في تلك الحزم بعمق كما يحلو لك.

2. أنواع البيانات

هناك اختلافات كبيرة في نظام نوع Dart يجب أن تكون على دراية بها ، فيما يتعلق بالقيم الخالية والأنواع الرقمية والمجموعات والأنواع الديناميكية.

لاغية في كل مكان

نظرًا لأننا قادمون من C # أو Java ، فقد اعتدنا على أنواع بدائية أو قيم مختلفة عن أنواع المرجع أو الكائنات . يتم تخصيص أنواع القيمة ، في الممارسة العملية ، في المكدس أو في السجلات ، ويتم إرسال نسخ من القيمة كمعلمات دالة. بدلاً من ذلك ، يتم تخصيص أنواع المراجع في كومة الذاكرة المؤقتة ، ويتم إرسال المؤشرات إلى الكائن فقط كمعلمات دالة. نظرًا لأن أنواع القيم تشغل الذاكرة دائمًا ، لا يمكن أن يكون متغير مكتوب القيمة فارغًا ، ويجب أن يكون لجميع أعضاء نوع القيمة قيم مهيأة.

يزيل Dart هذا التمييز لأن كل شيء هو كائن ؛ جميع الأنواع مشتقة في النهاية من نوع Object . إذن ، هذا قانوني:

 int i = null;

في الواقع ، تتم تهيئة جميع العناصر الأولية ضمنيًا إلى null . هذا يعني أنه لا يمكنك افتراض أن القيم الافتراضية للأعداد الصحيحة هي صفر كما اعتدت في C # أو Java ، وقد تحتاج إلى إضافة تدقيقات فارغة.

ومن المثير للاهتمام ، أنه حتى Null هو نوع ، والكلمة null تشير إلى مثيل Null :

 print(null.runtimeType); // prints Null

ليس العديد من أنواع الأرقام

على عكس المجموعة المألوفة لأنواع الأعداد الصحيحة من 8 إلى 64 بت مع نكهات موقعة وغير موقعة ، فإن نوع العدد الصحيح الرئيسي لـ Dart هو int فقط ، قيمة 64 بت. (هناك أيضًا BigInt للأعداد الكبيرة جدًا.)

نظرًا لعدم وجود مصفوفة بايت كجزء من بناء جملة اللغة ، يمكن معالجة محتويات الملف الثنائي كقوائم من الأعداد الصحيحة ، أي List<Int> .

إذا كنت تعتقد أن هذا يجب أن يكون غير فعال بشكل رهيب ، فقد فكر المصممون في ذلك بالفعل. في الممارسة العملية ، هناك تمثيلات داخلية مختلفة تعتمد على قيمة العدد الصحيح الفعلي المستخدمة في وقت التشغيل. لا يخصص وقت التشغيل ذاكرة الكومة لكائن int إذا كان بإمكانه تحسين ذلك بعيدًا واستخدام سجل وحدة المعالجة المركزية في الوضع غير المعبأ. أيضًا ، تقدم مكتبة byte_data UInt8List وبعض التمثيلات المحسّنة الأخرى.

المجموعات

المجموعات والأدوية تشبه إلى حد كبير ما اعتدنا عليه. الشيء الرئيسي الذي يجب ملاحظته هو أنه لا توجد مصفوفات ذات حجم ثابت: ما عليك سوى استخدام نوع بيانات List أينما كنت ستستخدم مصفوفة.

أيضًا ، هناك دعم نحوي لتهيئة ثلاثة من أنواع المجموعات:

 final a = [1, 2, 3]; // inferred type is List<int>, an array-like ordered collection final b = {1, 2, 3}; // inferred type is Set<int>, an unordered collection final c = {'a': 1, 'b': 2}; // inferred type is Map<string, int>, an unordered collection of name-value pairs

لذلك ، استخدم Dart List حيث ستستخدم مصفوفة Java أو ArrayList أو Vector ؛ أو مجموعة C # أو List . استخدم Set حيث يمكنك استخدام Java / C # HashSet . استخدم Map حيث يمكنك استخدام Java HashMap أو Dictionary C #.

3. الكتابة الديناميكية والثابتة

في اللغات الديناميكية مثل JavaScript و Ruby و Python ، يمكنك الإشارة إلى الأعضاء حتى لو لم يكونوا موجودين. هذا مثال على JavaScript:

 var person = {}; // create an empty object person.name = 'alice'; // add a member to the object if (person.age < 21) { // refer to a property that is not in the object // ... }

إذا قمت بتشغيل هذا ، person.age undefined ، لكنه يعمل على أي حال.

وبالمثل ، يمكنك تغيير نوع المتغير في JavaScript:

 var a = 1; // a is a number a = 'one'; // a is now a string

على النقيض من ذلك ، في Java ، لا يمكنك كتابة رمز مثل ما سبق لأن المترجم يحتاج إلى معرفة النوع ، ويتحقق من أن جميع العمليات قانونية - حتى إذا كنت تستخدم الكلمة الأساسية var:

 var b = 1; // a is an int // b = "one"; // not allowed in Java

تسمح لك Java فقط بالبرمجة بأنواع ثابتة. (يمكنك استخدام الاستبطان للقيام ببعض السلوك الديناميكي ، لكنه ليس جزءًا مباشرًا من بناء الجملة.) JavaScript وبعض اللغات الديناميكية البحتة الأخرى تسمح لك فقط بالبرمجة بأنواع ديناميكية.

تسمح لغة Dart بما يلي:

 // dart dynamic a = 1; // a is an int - dynamic typing a = 'one'; // a is now a string a.foo(); // we can call a function on a dynamic object, to be resolved at run time var b = 1; // b is an int - static typing // b = 'one'; // not allowed in Dart

تتميز Dart dynamic من النوع الزائف والتي تؤدي إلى معالجة كل منطق النوع في وقت التشغيل. لن تزعج محاولة استدعاء a.foo() المحلل الثابت وسيتم تشغيل الكود ، لكنها ستفشل في وقت التشغيل لأنه لا توجد مثل هذه الطريقة.

كان C # في الأصل مثل Java ، ثم أضاف لاحقًا دعمًا ديناميكيًا ، لذا فإن Dart و C # متماثلان تقريبًا في هذا الصدد.

4. وظائف

صيغة إعلان الوظيفة

تعد صيغة الوظيفة في Dart أخف وزناً وأكثر متعة من لغة C # أو Java. بناء الجملة هو أي من هذه:

 // functions as declarations return-type name (parameters) {body} return-type name (parameters) => expression; // function expressions (assignable to variables, etc.) (parameters) {body} (parameters) => expression

علي سبيل المثال:

 void printFoo() { print('foo'); }; String embellish(String s) => s.toUpperCase() + '!!'; var printFoo = () { print('foo'); }; var embellish = (String s) => s.toUpperCase() + '!!';

اجتياز المعلمة

نظرًا لأن كل شيء عبارة عن كائن ، بما في ذلك العناصر الأولية مثل int و String ، فقد يكون تمرير المعلمة أمرًا محيرًا. بينما لا توجد معلمة ref تمر كما في C # ، يتم تمرير كل شيء بالمرجع ، ولا يمكن للوظيفة تغيير مرجع المتصل. نظرًا لعدم استنساخ الكائنات عند تمريرها إلى الوظائف ، فقد تغير الوظيفة خصائص الكائن. ومع ذلك ، فإن هذا التمييز بين العناصر الأولية مثل int و String هو موضع نقاش فعال لأن هذه الأنواع غير قابلة للتغيير.

 var id = 1; var name = 'alice'; var client = Client(); void foo(int id, String name, Client client) { id = 2; // local var points to different int instance name = 'bob'; // local var points to different String instance client.State = 'AK'; // property of caller's object is changed } foo(id, name, client); // id == 1, name == 'alice', client.State == 'AK'

معلمات اختيارية

إذا كنت في عوالم C # أو Java ، فمن المحتمل أنك سببت مواقف بها طرق مثقلة بشكل مربك مثل هذه:

 // java void foo(string arg1) {...} void foo(int arg1, string arg2) {...} void foo(string arg1, Client arg2) {...} // call site: foo(clientId, input3); // confusing! too easy to misread which overload it is calling

أو مع المعلمات الاختيارية C # ، هناك نوع آخر من الالتباس:

 // c# void Foo(string arg1, int arg2 = 0) {...} void Foo(string arg1, int arg3 = 0, int arg2 = 0) {...} // call site: Foo("alice", 7); // legal but confusing! too easy to misread which overload it is calling and which parameter binds to argument 7 Foo("alice", arg2: 9); // better

لا تتطلب C # تسمية وسيطات اختيارية في مواقع الاتصال ، لذلك قد تكون طرق إعادة البناء باستخدام معلمات اختيارية خطيرة. إذا كانت بعض مواقع الاتصال قانونية بعد إعادة البناء ، فلن يلتقطها المترجم.

Dart لديها طريقة أكثر أمانًا ومرونة. بادئ ذي بدء ، لا يتم دعم الطرق المحملة بشكل زائد. بدلاً من ذلك ، هناك طريقتان للتعامل مع المعلمات الاختيارية:

 // positional optional parameters void foo(string arg1, [int arg2 = 0, int arg3 = 0]) {...} // call site for positional optional parameters foo('alice'); // legal foo('alice', 12); // legal foo('alice', 12, 13); // legal // named optional parameters void bar(string arg1, {int arg2 = 0, int arg3 = 0}) {...} bar('alice'); // legal bar('alice', arg3: 12); // legal bar('alice', arg3: 12, arg2: 13); // legal; sequence can vary and names are required

لا يمكنك استخدام كلا النمطين في نفس إعلان الوظيفة.

موقف الكلمات الرئيسية async

C # لها موقف محير async الرئيسية غير المتزامنة:

 Task<int> Foo() {...} async Task<int> Foo() {...}

هذا يعني أن توقيع الوظيفة غير متزامن ، ولكن في الحقيقة يكون تنفيذ الوظيفة فقط غير متزامن. يعتبر أي من التوقيعين أعلاه تنفيذًا صالحًا لهذه الواجهة:

 interface ICanFoo { Task<int> Foo(); }

في لغة Dart ، يكون غير async في مكان أكثر منطقية ، مما يدل على أن التنفيذ غير متزامن:

 Future<int> foo() async {...}

النطاق والإغلاق

مثل C # و Java ، يتم تصنيف Dart بشكل معجمي. هذا يعني أن المتغير المعلن في الكتلة يخرج عن النطاق في نهاية الكتلة. لذا يتعامل دارت مع الإغلاق بنفس الطريقة.

بناء جملة الخاصية

قامت Java بتعميم الخاصية get / set pattern ولكن اللغة لا تحتوي على أي صيغة خاصة لها:

 // java private String clientName; public String getClientName() { return clientName; } public void setClientName(String value}{ clientName = value; }

يحتوي C # على بناء جملة لذلك:

 // c# private string clientName; public string ClientName { get { return clientName; } set { clientName = value; } }

تتميز Dart بخصائص دعم بناء جملة مختلفة قليلاً:

 // dart string _clientName; string get ClientName => _clientName; string set ClientName(string s) { _clientName = s; }

5. البناة

تتمتع مُنشئات Dart بمرونة أكبر قليلاً من C # أو Java. إحدى الميزات الرائعة هي القدرة على تسمية مُنشئين مختلفين في نفس الفئة:

 class Point { Point(double x, double y) {...} // default ctor Point.asPolar(double angle, double r) {...} // named ctor }

يمكنك استدعاء مُنشئ افتراضي باستخدام اسم الفئة فقط: var c = Client();

هناك نوعان من الاختصارات لتهيئة أعضاء المثيل قبل استدعاء هيئة المنشئ:

 class Client { String _code; String _name; Client(String this._name) // "this" shorthand for assigning parameter to instance member : _code = _name.toUpper() { // special out-of-body place for initializing // body } }

يمكن للمُنشئين تشغيل مُنشئات الطبقة الفائقة وإعادة التوجيه إلى المُنشئين الآخرين في نفس الفئة:

 Foo.constructor1(int x) : this(x); // redirect to the default ctor in same class; no body allowed Foo.constructor2(int x) : super.plain(x) {...} // call base class named ctor, then run this body Foo.constructor3(int x) : _b = x + 1 : super.plain(x) {...} // initialize _b, then call base class ctor, then run this body

يمكن للمُنشئين الذين يستدعون المنشئات الأخرى في نفس الفئة في Java و C # أن يصابوا بالارتباك عندما يكون لديهم تطبيقات. في Dart ، القيد الذي لا يمكن أن يكون له جسم لإعادة توجيه المُنشئين يجبر المبرمج على جعل طبقات المُنشئين أكثر وضوحًا.

هناك أيضًا كلمة رئيسية factory تسمح باستخدام وظيفة مثل المُنشئ ، لكن التنفيذ مجرد وظيفة عادية. يمكنك استخدامه لإرجاع مثيل مخزن مؤقتًا أو مثيل من نوع مشتق:

 class Shape { factory Shape(int nsides) { if (nsides == 4) return Square(); // etc. } } var s = Shape(4);

6. المعدلات

في Java و C # ، لدينا معدِّلات وصول مثل private protected public . في Dart ، يتم تبسيط ذلك بشكل كبير: إذا بدأ اسم العضو بشرطة سفلية ، فسيكون مرئيًا في كل مكان داخل الحزمة (بما في ذلك من الفئات الأخرى) ومختفي من المتصلين الخارجيين ؛ خلاف ذلك ، هو مرئي من كل مكان. لا توجد كلمات رئيسية مثل private للدلالة على الرؤية.

هناك نوع آخر من المُعدِّلات يتحكم في قابلية التغيير: الكلمات الأساسية final const مخصصة لهذا الغرض ، لكنها تعني أشياء مختلفة:

 var a = 1; // a is variable, and can be reassigned later final b = a + 1; // b is a runtime constant, and can only be assigned once const c = 3; // c is a compile-time constant // const d = a + 2; // not allowed because a+2 cannot be resolved at compile time

7. فئة التسلسل الهرمي

تدعم لغة Dart الواجهات والفئات ونوعًا من الوراثة المتعددة. ومع ذلك ، لا توجد interface أساسية ؛ بدلاً من ذلك ، كل الفئات هي أيضًا واجهات ، لذا يمكنك تحديد فئة abstract ثم تنفيذها:

 abstract class HasDesk { bool isDeskMessy(); // no implementation here } class Employee implements HasDesk { bool isDeskMessy() { ...} // must be implemented here }

تتم الميراث المتعدد باستخدام النسب الرئيسي باستخدام الكلمة الأساسية extends ، وفئات أخرى باستخدام الكلمة الرئيسية with :

 class Employee extends Person with Salaried implements HasDesk {...}

في هذا الإعلان ، تُشتق فئة Employee من Person Salaried " ، لكن Person هو الطبقة الفائقة الرئيسية Salaried هو مزيج (الطبقة العليا الثانوية).

8. المشغلين

هناك بعض مشغلي Dart الممتعين والمفيدون الذين لم نعتد عليهم.

تسمح لك Cascades باستخدام نمط تسلسلي على أي شيء:

 emp ..name = 'Alice' ..supervisor = 'Zoltron' ..hire();

يسمح عامل التشغيل لمجموعة أن يتم التعامل معها كقائمة من عناصرها في مُهيئ:

 var smallList = [1, 2]; var bigList = [0, ...smallList, 3, 4]; // [0, 1, 2, 3, 4]

9. خيوط

لا تحتوي Dart على أي مؤشرات ترابط ، مما يسمح لها بالانتقال إلى JavaScript. لديها "عزلات" بدلاً من ذلك ، والتي تشبه إلى حد كبير عمليات منفصلة ، بمعنى أنها لا تستطيع مشاركة الذاكرة. نظرًا لأن البرمجة متعددة الخيوط معرضة جدًا للخطأ ، يُنظر إلى هذه السلامة على أنها إحدى مزايا Dart. للتواصل بين العزلات ، تحتاج إلى دفق البيانات بينها ؛ يتم نسخ الكائنات المستلمة في مساحة ذاكرة المستقبِل المعزول.

طور باستخدام لغة Dart: يمكنك فعل ذلك!

إذا كنت مطور C # أو Java ، فإن ما تعرفه بالفعل سيساعدك على تعلم لغة Dart بسرعة ، حيث تم تصميمها لتكون مألوفة. تحقيقًا لهذه الغاية ، قمنا بتجميع ملف PDF من ورقة الغش Dart للرجوع إليه ، مع التركيز بشكل خاص على الاختلافات المهمة من مكافئات C # وجافا:

ورقة الغش لغة دارت PDF

ستساعدك الاختلافات الموضحة في هذه المقالة جنبًا إلى جنب مع معرفتك الحالية على أن تصبح منتجًا خلال اليوم الأول أو الثاني من Dart. ترميز سعيد!

الموضوعات ذات الصلة: الطاقة الهجينة: مزايا وفوائد الرفرفة