هل من الممكن مقارنة السمات الخاصة في روبي؟
-
11-09-2019 - |
سؤال
أنا أفكر في:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
لكنه لا يعمل.
رسالة الخطأ هي:
syntax error, unexpected tIVAR
كيف يمكنني مقارنة سماتهما الخاصة من نفس الفصل بعد ذلك؟
المحلول
هناك العديد من الطرق
Getter:
class X
attr_reader :a
def m( other )
a == other.a
end
end
instance_eval
:
class X
def m( other )
@a == other.instance_eval { @a }
end
end
instance_variable_get
:
class X
def m( other )
@a == other.instance_variable_get :@a
end
end
لا أعتقد أن الروبي لديه مفهوم "صديق" أو "محمي"، وحتى "خاص" يتم اختراقه بسهولة. يؤدي استخدام Getter إلى إنشاء خاصية للقراءة فقط، ويعني مثيل_EVAL أن عليك معرفة اسم المتغير المثيل، وبالتالي فإن الدلالة مشابهة.
نصائح أخرى
كانت هناك بالفعل العديد من الإجابات الجيدة على مشكلتك الفورية، لكنني لاحظت بعض القطع الأخرى من التعليمات البرمجية التي تضمن التعليق. (معظمهم تافهة، رغم ذلك.)
فيما يلي أربعة منها تافهة، كلها مرتبطة بأسلوب الترميز:
- المسافة البادئة: أنت خلط 4 مساحات للمسافة البادئة و 5 مسافات. من الأفضل عموما التمسك فقط واحد أسلوب المسافة البادئة، وفي روبي هو عموما 2 مساحات.
- إذا كانت هناك طريقة لا تأخذ أي معلمات، فمن المعتاد أن تغادر النظارات النارية في تعريف الطريقة.
- وبالمثل، إذا قمت بإرسال رسالة دون وسيطات، يتم إيقاف تشغيل الجوارب.
- لا توجد مسافة بيضاء بعد خطوة فتح وقبل إغلاق واحد، إلا في كتل.
على أي حال، هذه مجرد الاشياء الصغيرة. الاشياء الكبيرة هي:
def new
@a = 1
end
هذا لا ليس افعل ما تعتقد أنه يفعل ذلك! هذا يحدد نموذج طريقة دعا X#new
و ليس طريقة فئة تسمى X.new
!
ما تتصل هنا:
x = X.new
هو صف دراسي طريقة دعا new
, ، الذي ورثته من Class
صف دراسي. لذلك، أنت لا تتصل بهذه الطريقة الجديدة، مما يعني @a = 1
لا يتم تنفيذها أبدا، مما يعني @a
غير محدد دائما، مما يعني أنه ستقيم دائما ل nil
مما يعني @a
من self
و ال @a
من other
سوف تكون دائما نفس الشيء الذي يعني m
سوف يكون دائما true
!
ما تريد القيام به هو توفير منشئ، باستثناء روبي لا يملك منشئون. روسي يستخدم فقط طرق المصنع.
طريقة لك حقا مطلوب لتجاوز هو نموذج طريقة initialize
. وبعد الآن ربما تسأل نفسك: "لماذا يجب أن أتجاوز نموذج طريقة دعا initialize
عندما أنا فعلا استدعاء صف دراسي طريقة دعا new
?"
حسنا، إن بناء الكائنات في Ruby يعمل مثل هذا: يتم تقسيم بناء الكائن إلى مرحلتين، توزيع و التهيئة. وبعد يتم التخصيص من قبل طريقة الطبقة العامة تسمى allocate
, ، والتي يتم تعريفها كطريقة مثيل للفئة Class
وعموما أبدا تجاوز. يخصص فقط مساحة الذاكرة للكائن وإعداد عدد قليل من المؤشرات، ومع ذلك، فإن الكائن غير قابل للاستخدام حقا في هذه المرحلة.
هذا هو المكان الذي يأتي فيه التهيئة: إنها طريقة مثيل يسمى initialize
, ، الذي يقوم بإعداد حالة الكائن الداخلية ويجلبه إلى حالة ثابتة ومحددة بالكامل يمكن استخدامها من قبل أشياء أخرى.
لذلك، من أجل إنشاء كائن جديد تماما، ما عليك القيام به هو:
x = X.allocate
x.initialize
ملاحظة: قد يتعرف المبرمجون الهدف على هذا.
ومع ذلك، لأنه من السهل جدا أن تنسى الاتصال initialize
وكقاعدة عامة يجب أن يكون كائن صالح تماما بعد البناء، هناك طريقة مصنع راحة تسمى Class#new
, ، والتي كلها تعمل من أجلك وتبدو شيء مثل هذا:
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
ملاحظة: في الواقع، initialize
هو خاص، لذلك يجب استخدام الانعكاس للتحايل على قيود الوصول مثل هذا: obj.send(:initialize, *args, &block)
]
أخيرا، اسمحوا لي أن أشرح ما الذي يحدث في حياتك m
طريقة. (أوضح الآخرون بالفعل كيفية حلها.)
في Ruby، لا توجد وسيلة (ملاحظة: في Ruby، "لا توجد طريقة" يترجم فعلا إلى "هناك دائما طريقة تنطوي على انعكاس") للوصول إلى متغير مثيل من خارج المثيل. لهذا السبب يسمى متغير مثيل بعد كل شيء، لأنه ينتمي إلى المثيل. هذه إرث من SmallTalk: في Smalltalk لا توجد قيود على الرؤية، الكل الأساليب عامة. وبالتالي، فإن المثيل المتغيرات هي فقط طريقة للقيام بتغليفها في Smalltalk، وبعد كل شيء، تعد التغليف واحدا من أعمدة OO. في روبي، هناك نكون قيود الرؤية (كما رأينا أعلاه، على سبيل المثال)، لذلك فهي ليست ضرورية بدقة لإخفاء متغيرات المثيل لهذا السبب. هناك سبب آخر، ومع ذلك: مبدأ الوصول الموحد.
تنص uap على كيفية استعمال يجب أن تكون الميزة مستقلة عن كيفية وجود ميزة منفذ. وبعد لذلك، يجب أن تكون الوصول إلى ميزة هي نفسها، أي موحدة. السبب في ذلك هو أن مؤلف الميزة مجاني لتغيير كيفية عمل الميزة داخليا، دون كسر مستخدمي الميزة. بمعنى آخر، إنها مشرفة أساسية.
هذه الوسيلة على سبيل المثال أن الحصول على حجم المجموعة يجب أن تكون هي نفسها دائما، بغض النظر عما إذا كان الحجم يتم تخزينه في متغير، محسوب ديناميكيا في كل مرة، قامت بحسابها في المرة الأولى ثم تخزينها في متغير أو مذكرت أو أيا كان. يبدو واضحا، ولكن على سبيل المثال جافا يحصل هذا الخطأ:
obj.size # stored in a field
ضد.
obj.getSize() # computed
روبي يأخذ السهل الخروج. في روبي، هناك فقط واحد طريقة لاستخدام ميزة: إرسال رسالة. نظرا لوجود طريقة واحدة فقط، فإن الوصول موحدة تافهة.
لذلك، لجعل قصة قصيرة طويلة: يمكنك ببساطة عدم الوصول إلى متغير مثيل مثيل آخر. يمكنك التفاعل فقط مع هذه الحالة عبر إرسال الرسائل. مما يعني أن الكائن الآخر يجب أن يوفر لك طريقة (في هذه الحالة على الأقل protected
الرؤية) للوصول إلى متغير مثيلها، أو عليك أن تنتهك مغلفة الكائن (وبالتالي فقدان الوصول الموحد، وزيادة اقتران وكسر المستقبل في المستقبل) باستخدام الانعكاس (في هذه الحالة instance_variable_get
).
هنا هو في كل مجدها:
#!/usr/bin/env ruby
class X
def initialize(a=1)
@a = a
end
def m(other)
@a == other.a
end
protected
attr_reader :a
end
require 'test/unit'
class TestX < Test::Unit::TestCase
def test_that_m_evaluates_to_true_when_passed_two_empty_xs
x, y = X.new, X.new
assert x.m(y)
end
def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
assert X.new('foo').m(X.new('foo'))
end
end
أو بدلا من ذلك:
class X
def m(other)
@a == other.instance_variable_get(:@a)
end
end
أي واحد من هذين اثنين أنت اخترت مسألة ذوق شخصي، أود أن أقول. ال Set
يستخدم الفصل في المكتبة القياسية إصدار الانعكاس، على الرغم من هو - هي الاستخدامات instance_eval
في حين أن:
class X
def m(other)
@a == other.instance_eval { @a }
end
end
(ليس لدي أي فكرة لماذا. ربما instance_variable_get
ببساطة لم تكن موجودة عندما Set
كتب. يبلغ عمر روبي 17 عاما في فبراير، وبعض الأشياء في Stdlib من الأيام الأولى.)
إذا كنت لا تستخدم instance_eval
الخيار (as @ jleedev نشر)، واختر استخدام getter
الطريقة، لا يزال بإمكانك الاحتفاظ بها protected
إذا كنت تريد protected
الطريقة في Ruby، فقط قم بما يلي لإنشاء أحد Getter يمكن قراءته إلا من كائنات من نفس الفئة:
class X
def new()
@a = 1
end
def m( other )
@a == other.a
end
protected
def a
@a
end
end
x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a # Throws error
لست متأكدا، ولكن هذا قد يساعد:
خارج الفصل، إنه أصعب قليلا:
# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
from (irb):9
# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
=> []
http://whynotwiki.com/ruby_/_variables_and_constants#variable_scope.2faciessivity.