مفهوم الصنف


الصنف هو اللبنة الأساسية التي تقودنا إلى البرمجية غرضية التوجه OOP، وهو عبارة عن نوع جديد يتم تعريفه بواسطة المستخدم، يحتوي على أعضاء Members (دوال و بيانات) والتي يمكن الوصول إليها واستخدامها عن طريق إنشاء مثيل instance (يمكن أن تسميه غرض أو كائن) من ذلك الصف.
على سبيل المثال: ضع في اعتبارك صنف السيارات. قد يكون هناك العديد من السيارات بأسماء وعلامات تجارية مختلفة، ولكن ستشترك جميعها في بعض الخصائص المشتركة مثل كل منها سيكون لها 4 عجلات، وحد للسرعة، ونطاق الأميال وما إلى ذلك، لذا هنا السيارة هي الصنف والعجلات، وحدود السرعة، وعدد الأميال المقطوعة خصائصها.ويمكن أن نعتبر أن أعضاء البيانات هي الحد الأقصى للسرعة، والمسافة المقطوعة، وما إلى ذلك، ويمكن أن تكون الدوال الأعضاء هي الضغط على المكابح، وزيادة السرعة، وما إلى ذلك.

- أعضاء البيانات Data members هم متغيرات البيانات Data variables والدوال الأعضاء Member functions هم الدوال المستخدمة لمعالجة هذه المتغيرات ويحدّد أعضاء البيانات ووظائف الأعضاء معًا خصائص وسلوك الكائنات في الصنف.
- الكائن أو الغرض Object هو مثيل instance من الصنف. وعندما يتم تعريف الصنف، لا يتم تخصيص ذاكرة ولكن عند إنشاء مثيل له (أي يتم إنشاء كائن) يتم تخصيص الذاكرة.
- الخصائص Attributes هي الأشياء (المتغيرات المصفوفات الكائنات..إلخ) التي يتم تعريفها بداخل الكلاس و التي سيملك نسخة خاصة منها أي كائن ننشئه منه.

تعريف صنف والإعلان عن كائن


لتعريف صنف جديد نكتب الكلمة `صنف` (class) ثم نعطيه اسم، ثم نفتح أقواس هلالية تحدد بدايته و نهايته. المثال التالي يوضح طريقة تعريف صنف اسمه `سـيارة` (Car) ويحتوي على متغيرين هما حد السرعة ونطاق الأميال.

صنف سـيارة {
  عرف حد_السرعة: صحيح؛
  عرف نطاق_الأميال: صحيح؛
}
class Car {
  def speed_limit: int;
  def mileage: int;
}

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

عرف أودي: سـيارة؛
def audi: Car;

هنا قمنا بتعريف كائن يمثل سيارة أودي من الصنف `سـيارة` (Car). أما بالنسبة للوصول للأشياء التي يملكها الكائن، فيمكنك الوصول لأي شيء موجود فيه من خلال كتابة اسم الكائن ثم وضع نقطة ('.') ثم وضع اسم الشيء الذي تريد الوصول إليه. على سبيل المثال لتحديد حد السرعة لسيارة الأودي السابقة نكتب:

أودي.حد_السرعة = 190؛
audi.speed_limit = 190;

المثال الأول:
سنقوم الآن بكتابة صنف يمثل سيارة ثم إنشاء كائنات منه.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛
// أنشئ صنف سـيارة مع بعض الخصائص
صنف سـيارة {
  عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  عرف حد_السرعة: صحيح؛
  عرف نطاق_الأميال: صحيح؛
}

// أنشئ كائنًا من صنف سـيارة
عرف كائن_سيارة_1: سـيارة؛
كائن_سيارة_1.العلامة_التجارية = "BMW"؛
كائن_سيارة_1.الطراز = "X5"؛
كائن_سيارة_1.السنة = 1999؛
كائن_سيارة_1.حد_السرعة = 200؛
كائن_سيارة_1.نطاق_الأميال = 10000؛
// أنشئ كائنًا آخر من صنف سـيارة
كائن_سيارة_2.العلامة_التجارية = "Ford"؛
كائن_سيارة_2.الطراز = "Mustang"؛
كائن_سيارة_2.السنة = 1969؛
كائن_سيارة_2.حد_السرعة = 160؛
كائن_سيارة_2.نطاق_الأميال = 8500؛

// طباعة قيم الكائنين
طـرفية.اطبع("-----------------------\ج");
طـرفية.اطبع("\tكائن_سيارة_1\ج");
طـرفية.اطبع("-----------------------\ج");
طـرفية.اطبع("العلامة التجارية: %s\ج", كائن_سيارة_1.العلامة_التجارية.صوان);
طـرفية.اطبع("الطراز: %s\ج", كائن_سيارة_1.الطراز.صوان);
طـرفية.اطبع("السنة: %d\ج", كائن_سيارة_1.السنة);
طـرفية.اطبع("حد السرعة: %d\ج", كائن_سيارة_1.حد_السرعة);
طـرفية.اطبع("نطاق الأميال: %d\ج", كائن_سيارة_1.نطاق_الأميال);
طـرفية.اطبع("-----------------------\ج");
طـرفية.اطبع("\tكائن_سيارة_2\ج");
طـرفية.اطبع("-----------------------\ج");
طـرفية.اطبع("العلامة التجارية: %s\ج", كائن_سيارة_2.العلامة_التجارية.صوان);
طـرفية.اطبع("الطراز: %s\ج", كائن_سيارة_2.الطراز.صوان);
طـرفية.اطبع("السنة: %d\ج", كائن_سيارة_2.السنة);
طـرفية.اطبع("حد السرعة: %d\ج", كائن_سيارة_2.حد_السرعة);
طـرفية.اطبع("نطاق الأميال: %d\ج", كائن_سيارة_2.نطاق_الأميال);
طـرفية.اطبع("-----------------------\ج");

/*
-----------------------
كائن_سيارة_1
-----------------------
العلامة_التجارية: BMW
الطراز: X5
السنة: 1999
حد السرعة: 200
نطاق الأميال: 10000
-----------------------
كائن_سيارة_2
-----------------------
العلامة_التجارية: Ford
الطراز: Mustang
السنة: 1969
حد السرعة: 160
نطاق الأميال: 8500
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
// Create a Car class with some attributes
class Car {
  def brand: String;  // العلامة التجارية
  def model: String;   // الطراز
  def year: int; // السنة
  def speed_limit: int; // حد السرعة
  def mileage: int; // نطاق الأميال
}

// Create an object of Car
def carObj1: Car; // إنشاء كائن من الصنف وتعبئة بياناته
carObj1.brand = "BMW";
carObj1.model = "X5";
carObj1.year = 1999;
carObj1.speed_limit = 200;
carObj1.mileage = 10000;
// Create another object of Car
def carObj2:Car; // إنشاء كائن آخر
carObj2.brand = "Ford";
carObj2.model = "Mustang";
carObj2.year = 1969;
carObj2.speed_limit = 160;
carObj2.mileage = 8500;

// Print attribute values
Console.print("-----------------------\n");
Console.print("\tcarObj1\n");
Console.print("-----------------------\n");
Console.print("Brand: %s\n", carObj1.brand.buf);
Console.print("Model: %s\n", carObj1.model.buf);
Console.print("Year: %d\n", carObj1.year);
Console.print("Speed limit: %d\n", carObj1.speed_limit);
Console.print("Mileage: %d\n", carObj1.mileage);
Console.print("-----------------------\n");
Console.print("\tcarObj2\n");
Console.print("-----------------------\n");
Console.print("Brand: %s\n", carObj2.brand.buf);
Console.print("Model: %s\n", carObj2.model.buf);
Console.print("Year: %d\n", carObj2.year);
Console.print("Speed limit: %d\n", carObj2.speed_limit);
Console.print("Mileage: %d\n", carObj2.mileage);
Console.print("-----------------------\n");

/*
-----------------------
carObj1
-----------------------
Brand: BMW
Model: X5
Year: 1999
Speed limit: 200
Mileage: 10000
-----------------------
carObj2
-----------------------
Brand: Ford
Model: Mustang
Year: 1969
Speed limit: 160
Mileage: 8500
-----------------------
*/

