سؤال

شيء تساءلت عنه لفترة طويلة: لماذا لا تتمكن Delphi Records من الحصول على الميراث (وبالتالي جميع ميزات OOP المهمة الأخرى)؟

هذا من شأنه أن يجعل السجلات أساسًا النسخة المخصصة للصفوف ، تمامًا مثل فئات C ++ ، وسيجعل "الكائنات" (ملاحظة: ليس الحالات) قديمة. لا أرى أي شيء مشكلة معها. ستكون هذه أيضًا فرصة جيدة لتنفيذ إعلانات إلى الأمام للسجلات (والتي ما زلت محيرًا حول سبب عدم وجود مفقود).

هل ترى أي مشاكل مع هذا؟

هل كانت مفيدة؟

المحلول

ذات صلة بهذا السؤال ، هناك نوعان من الميراث: الواجهة الوراثية وميراث التنفيذ.

وراثة الواجهة ينطوي بشكل عام على تعدد الأشكال. وهذا يعني أنه إذا تم اشتقاق B من A ، فيمكن تخزين قيم النوع B في مواقع من النوع A. هذا يمثل مشكلة بالنسبة لأنواع القيمة (مثل السجلات) بدلاً من الأنواع المرجعية ، بسبب التقطيع. إذا كان B أكبر من A ، فإن تخزينه في موقع من النوع A سيؤدي إلى اقتطاع القيمة - أي حقول تمت إضافتها في تعريفها فوق تلك التي ستضيع.

ميراث التنفيذ أقل إشكالية من هذا المنظور. إذا كان لدى Delphi ميراث قياسي ولكن فقط للتنفيذ ، وليس للواجهة ، فلن تكون الأمور سيئة للغاية. المشكلة الوحيدة هي أن ببساطة جعل قيمة النوع A حقل من النوع B يقوم بمعظم ما تريده من ميراث التنفيذ.

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

لذلك من أجل دعم ميراث واجهة السجل بشكل صحيح بشكل صحيح ، سنحتاج إلى نوع من الحل لمشكلة التقطيع. سيكون الملاكمة نوعًا واحدًا من الحلول ، ولكنه يتطلب عمومًا أن يكون جمع القمامة قابلاً للاستخدام ، وسيقدم الغموض في اللغة ، حيث قد لا يكون واضحًا ما إذا كنت تعمل بقيمة أو مرجعًا - مثل عدد صحيح مقابل عدد صحيح مقابل int في جافا مع autoboxing. على الأقل في Java ، هناك أسماء منفصلة لـ "أنواع" القيمة غير المربعة مقابل أنواع القيمة. هناك طريقة أخرى للقيام بالملاكمة مثل Google Go مع واجهاتها ، وهي نوع من ميراث الواجهة دون ميراث التنفيذ ، ولكنها تتطلب تعريف الواجهات بشكل منفصل ، وجميع مواقع الواجهة هي مراجع. يتم محاذاة أنواع القيمة (مثل السجلات) عند الإشارة إليها بواسطة مرجع واجهة. وبالطبع ، GO لديه أيضًا مجموعة من القمامة.

نصائح أخرى

السجلات والفئات/الكائنات هما شيئان مختلفان للغاية في دلفي. في الأساس ، يكون سجل Delphi عبارة عن هيكل C - حتى يدعم Delphi بناء الجملة للقيام بأشياء مثل وجود سجل يمكن الوصول إليه إما على 4 أعداد صحيحة 16bit أو أعداد صحيحة 2 32 بت. يحب struct, record يعود تاريخه إلى قبل برمجة الكائن الموجهة إلى اللغة (عصر Pascal).

مثل الهيكل ، يعد السجل أيضًا جزءًا مضمّنًا من الذاكرة ، وليس مؤشرًا إلى جزء كبير من الذاكرة. هذا يعني أنه عندما تقوم بتمرير سجل في وظيفة ، فأنت تمرر نسخة ، وليس مؤشرًا/مرجعًا. هذا يعني أيضًا أنه عندما تعلن عن متغير نوع السجل في الكود الخاص بك ، يتم تحديده في وقت الترجمة مدى حجمه - سيتم تخصيص متغيرات نوع السجل المستخدمة في الدالة على المكدس (ليس كمؤشر على المكدس ، ولكن كما 4 ، 10 ، 16 ، إلخ بنية بايت). هذا الحجم الثابت لا يلعب بشكل جيد مع تعدد الأشكال.

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

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

خلاصة القول: إذا كنت تريد نوع بيانات يمكن ورثها وتوسيعها ، فاستخدم الفئات. هذا ما هم هناك.

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

لأن السجلات ليس لديها VMT (جدول الطريقة الافتراضية).

يمكنك محاولة استخدام دلفي هدف الكلمة الرئيسية لذلك. هؤلاء هم في الأساس وراثي ، ولكن يتصرفون مثل السجلات أكثر من الفصول الدراسية.

انظر الى هذا مسلك وهذا وصف.

في الأوقات الماضية ، استخدمت الكائنات (وليس الفصول!) كسجلات ذات ميراث.

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

مثل هذه الحالات نادرة جدا ، رغم ذلك.

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

على سبيل المثال. هذا المثال يعمل في Delphi XE4. قم بإنشاء تطبيق نماذج VCL جديد واستبدل الرمز من Unit1 بالرمز التالي:

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Types;

type

  TMyArray2D = array [0..1] of single;

  TMyVector2D = record
  public
    function Len: single;
    case Integer of
      0: (P: TMyArray2D);
      1: (X: single;
          Y: single;);
  end;

  TMyHelper = record helper for TMyVector2D
    function Len: single;
  end;


  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


implementation

function TMyVector2D.Len: Single;
begin
  Result := X + Y;
end;

function TMyHelper.Len: single;
begin
  Result := Sqrt(Sqr(X) + Sqr(Y));
end;

procedure TestHelper;
var
  Vec: TMyVector2D;
begin
  Vec.X := 5;
  Vec.Y := 6;
  ShowMessage(Format('The Length of Vec is %2.4f',[Vec.Len]));
end;

procedure TForm1.Form1Create(Sender: TObject);
begin
  TestHelper;
end;

لاحظ أن النتيجة هي 7.8102 بدلاً من 11. هذا يدل على أنه يمكنك إخفاء أساليب الأعضاء في الفئة الأصلية أو السجل مع فئة أو مساعد تسجيل.

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

شكرا لطرحك السؤال. من المؤكد أنني تعلمت الكثير في محاولة العثور على الإجابة وساعدني كثيرًا أيضًا.

براين جوزيف جونز

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top