تعريف دالة عضو


تعريف دالة عضو يتم بطريقتين:

  1. من خلال استخدام المبدل `@عضو` (member@)، متبوعاً بالكلمة المفتاحية `دالة` (function) متبوعاً باسم الدالة متبوعاً بقوسين هلاليين "()" نضع ضمنهما معطيات الدالة، إضافةً إلى أن المعطى الأول للدالة يجب أن يكون سنداً لهذا الصنف لنتمكن من الوصول إلى أعضاء الكائن الأخرى. ثم نضع نقطتين ثم نكتب نوع القيمة التي ستعيدها هذه الدالة (في حال كانت الدالة لاتعيد شيء لانضع أي نوع).
    @عضو دالة اسم_الدالة (هذا: سند[هذا_الصنف]، معطى1، معطى2، ...): صنف_الإرجاع {
      // متن الدالة
    }
    
    @member function func_name (this: ref[this_type], arg2, arg3, ...): ret_type {
      // Function body
    }
    
  2. من خلال الصيغة المحُسنة، نكتب الكلمة المفتاحية `عملية` (handler)، متبوعةً بالكلمة `هذا` (this) واسم الدالة ونمط القيمة المُعدلة.
    عملية هذا.اسم_الدالة (معطى1، معطى2، ...): صنف_الإرجاع {
      // متن الدالة
    }
    
    handler this.func_name (arg1, arg2, ...): ret_type {
      // Function body
    }
    

المثال الثاني:
الآن سنكتب مثالاً حول كيفية تعريف الدوال الأعضاء بداخل صنف (Member Functions) بالإضافة إلى كيفية استدعاءهم منه (سنعدل على المثال السابق).

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  عرف العلامة_التجارية: نـص = "BMW"؛
  عرف الطراز: نـص = "X5"؛
  عرف السنة: صحيح = 1999؛
  عرف حد_السرحة: صحيح؛
  عرف نطاق_الأميال: صحيح؛
  عرف بيانات: نـص؛
  // تعريف دالة لإرجاع معلومات السيارة الأساسية (العلامة التجارية+الطراز+السنة)
  عملية هذا.هات_البيانات(): نـص {
    هذا.بيانات =
      نـص("العلامة التجارية: ") +
      هذا.العلامة_التجارية +
      نـص("\جالطراز: ") +
      هذا.الطراز +
      نـص("\جالسنة: ") +
      نـص.املأ("%i", هذا.السنة)؛
    أرجع هذا.بيانات؛
  }
  // تعريف دالة لإرجاع حد السرعة
  عملية هذا.هات_السرعة(): نـص {
    أرجع نـص("السرعة: ") + هذا.حد_السرعة؛
  }
  // إرجاع نطاق الأميال
  عملية هذا.هات_نطاق_الأميال(): نـص {
    أرجع نـص("نطاق الأميال: ") + هذا.نطاق_الأميال؛
  }
  // تعريف دالة لتحديد السرعة
  عملية هذا.حدد_السرعة(حد_السرعة: صحيح) {
    هذا.حد_السرعة = حد_السرعة؛
  }
  // تعريف دالة لتحديد نطاق الأميال
  عملية هذا.حدد_نطاق_الأميال(نطاق_الأميال: صحيح) {
    هذا.نطاق_الأميال = نطاق_الأميال؛
  }
}

عرف سيارة1: سـيارة؛
// ضبط قيم السرعة والأميال.
سيارة1.حدد_السرعة(200)؛
سيارة1.حدد_نطاق_الأميال(10000)؛
// استدعاء دوال الطباعة.
طـرفية.اطبع("-----------------------\ج")؛
طـرفية.اطبع(سيارة1.هات_البيانات())؛
طـرفية.اطبع("\ج-----------------------\ج")؛
طـرفية.اطبع(سيارة1.هات_السرعة())؛
طـرفية.اطبع("\ج-----------------------\ج")؛
طـرفية.اطبع(سيارة1.هات_نطاق_الأميال())؛
طـرفية.اطبع("\ج-----------------------")؛

// الخرج سيكون بالشكل التالي:
/*
-----------------------
العلامة التجارية: BMW
الطراز: X5
السنة: 1999
-----------------------
حد السرعة: 200
-----------------------
نطاق الأميال: 10000
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  def brand: String = "BMW";  // العلامة التجارية
  def model: String = "X5";   // الطراز
  def year: int = 1999; // السنة
  def speed_limit: int; // حد السرعة
  def mileage: int; // نطاق الأميال
  def info: String;
  // تعريف دالة لإرجاع معلومات السيارة الأساسية (العلامة التجارية+الطراز+السنة)
  handler this.getInfo(): String {
    this.info =
      String("Brand: ") +
      this.brand +
      String("\nModel: ") +
      this.model +
      String("\nYear: ") +
      String.format("%i", this.year);
    return this.info;
  }
  // تعريف دالة لإرجاع حد السرعة
  handler this.getSpeed(): String {
    return String("Speed: ") + this.speed_limit;
  }
  // إرجاع نطاق الأميال
  handler this.getMileage(): String{
    return String("Mileage: ") + this.mileage;
  }
  // تعريف دالة لتحديد السرعة
  handler this.setSpeed(speed_limit: int) {
    this.speed_limit = speed_limit;
  }
  // تعريف دالة لتحديد نطاق الأميال
  handler this.setMileage(mileage: int) {
    this.mileage = mileage;
  }
}

def carObj1:Car;
// ضبط قيم السرعة والأميال.
carObj1.setSpeed(200);
carObj1.setMileage(10000);
// استدعاء دوال الطباعة.
Console.print("-----------------------\n");
Console.print(carObj1.getInfo());
Console.print("\n-----------------------\n");
Console.print(carObj1.getSpeed());
Console.print("\n-----------------------\n");
Console.print(carObj1.getMileage());
Console.print("\n-----------------------");

// الخرج سيكون بالشكل التالي:
/*
-----------------------
Brand: BMW
Model: X5
Year: 1999
-----------------------
Speed: 200
-----------------------
Mileage: 10000
-----------------------
*/

ملاحظة: في المثال السابق يمكنك أيضا تحويل تعريف `بيانات` (info) إلى خصلة (property) بدل أن تكون متغير، وبالتالي تُستخدم وكأنها متغير لكنها في الحقيقة دالة. وهذا يقتضي أن تُعاد حساب قيمتها عند كل استخدام بدل أن يُحسب مرة واحدة عند التهيئة.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  عرف حد_السرحة: صحيح؛
  عرف نطاق_الأميال: صحيح؛

  عملية هذا.بيانات: نـص { // تعريف خصلة
    أرجع نـص("العلامة التجارية: ") + هذا.العلامة_التجارية
      + نـص("\جالطراز") + هذا.الطراز؛
  }
}

عرف سيارة1: سـيارة؛
سيارة1.العلامة_التجارية = "BMW"؛
سيارة1.الطراز = "X5"؛
سيارة1.السنة = 1999؛
سيارة1.حد_السرعة = 200؛
سيارة1.نطاق_الأميال =10000؛

طـرفية.اطبع("-----------------------\n")؛
طـرفية.اطبع(سيارة1.بيانات)؛
طـرفية.اطبع("\n-----------------------")؛

/*
-----------------------
العلامة التجارية: BMW
الطراز: X5
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
// Create a Car class with some attributes
class Car {
  def brand: String;
  def model: String;
  def year: int;
  def speed_limit: int;
  def mileage: int;

  handler this.info: String { // تعريف خصلة
    return String("Brand: ") + this.brand + String("\nModel: ") + this.model;
  }
}

def carObj1: Car;
carObj1.brand = "BMW";
carObj1.model = "X5";
carObj1.year = 1999;
carObj1.speed_limit = 200;
carObj1.mileage =10000;

Console.print("-----------------------\n");
Console.print(carObj1.info);
Console.print("\n-----------------------");
/*
-----------------------
Brand: BMW
Model: X5
-----------------------
*/

البواني (عمليات التهيئة)


من أهم الأشياء التي عليك التفكير بها عند إنشاء صنف جديد هي تسهيل طريقة إنشاء كائنات من هذا الصنف لاحقاً. البواني هم أعضاء صنف خاصون يتم استدعاؤهم بواسطة المترجم في كل مرة يتم فيها إنشاء كائن من ذلك الصنف.
يمكن تعريف البواني داخل الصنف بالشكل التالي:

عملية هذا~هيئ (تعريفات_المعطيات) {
  // متن الدالة
}
handler this~init (argument_defs) {
  // function body
};

عند تعريف كائن (مسمى أو مؤقت) من صنف، يمكن تحديد الباني المطلوب استدعاؤه باستخدام أقواس بعد اسم الصنف متضمنة للمعطيات التي يحتاجها الباني المقصود. سيختار المترجم تلقائيا الباني الموافق للمعطيات.

ملاحظات:

  1. كل صنف يتم إنشاؤه يحتوي على باني واحد على الأقل. حتى وإن لم تقم بتعريف أي باني، سيقوم المترجم بإنشاء واحد افتراضي.
  2. في كل مرة يتم إنشاء كائن جديد من الصنف، يجب استدعاء باني من الصنف حتى يتم إنشاء هذا الكائن.
  3. في حال قمت بتعريف باني، لن يقوم المترجم بإنشاء واحد افتراضي، أي لن يعود هناك باني افتراضي.
  4. يمكنك تعريف أكثر من باني. و يمكنك دائماً إنشاء باني فارغ، حتى تستخدمه إن كنت لا تريد إعطاء قيم أولية محددة للخصائص عند إنشاء كائن.

مثال:
في المثال التالي سنقوم بتعريف عدة بواني (سنعدل على المثال الأساسي).

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  عرف حد_السرحة: صحيح؛
  عرف نطاق_الأميال: صحيح؛
  // تعريف باني بدون معطيات يقوم بتهيئة قيم العلامة التجارية والطراز (الباني الأول).
  عملية هذا~هيئ() {
    طـرفية.اطبع("أنا الباني الأول!\ج")؛
    هذا.العلامة_التجارية = "فورد"؛
    هذا.الطراز = "مستانج"؛
  }
  // تعريف باني آخر يقوم بتهيئة نفس الخصائص السابقة، لكن
  // يأخذ القيم كمعطيات عند تعريف الكائن (الباني الثاني).
  عملية هذا~هيئ (العلامة_التجارية: نـص، الطراز: نـص) {
    طـرفية.اطبع("أنا الباني الثاني!\ج")؛
    هذا.العلامة_التجارية = العلامة_التجارية؛
    هذا.الطراز = الطراز؛
  }
  // تعريف باني يقوم بتهيئة كل الخصائص السابقة، ويأخذ
  // القيم كمعطيات عند تعريف الكائن (الباني الثالث).
  عملية هذا~هيئ(
    العلامة_التجارية: نـص، الطراز: نـص، السنة: صحيح،
    حد_السرعة: صحيح، نطاق_الأميال: صحيح
  ) {
    طـرفية.اطبع("أنا الباني الثالث!\ج")؛
    هذا.العلامة_التجارية = العلامة_التجارية؛
    هذا.الطراز = الطراز؛
    هذا.السنة = السنة؛
    هذا.حد_السرعة = حد_السرعة؛
    هذا.نطاق_الأميال = نطاق_الأميال؛
  }

  عملية هذا.بيانات_مختصرة: نـص {
    أرجع نـص("العلامة التجارية: ") + هذا.العلامة_التجارية
      + نـص("\nالطراز: ") + هذا.الطراز؛
  }
  عملية هذا.بيانات_كاملة: نـص {
    أرجع نـص("العلامة التجارية: ") + هذا.العلامة_التجارية
      + نـص("\nالطراز: ") + هذا.الطراز
      + نـص("\nالسنة: ") + هذا.السنة
      + نـص("\nحد السرعة: ") +هذا.حد_السرعة
      + نـص("\nنطاق الأميال: ") +هذا.نطاق_الأميال؛
  }
}

عرف سيارة1: سـيارة; // هنا سيستدعي الباني الأول
طـرفية.اطبع("-----------------------\n");
طـرفية.اطبع(سيارة1.بيانات_مختصرة);
طـرفية.اطبع("\n-----------------------");
عرف سيارة2: سـيارة(نـص("BMW"), نـص("X5")); // هنا سيستدعي الباني الثاني
طـرفية.اطبع("-----------------------\n");
طـرفية.اطبع(سيارة2.بيانات_مختصرة);
طـرفية.اطبع("\n-----------------------");
عرف سيارة3: سـيارة(نـص("BMW"), نـص("X5"), 1999, 200, 10000); // الباني الثالث
طـرفية.اطبع("-----------------------\n");
طـرفية.اطبع(سيارة3.fullInfo);
طـرفية.اطبع("\n-----------------------");

/*
-----------------------
أنا الباني الأول!
العلامة التجارية: فورد
الطراز: مستانج
----------------------------------------------
أنا الباني الثاني!
العلامة التجارية: BMW
الطراز: X5
----------------------------------------------
أنا الباني الثالث!
العلامة التجارية: BMW
الطراز: X5
السنة: 1999
حد السرعة: 200
نطاق الأميال: 10000
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  def brand: String;
  def model: String;
  def year: int;
  def speed_limit: int;
  def mileage: int;
  // تعريف باني بدون معطيات يقوم بتهيئة قيم العلامة التجارية والطراز (الباني الأول).
  handler this~init() {
    Console.print("Hi.. I'm the first Constructor!\n");
    this.brand = "Ford";
    this.model = "Mustang";
  }
  // تعريف باني آخر يقوم بتهيئة نفس الخصائص السابقة، لكن
  // يأخذ القيم كمعطيات عند تعريف الكائن (الباني الثاني).
  handler this~init (brand: String, model: String) {
    Console.print("Hi.. I'm the second Constructor!\n");
    this.brand = brand;
    this.model = model;
  }
  // تعريف باني يقوم بتهيئة كل الخصائص السابقة، ويأخذ
  // القيم كمعطيات عند تعريف الكائن (الباني الثالث).
  handler this~init (
    brand: String, model: String, year: int, speed_limit: int, mileage: int
  ) {
    Console.print("Hi.. I'm the third Constructor!\n");
    this.brand = brand;
    this.model = model;
    this.year = year;
    this.speed_limit = speed_limit;
    this.mileage = mileage;
  }

  handler this.basicInfo:String {
    return String("Brand: ") + this.brand + String("\nModel: ") + this.model;
  }
  handler this.fullInfo:String {
    return String("Brand: ") + this.brand + String("\nModel: ") + this.model
      + String("\nYear: ") +this.year+String("\nSpeed: ") +this.speed_limit
      + String("\nMileage: ") +this.mileage;
  }
}

def carObj1:Car; // هنا سيستدعي الباني الأول
Console.print("-----------------------\n");
Console.print(carObj1.basicInfo);
Console.print("\n-----------------------");
def carObj2:Car(String("BMW"), String("X5")); // هنا سيستدعي الباني الثاني
Console.print("-----------------------\n");
Console.print(carObj2.basicInfo);
Console.print("\n-----------------------");
def carObj3:Car(String("BMW"), String("X5"), 1999, 200, 10000); // الباني الثالث
Console.print("-----------------------\n");
Console.print(carObj3.fullInfo);
Console.print("\n-----------------------");

/*
-----------------------
Hi.. I'm the first Constructor!
Brand: Ford
Model: Mustang
----------------------------------------------
Hi.. I'm the second Constructor!
Brand: BMW
Model: X5
----------------------------------------------
Hi.. I'm the third Constructor!
Brand: BMW
Model: X5
Year: 1999
Speed: 200
Mileage: 10000
-----------------------
*/

الهوادم (عمليا الإتلاف)


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

ملاحظات:

  1. كل صنف يتم إنشاؤه يحتوي على هادم. أي حتى إن لم تقم بتعريف هادم بنفسك سيقوم المترجم بإنشاء واحد إفتراضي عنك.
  2. الهادم يتم إستدعاؤه بشكل تلقائي عندما يتم مسح الكائن من الذاكرة.
  3. يمكنك تعريف هادم واحد فقط في الصنف.
  4. لا يمكن وضع معطيات في الهادم.

متى يتم استدعاء الهادم؟

  1. في حال تم إنشاء الكائن بداخل دالة و تم تنفيذ كل محتوى الدالة لأنه من الطبيعي أن يتم حذف أي شيء تم تعريفه فيها بعد أن تتنفذ.
  2. في حال تم حذف الكائن من الذاكرة بأي طريقة أخرى.
  3. بشكل عام عند خروج التنفيذ من أي مجال (Scope) فإن المترجم يقوم تلقائياً باستدعاء هذه الدالة لكل العناصر المعرفة ضمن ذلك المجال. يمكن استخدام هذه الدالة لتحرير أي موارد تم حجزها من قبل ذلك الكائن.

مثال:
سنعدل على المثال السابق بحيث نقوم بإنشاء باني وهادم.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  // تعريف باني.
  عملية هذا~هيئ() {
    طـرفية.اطبع("استدعيت دالة التهيئة!\ج")؛
    هذا.العلامة_التجارية = "فورد"؛
    هذا.الطراز = "مستانج"؛
  }
  // تعريف هادم.
  عملية هذا~أتلف() {
    طـرفية.اطبع("استدعيت دالة الإتلاف!\ج")؛
  }
}

إذا 1 {
  عرف سيارة: سـيارة؛
} // هنا انتهى مجال الكائن السابق وبالتالي سيتم استدعاء الهادم وتنفيذ جملة الطباعة

/*
استدعيت دالة التهيئة!
استدعيت دالة الإتلاف!
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  def brand: String;
  def model: String;
  // تعريف باني.
  handler this~init() {
    Console.print("Constructor is called!\n");
    this.brand = "Ford";
    this.model = "Mustang";
  }
  // تعريف هادم.
  handler this~terminate() {
    Console.print("Destructor is called\n");
  };
}

if true {
  def carObj: Car;
} // هنا انتهى مجال الكائن السابق وبالتالي سيتم استدعاء الهادم وتنفيذ جملة الطباعة

/*
Constructor is called!
Destructor is called
*/

المتغيرات والدوال المشتركة


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

تعريف متغيرات مشتركة

- تعريف المتغير في الصنف كمتغير مشترك يجعلك قادر على الوصول لقيمة هذا المتغير بشكل مباشر من الصنف، أي بدون الحاجة لإنشاء كائن منه.
- بعد إسناد قيمة أولية للمتغير المشترك، تستطيع تغيير قيمته لاحقاً من خلال ذكر اسم الصنف متبوعًا بنقطة متبوعًا باسم المتغير.
- لايمكن استخدام الكائنات المأخوذة من الصنف لضبط قيمة المتغير المشترك (أي لايمكنك الوصول للمتغير المشترك من خلال اسم الكائن-حصراً اسم الصنف-).
- يمكنك الوصول إلى هذا المتغير من أي مكان داخل الصنف دون استخدام الكلمة المفتاحية this، فكما قلنا أصبح وكأنه متغير عمومي.
- لايمكن تهيئة قيمة المتغير المشترك داخل الدالة (إعطاءه قيمة ايتدائية) لكن من الممكن تعديل قيمته داخل الدالة.
- لتعريف متغير على أنه مشترك، نستخدم الكلمة المفتاحية shared@.

مثال:
سنقوم بإنشاء صنف يمثل سيارة، وسنقوم بتعريف عدة سيارات (كائنات) تحمل نفس العلامة التجارية، لكن كل منها بطراز مختلف.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  @مشترك عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  // تعريف باني
  عملية هذا~هيئ (الطراز: نـص، السنة: صحيح) {
    طـرفية.اطبع("أستدعيت دالة التهيئة!\ج")؛
    هذا.الطراز = الطراز؛
    هذا.السنة = السنة؛
  }
  // طباعة المعلومات الخاصة بالسيارة.
  عملية هذا.بيانات: نـص {
    أرجع نـص("العلامة التجارية: ") +
      العلامة_التجارية + // لاحظ أننا لم نستخدم `هذا` مع المتغير المشترك
      نـص("\جالطراز: ") + هذا.الطراز +
      نـص + نـص.املأ("%i"، هذا.السنة)؛
  }
}

سـيارة.العلامة_التجارية = "BMW"؛ // لاحظ أننا هنا لم نحتج لإنشاء كائن من الصنف
عرف سيارة1: سـيارة(نـص("BMW_8_Series")، 2020)؛
عرف سيارة2: سـيارة(نـص("BMW_M6")، 2018)؛
عرف سيارة3: سـيارة(نـص("BMW_i8")، 2019)؛
طـرفية.اطبع("-----------------------\ج");
طـرفية.اطبع(سيارة1.بيانات);
طـرفية.اطبع("\ج-----------------------\ج");
طـرفية.اطبع(سيارة2.بيانات);
طـرفية.اطبع("\ج-----------------------\ج");
طـرفية.اطبع(سيارة3.بيانات);
طـرفية.اطبع("\ج-----------------------");

  /*
أستدعيت دالة التهيئة!
العلامة التجارية: BMW
الطراز: BMW_8_Series
السنة: 2020
-----------------------
أستدعيت دالة التهيئة!
العلامة التجارية: BMW
الطراز: BMW_M6
السنة: 2018
-----------------------
أستدعيت دالة التهيئة!
العلامة التجارية: BMW
الطراز: BMW_i8
السنة: 2019
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  @shared def brand: String;
  def model: String;
  def year: int;
  // تعريف باني.
  handler this~init (model: String, year: int) {
    Console.print("Constructor is called!\n")
    this.model=model;
    this.year=year;
  }
  // طباعة المعلومات الخاصة بالسيارة.
  handler this.info: String {
    return
      String("Brand: ") + brand + // مع المتغير المشترك this لاحظ أننا لم نستخدم
      String("\nModel: ") + this.model +
      String("\nYear: ") + String.format("%i", this.year);
  }
}

Car.brand = "BMW"; // لاحظ أننا هنا لم نحتج لإنشاء كائن من الصنف
def carObj1: Car(String("BMW_8_Series"), 2020);
def carObj2: Car(String("BMW_M6"), 2018);
def carObj3: Car(String("BMW_i8"), 2019);
Console.print("-----------------------\n");
Console.print(carObj1.info);
Console.print("\n-----------------------\n");
Console.print(carObj2.info);
Console.print("\n-----------------------\n");
Console.print(carObj3.info);
Console.print("\n-----------------------");

/*
Constructor is called!
Brand: BMW
Model: BMW_8_Series
Year: 2020
-----------------------
Constructor is called!
Brand: BMW
Model: BMW_M6
Year: 2018
-----------------------
Constructor is called!
Brand: BMW
Model: BMW_i8
Year: 2019
-----------------------
*/

تعريف دالات مشتركة

- فكرة الدوال المشتركة ببساطة هي تعريف دالة ضمن الصنف يمكن استدعاءها بشكل مباشر منه بدون الحاجة لإنشاء كائن منه.
- الدالة التي يتم تعريفها كدالة مشتركة يمكنها فقط التعامل مع المتغيرات المشتركة (أو المتغيرات العمومية بطبيعة الحال، أي تلك التي تعرف خارج الكائن).
- بالنسبة لطريقة تعريف دالة مشتركة فهي نفسها طريقة تعريف دالة عادية داخل الصنف لكن من دون استخدام المبدل `@عضو` (member@) معها.

مثال:
سنستخدم متغير ودالة مشتَركين لمعرفة عدد الكائنات التي يتم إنشاؤها من الصنف (سنعدل على المثال الأساسي).
في البداية سنقوم بتعريف متغير مشترك اسمه `عداد` (counter) ودالة مشتركة إسمها `هات_العداد` (getCounter) عند استدعاءها تقوم بإرجاع قيمة المتغير `عداد` (counter) فقط.
في الصنف `سـيارة` (Car) قمنا بتعديل الباني لجعله يقوم بإضافة 1 على قيمة المتغير `عداد` كلما تم إستدعاؤه. بمعنى آخر، كلما قمنا بإنشاء كائن سيتم إضافة 1 على قيمة المتغير `عداد` وبالتالي فإن قيمة `عداد` ستمثل عدد الكائنات التي يتم إنشاءها من الصنف `سـيارة`. في النهاية قمنا بإنشاء ثلاث كائنات من الصنف `سـيارة` مع استدعاء الدالة `هات_العداد` (getCounter) لمعرفة عدد الكائنات التي تم إنشاؤها.

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  @مشترك عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  @مشترك عرف عداد: صحيح؛
  عملية هذا~هيئ (الطراز: نـص، السنة: صحيح) {
    عداد++؛
    هذا.الطراز = الطراز؛
    هذا.السنة = السنة؛
  }
  دالة هات_العداد(): صحيح {
    أرجع عداد؛
  }
}

سـيارة.العلامة_التجارية = "BMW"؛
سـيارة.عداد = 0؛
عرف سيارة1: سـيارة(نـص("BMW_8_Series"), 2020)؛
عرف سيارة2: سـيارة(نـص("BMW_M6"), 2018)؛
عرف سيارة3: سـيارة(نـص("BMW_i8 "), 2019)؛
طـرفية.اطبع("-----------------------\n")؛
طـرفية.اطبع("%d\n", سـيارة.هات_العداد())؛
طـرفية.اطبع("%s\n", سيارة1.الطراز.صوان)؛
طـرفية.اطبع("%d\n", سـيارة.هات_العداد())؛
طـرفية.اطبع("%s\n", سيارة2.الطراز.صوان)؛
طـرفية.اطبع("%d\n", سـيارة.هات_العداد())؛
طـرفية.اطبع("%s\n", سيارة3.الطراز.صوان)؛
طـرفية.اطبع("%d\n", سـيارة.هات_العداد())؛
طـرفية.اطبع("-----------------------\n")؛

/*
-----------------------
0
BMW_8_Series
1
BMW_M6
2
BMW_i8
3
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  @shared def brand: String;
  def model: String;
  def year: int;
  @shared def counter: int;
  handler this~init(model: String, year: int){
    counter++;
    this.model = model;
    this.year = year;
  }
  function getCounter(): int {
    return counter;
  }
}

Car.brand="BMW";
Car.counter=0;
def carObj1: Car(String("BMW_8_Series"), 2020);
def carObj2: Car(String("BMW_M6"), 2018);
def carObj3: Car(String("BMW_i8 "), 2019);
Console.print("-----------------------\n");
Console.print("%d\n", Car.getCounter());
Console.print("%s\n", carObj1.model.buf);
Console.print("%d\n", Car.getCounter());
Console.print("%s\n", carObj2.model.buf);
Console.print("%d\n", Car.getCounter());
Console.print("%s\n", carObj3.model.buf);
Console.print("%d\n", Car.getCounter());
Console.print("-----------------------\n");

/*
-----------------------
0
BMW_8_Series
1
BMW_M6
2
BMW_i8
3
-----------------------
*/

حسناً، الكود في الأعلى قام بدايةً بطباعة 0 عند أول استداعاء للدالة `هات_العداد` (getCounter)، بينما كان من المفترض أن يقوم بطباعة العدد 3 لأنه تم إنشاء 3 كائنات. لمعرفة السبب تابع التعديلات البسيطة على المثال السابق، وما يليه من توضيح:

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛

صنف سـيارة {
  @مشترك عرف العلامة_التجارية: نـص؛
  عرف الطراز: نـص؛
  عرف السنة: صحيح؛
  @مشترك عرف عداد: صحيح؛
  عملية هذا~هيئ (الطراز: نـص، السنة: صحيح) {
    عداد++؛
    هذا.الطراز = الطراز؛
    هذا.السنة = السنة؛
  }
  دالة هات_العداد(): صحيح {
    أرجع عداد؛
  }
}

سـيارة.العلامة_التجارية = "BMW"؛
سـيارة.عداد = 0؛
عرف سيارة1: سـيارة(نـص("BMW_8_Series"), 2020)؛
عرف سيارة2: سـيارة(نـص("BMW_M6"), 2018)؛
عرف سيارة3: سـيارة(نـص("BMW_i8 "), 2019)؛
طـرفية.اطبع("-----------------------\ج")؛
طـرفية.اطبع("%s\ج", سيارة1.الطراز.صوان)؛
طـرفية.اطبع("%d\ج", سـيارة.عداد)؛
طـرفية.اطبع("%s\ج", سيارة2.الطراز.صوان)؛
طـرفية.اطبع("%d\ج", سـيارة.عداد)؛
طـرفية.اطبع("%s\ج", سيارة3.الطراز.صوان)؛
طـرفية.اطبع("%d\ج", سـيارة.عداد)؛
طـرفية.اطبع("-----------------------\ج")؛

دالة اختبر {
  عرف سيارة1: سـيارة(نـص("BMW_8_Series"), 2020);
  عرف سيارة2: سـيارة(نـص("BMW_M6"), 2018);
  عرف سيارة3: سـيارة(نـص("BMW_i8 "), 2019);
  طـرفية.اطبع("-----------------------\ج");
  طـرفية.اطبع("%s\ج", سيارة1.الطراز.صوان);
  طـرفية.اطبع("%d\ج", سـيارة.عداد);
  طـرفية.اطبع("%s\ج", سيارة2.الطراز.صوان);
  طـرفية.اطبع("%d\ج", سـيارة.عداد);
  طـرفية.اطبع("%s\ج", سيارة3.الطراز.صوان);
  طـرفية.اطبع("%d\ج", سـيارة.عداد);
  طـرفية.اطبع("-----------------------\ج");
}
اختبر();

/*
-----------------------
BMW_8_Series
1
BMW_M6
2
BMW_i8
3
-----------------------
-----------------------
BMW_8_Series
6
BMW_M6
6
BMW_i8
6
-----------------------
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;

class Car {
  @shared def brand: String;
  def model: String;
  def year: int;
  @shared def counter: int;

  handler this~init(model: String, year: int) {
    counter++;
    this.model = model;
    this.year = year;
  }

  function getCounter(): int {
    return counter;
  }
}

Car.brand = "BMW";
Car.counter = 0;
def carObj1: Car(String("BMW_8_Series"), 2020);
def carObj2: Car(String("BMW_M6"), 2018);
def carObj3: Car(String("BMW_i8 "), 2019);
Console.print("-----------------------\n");
Console.print("%s\n", carObj1.model.buf);
Console.print("%d\n", Car.counter);
Console.print("%s\n", carObj2.model.buf);
Console.print("%d\n", Car.counter);
Console.print("%s\n", carObj3.model.buf);
Console.print("%d\n", Car.counter);
Console.print("-----------------------\n");

func test {
  def carObj1: Car(String("BMW_8_Series"), 2020);
  def carObj2: Car(String("BMW_M6"), 2018);
  def carObj3: Car(String("BMW_i8 "), 2019);
  Console.print("-----------------------\n");
  Console.print("%s\n", carObj1.model.buf);
  Console.print("%d\n", Car.count);
  Console.print("%s\n", carObj2.model.buf);
  Console.print("%d\n", Car.count);
  Console.print("%s\n", carObj3.model.buf);
  Console.print("%d\n", Car.count);
  Console.print("-----------------------\n");
}
test();

/*
-----------------------
BMW_8_Series
1
BMW_M6
2
BMW_i8
3
-----------------------
-----------------------
BMW_8_Series
6
BMW_M6
6
BMW_i8
6
-----------------------
*/

السبب أن المتغيرات العمومية لا تتم تهيئتها حتى تُستخدم في مكان ما. فإن عرفت متغيراً لكنك لم تستخدمه إطلاقاً فكأنك لم تُعرفه، أي أن المترجم في هذه الحالة سيتجاهله ولن يحجز له موقعاً في الذاكرة. وهذا ينطبق على المتغيرات العمومية فقط، وليس المتغيرات المحلية. لذلك ترى من البرنامج أعلاه أن قيمة العداد في حالة المتغيرات العمومية تزداد فقط بعد أن تستخدم المتغير، فبعد استخدام المتغير الأول أصبحت القيمة 1 وبعد استخدام الثاني أصبحت 2 وهكذا. في حالة المتغيرات المحلية (كما رأينا داخل الدالة `اختبر` (test)) تتم تهيئة المتغير حال تعريفه وبالتالي بمجرد تعريف المتغيرات المحلية الثلاث وحتى قبل استخدامهم صارت قيمة العداد 6.

تخصيص المؤثرات


يمكننا تخصيص المؤثرات المطبقة على الكائنات، لكن ما المقصود بذلك؟
المقصود من ذلك هو جعل الكائن يقوم بأمر معين عند تطبيق أحد المؤثرات عليه (+ أو - أو = أو * إلخ..).
لتخصيص مؤثر نستخدم الأمر `عملية` (handler) بطريقة مشابهة للطريقة التي قمنا فيها بتعريف البواني.
مثال:
سنقوم الآن بتعريف صنف يمثل عدد صحيح، ثم سنقوم بتخصيص عملية + له:

استخدم "مـتم/طـرفية"؛
استخدم "مـتم/نـص"؛
استخدم مـتم؛
صنف عـدد_صحيح {
  عرف رقم: صحيح؛
  // تعريف باني افتراضي يقوم بالتهيئة بصفر
  عملية هذا~هيئ() {
    هذا.رقم = 0؛
  }
  // تعريف باني بمعطى يقوم بالتهيئة بعدد محدد
  عملية هذا~هيئ(قيمة_مبدئية: صحيح) {
    هذا.رقم = قيمة_مبدئية؛
  }
  // تخصيص مؤثر + لهذا الكائن
  عملية هذا + صحيح: صحيح {
    أرجع هذا.رقم + قيمة؛
  }
  // تخصيص المؤثر =+ أيضاً
  عملية هذا += صحيح: صحيح {
    أرجع هذا.رقم += قيمة؛
  }
}

// الآن لنطبق ما قمنا به
عرف عدد1: عـدد_صحيح؛
عرف عدد2: عـدد_صحيح(15)؛
عرف ناتج: صحيح؛
ناتج = عدد1 + 15؛
طـرفية.اطبع("ناتج: %d\ج", ناتج)؛
عدد1 += 5؛
عدد2 += 5؛
طـرفية.اطبع("عدد1: %d\ج", عدد1.رقم)؛
طـرفية.اطبع("عدد2: %d\ج", عدد2.رقم)؛
/*
ناتج: 15
عدد1: 5
عدد2: 20
*/
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
class IntegerNumber {
  def number: int;
  // تعريف باني افتراضي يقوم بالتهيئة بصفر
  handler this~init() {
    this.number = 0;
  }
  // تعريف باني بمعطى يقوم بالتهيئة بعدد محدد
  handler this~init(initialValue: int) {
   this.number = initialValue;
  }
  // تخصيص مؤثر + لهذا الكائن
  handler this + int: int {
    return this.number + value;
  }
  // تخصيص المؤثر =+ أيضاً
  handler this += int: int {
    return this.number += value;
  }
}

// الآن لنطبق ما قمنا به
def num1: IntegerNumber;
def num2: IntegerNumber(15);
def res:int;
res = num1 + 15;
Console.print("res: %d\n", res);
num1 += 5;
num2 += 5;
Console.print("num1: %d\n", num1.number);
Console.print("num2: %d\n", num2.number);
/*
res: 15
num1: 5
num2: 20
*/

لاحظ كيف قمنا في المثال السابق بتخصيص المؤثر + لتنفيذ عملية الجمع على الكائن.
حيث أن `هذا` (this) تشير إلى الكائن نفسه (في مثالنا `عدد1` (num1) أو `عدد2` (num2)) و + هو المؤثر المطلوب، أما `صحيح` (int) فهي صنف البيانات الذي سيصادفه (سيحل محلها عدد من هذا الصنف)، بينما القيمة الُمعادة ستكون قيمة المتغير الموجود داخل الصنف (أي قيمة `رقم` (number)) مضافاً عليه ال `قيمة` (value) وهي تشير إلى القيمة التي سيصادفها بعد المؤثر + (التي حددنا صنفها ب صحيح).
وبشكل مشابه قمنا بتخصيص المؤثر =+.

ملاحظات هامة جداً


- المترجم يفرق بين الأصناف البسيطة و الأصناف ذات البواني (أي الأصناف التي تحتوي على بواني أو هوادم)، و ذلك لأسباب سنتعرف عليها بعد قليل.
- الحالة الوحيدة التي يُعامل فيها الصنف على أنه صنف بسيط (أي صنف يقابل ال struct في لغة السي) هي إن كان الصنف لا يحتوي سوى على تعريفات لمتغيرات من الأصناف الأساسية وبدون أي تخصيص للقيم أو أي عمليات أخرى، فيما عدا هذا سيُعامل الصنف على أنه صنف بتهيئة مخصصة (يمتلك بواني).
- بالعودة إلى المثال السابق، لو حاولنا القيام بنسخ الكائن `عدد2` (num2) إلى `عدد1` (num1):

عرف عدد1: عـدد_صحيح؛
عرف عدد2: عـدد_صحيح(15)؛
عدد1 = عدد2؛
def num1: IntegerNumber;
def num2: IntegerNumber(15);
num1 = num2;

سنحصل على خطأ، لكن لماذا؟ و ما هو الحل؟

الإجابة تكمن فيما يأتي:
المترجم يفرق بين أصناف المستخدم البسيطة و بين الأصناف ذات البواني كما ذكرنا، حيث أن الأصناف ذات البواني يمتنع المترجم عن إجراء أي عملية نسخ تلقائية لكائناتها ويعتمد على المستخدم أن يعرف عمليات للنسخ (عملية =) و كذلك باني التهيئة من كائن آخر، و السبب في ذلك هو تجنب حالات قد تؤدي إلى تسريب الذاكرة أو الدخول إلى ذاكرة تم تحريرها أو ما شابه، أي باختصار فإن الغاية من ذلك هي تجنب حالات النسخ غير الآمنة التي قد تحدث. وبالتالي دوماً يجب علينا (في حالة الأصناف غير البسيطة -أغلب الحالات-) التقيد بالقاعدتين التاليتين:

  1. تخصيص مؤثر = كي تتمكن من استخدام هذا المؤثر مع هذا الكائن (كما فعلنا في المثال السابق عندما حاولنا نسخ عدد2 إلى عدد1).
  2. تعريف باني آخر يأخذ سنداً على كائن من نفس الصنف في حال أردت تمرير الكائن كمعطى إلى دالة أو إرجاعه من دالة أو تهيئة كائن من كائن آخر (سنأخذ مثالاً عن هكذا حالة).

الحل:
الآن سنعالج المشكلة السابقة، و هي عدم القدرة على استخدام المؤثر =.
لحل مشكلة استخدام المؤثر =، لابد لنا من التقيد بالقاعدة (1).

استخدم "مـتم/طـرفية"؛
استخدم "مـتم/نـص"؛
استخدم مـتم؛
صنف عـدد_صحيح {
  عرف رقم: صحيح؛
  عملية هذا~هيئ() {
    هذا.رقم = 0؛
  }
  عملية هذا~هيئ(قيمة_مبدئية: صحيح) {
    هذا.رقم = قيمة_مبدئية؛
  }
  عملية هذا + صحيح: صحيح {
    أرجع هذا.رقم + قيمة؛
  }
  عملية هذا += صحيح: صحيح {
    أرجع هذا.رقم += قيمة؛
  }
  // تخصيص المؤثر = لحل المشكلة
  عملية هذا = عـدد_صحيح {
    هذا.رقم = قيمة.رقم;
  }
}

عرف عدد1: عـدد_صحيح؛
عرف عدد2: عـدد_صحيح(15)؛
عدد1 = عدد2؛ // الآن صار بإمكاننا استخدام هذا المؤثر لتنفيذ عملية النسخ
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
class IntegerNumber {
  def number:int;
  handler this~init() {
    this.number = 0;
  }
  handler this~init(initialValue: int) {
    this.number = initialValue;
  }
  handler this + int: int {
    return this.number + value;
  }
  handler this += int: int {
    return this.number += value;
  }
  // تخصيص المؤثر = لحل المشكلة
  handler this = IntegerNumber {
    this.number = value.number;
  }
}

def num1: IntegerNumber;
def num2: IntegerNumber(15);
num1 = num2; // الآن صار بإمكاننا استخدام هذا المؤثر لتنفيذ عملية النسخ

الآن سنقوم بتعريف دالة تقوم بإنشاء كائن وإرجاعه، ثم سنحاول استخدام هذه الدالة.

استخدم "مـتم/طـرفية"؛
استخدم "مـتم/نـص"؛
استخدم مـتم؛
صنف عـدد_صحيح {
  عرف رقم: صحيح؛
  عملية هذا~هيئ() {
    هذا.رقم = 0؛
  }
  عملية هذا = عـدد_صحيح {
    هذا.رقم = قيمة.رقم;
  }
}

دالة هات_عدد_صحيح(): عـدد_صحيح {
  عرف ع: عـدد_صحيح؛
  أرجع ع؛
}
عرف ن: عـدد_صحيح؛
ن = هات_عدد_صحيح()؛ // ستعطي خطأ
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
class IntegerNumber {
  def number:int;
  handler this~init() {
    this.number = 0;
  }
  handler this = IntegerNumber {
    this.number = value.number;
  }
}
// وإرجاعه IntegerNumber تعريف دالة تقوم بإنشاء كائن من الصنف
function getIntegerNumber(): IntegerNumber {
  def num:IntegerNumber;
  return num;
}
def obj:IntegerNumber;
obj = getIntegerNumber(); // ستعطي خطأ

كما تلاحظ في المثال أعلاه فإنه يعطي خطأ عند محاولة استدعاء هذه الدالة، لأننا لم نقم بالتقيد بالقاعدة (2). لذا لحل المشكلة علينا تعريف باني بمعطى بالشكل التالي:

استخدم "مـتم/طـرفية"؛
استخدم "مـتم/نـص"؛
استخدم مـتم؛
صنف عـدد_صحيح {
  عرف رقم: صحيح؛
  عملية هذا~هيئ() {
    هذا.رقم = 0؛
  }
  عملية هذا = عـدد_صحيح {
    هذا.رقم = قيمة.رقم;
  }
  // تعريف باني يأخذ سنداً على كائن من نفس الصنف
  عملية هذا~هيئ(مصدر: سند[عـدد_صحيح]) {
    هذا.رقم = مصدر.رقم؛
  }
}

دالة هات_عدد_صحيح(): عـدد_صحيح {
  عرف ع: عـدد_صحيح؛
  أرجع ع؛ // هنا سيتم استدعاء الباني بمعطى هذا~هيئ(مصدر)
}
عرف ن: عـدد_صحيح؛
ن = هات_عدد_صحيح()؛
import "Srl/Console.alusus"
import "Srl/String.alusus"
use Srl;
class IntegerNumber {
  def number:int;
  handler this~init() {
    this.number = 0;
  }
  handler this = IntegerNumber {
    this.number = value.number;
  }
  // تعريف باني يأخذ سنداً على كائن من نفس الصنف
  handler this~init(src: ref[IntegerNumber]) {
    this.number = src.number;
  }
}

function getIntegerNumber(): IntegerNumber {
  def num:IntegerNumber;
  return num; // this~init(src) هنا سيتم استدعاء الباني بمعطى
}
def obj: IntegerNumber;
obj = getIntegerNumber();

نفس الأمر ينطبق في حالة كان لدينا عملية تهيئة لكائن من كائن آخر أو دالة تستلم مُعطى من هذا الصنف، مثلاًهنا لدينا دالة تقوم باستلام مُعطى يمثل كائن من الصنف السابق ثم تقوم بطباعة قيمة المتغير `رقم` (number) الذي بداخله:

دالة اطبع_عددا (عدد: عـدد_صحيح) {
  اطبع(عدد.رقم)؛
}
عرف عدد: عـدد_صحيح؛
اطبع_عددا(عدد)؛
function printNumber (num: IntegerNumber) {
  print(num.number);
}
def num: IntegerNumber;
printNumber(num);

و بالطبع سيعطي خطأ الكود السابق في حال لم نتقيد بالقاعدة (2).

قوالب الأصناف


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

صنف اسم_الصنف [ تعريفات_معطيات_القالب ] {
  // متن_الصنف
}
class class_name [ template_arg_defs ] {
  // class_body
}

أما الاستخدام فيأخذ الصيغة التالية:

عرف اسم_المتغير: اسم_الصنف[معطيات_القالب]؛
def var_name: class_name[template_args];

لفهم قوالب الأصناف دعونا نتابع المثال التالي:
نريد تعريف صنفين كل منهما يمثل نقطة point تحتوي متغيرين يمثلان إحداثياتها النقطة س و ص، لكن أحدهما بياناته هي من الصنف الصحيح، والآخر من الصنف الحقيقي.
الحل بدون استخدام مفهوم القوالب:

اشمل "مـتم/طـرفية"؛
استخدم مـتم.طـرفية؛
// تعريف نقطة بياناتها من الصنف الصحيح
صنف نـقطة_صحيحة {
  عرف س: صحيح؛
  عرف ص: صحيح؛
  عملية هذا~هيئ(س: صحيح, ص: صحيح) {
    هذا.س = س؛
    هذا.ص = ص؛
  }
}
// تعريف نقطة أخرى بياناتها من الصنف الحقيقي
صنف نـقطة_حقيقية {
  عرف س: عائم[64]؛
  عرف ص: عائم[64]؛
  عملية هذا~هيئ(س: عائم, ص: عائم) {
    هذا.س = س؛
    هذا.ص = ص؛
  }
}
عرف نقطة1: نـقطة_صحيحة(5, 3)؛
عرف نقطة2: نـقطة_حقيقية(5.0, 3.0)؛
اطبع("نقطة صحيحة: (%d, %d)\n", نقطة1.س, نقطة1.ص)؛
اطبع("نقطة حقيقية: (%0.1f, %0.1f)", نقطة2.س, نقطة2.ص)؛
/*
نقطة صحيحة: (5, 3)
نقطة حقيقية: (5.0, 3.0)
*/
import "Srl/Console.alusus";
use Srl.Console;
// تعريف نقطة بياناتها من الصنف الصحيح
class IntegerPoint {
  def x: int;
  def y: int;
  handler this~init(x: int, y: int) {
    this.x = x;
    this.y = y;
  }
}
// تعريف نقطة أخرى بياناتها من الصنف الحقيقي
class FloatPoint {
  def x: float[64];
  def y: float[64];
  handler this~init(x: float, y: float) {
    this.x = x;
    this.y = y;
  }
}
def point1: IntegerPoint(5, 3);
def point2: FloatPoint(5.0, 3.0);
print("Integer point: (%d, %d)\n", point1.x, point1.y);
print("Float point: (%0.1f, %0.1f)", point2.x, point2.y);
/*
Integer point: (5, 3)
Float point: (5.0, 3.0)
*/

مع استخدام مفهوم القوالب:

اشمل "مـتم/طـرفية"؛
استخدم مـتم.طـرفية؛
// تعريف صنف واحد يحقق مفهوم القوالب
صنف نـقطة [الـصنف: صنف] {
  عرف س: الـصنف؛
  عرف ص: الـصنف؛
  عملية هذا~هيئ(س: الـصنف, ص: الـصنف) {
    هذا.س = س؛
    هذا.ص = ص؛
  }
}
عرف نقطة1: نـقطة[صحيح](5, 3)؛ // تعريف نقطة بياناتها من الصنف الصحيح
عرف نقطة2: نـقطة[عائم[64]](5.0, 3.0)؛ // تعريف نقطة أخرى بياناتها من الصنف الحقيقي
اطبع("نقطة صحيحة: (%d, %d)\n", نقطة1.س, نقطة1.ص)
اطبع("نقطة حقيقية: (%0.1f, %0.1f)", نقطة2.س, نقطة2.ص)
/*
نقطة صحيحة: (5, 3)
نقطة حقيقية: (5.0, 3.0)
*/
import "Srl/Console.alusus";
use Srl.Console;
// تعريف صنف واحد يحقق مفهوم القوالب
class Point [T: type] {
  def x: T;
  def y: T;
  handler this~init(x: T, y: T) {
    this.x = x;
    this.y = y;
  }
}
def point1: Point[int](5, 3); // تعريف نقطة بياناتها من الصنف الصحيح
def point2: Point[float[64]](5.0, 3.0); // تعريف نقطة أخرى بياناتها من الصنف الحقيقي
print("Integer point: (%d, %d)\n", point1.x, point1.y)
print("Float point: (%0.1f, %0.1f)", point2.x, point2.y)
/*
Integer point: (5, 3)
Float point: (5.0, 3.0)
*/

حيث أن:
الـصنف (T): هو عنصر نائب (placeholder) ينوب عن صنف البيانات إلى حين تحديده عند تعريف كائن من هذا الصنف أي أننا أجلنا تحديد صنف البيانات إلى حين تعريف الكائن.
صنف (type): تعني صنف أو نوع، أي بمعنى أن الـصنف (T) هو عنصر نائب عن صنف بيانات. و هو أحد أنواع معطيات القوالب ال 5 التي سنذكرها بعد قليل.

إذاً لاحظ أننا حصلنا على نفس النتيجة لكن بطريقة أبسط وأقصر.
معطيات القوالب يمكن أن تكون من خمسة أنواع:

  1. صنف (type): أي أن العنصر النائب هو صنف بيانات.
  2. دالة (function): أي أن العنصر النائب هو دالة.
  3. صحيح (integer): أي أن العنصر النائب هو قيمة من الصنف صحيح (Integer).
    اشمل "مـتم/طـرفية"؛
    استخدم مـتم.طـرفية؛
    صنف نـقطة [الـصنف: صنف, قـيمة: صحيح] {
      عرف س: الـصنف = ق ـيمة؛
      عرف ص: الـصنف = ق ـيمة؛
    }
    عرف نقطة: نـقطة[int, 8]؛
    اطبع("نقطة صحيحة: (%d,%d)\ج", نقطة.س, نقطة.ص)؛
    /*
    نقطة صحيحة: (8,8)
    */
    
    import "Srl/Console.alusus";
    use Srl.Console;
    class Point [T:type, Value:integer] {
      def x: T = Value;
      def y: T = Value;
    }
    def point: Point[int, 8];
    print("Integer point: (%d,%d)\n", point.x, point.y);
    /*
    Integer point: (8,8)
    */
    
  4. محارف (string): أي أن العنصر النائب هو سلسة محارف.
  5. شبم (ast): تستخدم في المعالجة التمهيدية للصنف، فهي تمكن المستخدم من تمرير شفرة مصدرية كمعطى للقالب. لمزيد من التفاصيل عنها يمكنك الانتقال إلى التوثيق من هنا.

من الممكن أيضاً تحديد قيم مبدئية لمعطيات القوالب، فمثلاً في الكود السابق كان بإمكاننا وضع قيم افتراضية ل `قـيمة` (Value) ليتم استخدامها في حال لم يتم تحديدها، بالشكل التالي:

اشمل "مـتم/طـرفية"؛
استخدم مـتم.طـرفية؛
صنف نـقطة [الـصنف:صنف, قـيمة:صحيح = 5] {
  عرف س: الـصنف = قـيمة؛
  عرف ص: الـصنف = قـيمة؛
}
عرف نقطة: نـقطة[صحيح]؛
اطبع("نقطة صحيحة: (%d,%d)\ج", نقطة.س, نقطة.ص)؛
/*
نقطة صحيحة: (5,5)
*/
import "Srl/Console.alusus";
use Srl.Console;
class Point [T:type, Value:integer = 5] {
  def x: T = Value;
  def y: T = Value;
}
def point: Point[int];
print("Integer point: (%d,%d)\n", point.x, point.y);
/*
Integer point: (5,5)
*/