أقرب تمثيل لروبي لمتغير فئة "نهائي ثابت خاص" و"نهائي ثابت عام" في Java؟
سؤال
بالنظر إلى كود Java أدناه، ما هو أقرب ما يمكن أن تمثل هذين الاثنين static final
المتغيرات في فئة روبي؟وهل من الممكن في روبي التمييز بينهما private static
و public static
المتغيرات كما هو الحال في جافا؟
public class DeviceController
{
...
private static final Device myPrivateDevice = Device.getDevice("mydevice");
public static final Device myPublicDevice = Device.getDevice("mydevice");
...
public static void main(String args[])
{
...
}
}
المحلول
لا يوجد حقًا بناء مكافئ في روبي.
ومع ذلك، يبدو أنك ترتكب أحد أخطاء النقل الكلاسيكية:لديك حل في اللغة (أ) وحاول ترجمة ذلك إلى اللغة (ب)، عندما يكون ما يجب عليك فعله حقًا هو معرفة مشكلة ومن ثم معرفة كيفية حلها في اللغة ب.
لا أستطيع حقًا التأكد من المشكلة التي تحاول حلها من خلال مقتطف التعليمات البرمجية الصغير هذا، ولكن هنا واحد فكرة محتملة لكيفية تنفيذها في روبي:
class DeviceController
class << self
def my_public_device; @my_public_device ||= Device['mydevice'] end
private
def my_private_device; @my_private_device ||= Device['mydevice'] end
end
end
هنا آخر:
class DeviceController
@my_public_device ||= Device['mydevice']
@my_private_device ||= Device['mydevice']
class << self
attr_reader :my_public_device, :my_private_device
private :my_private_device
end
end
(الفرق هو أن المثال الأول كسول، فهو يقوم فقط بتهيئة متغير المثيل عند استدعاء قارئ السمة المقابل لأول مرة.أما الخيار الثاني فيقوم بتهيئتها بمجرد تنفيذ نص الفصل، حتى لو لم تكن هناك حاجة إليها مطلقًا، تمامًا كما يفعل إصدار Java.)
دعونا نتناول بعض المفاهيم هنا.
في روبي، كما هو الحال في كل لغة كائنية التوجه "مناسبة" (للتعريفات المختلفة للغة "المناسبة")، الحالة (متغيرات المثيل، الحقول، الخصائص، الفتحات، السمات، أي شيء تريد تسميتها) هي دائماً خاص.هنالك مستحيل للوصول إليهم من الخارج.الطريقة الوحيدة للتواصل مع كائن ما هي إرسال رسائل إليه.
[ملحوظة:عندما أكتب شيئًا مثل "لا مفر"، "دائمًا"، "الطريقة الوحيدة" وما إلى ذلك، فهي في الواقع لا تعني "لا سبيل إلا للتفكير".في هذه الحالة بالذات، هناك Object#instance_variable_set
, ، على سبيل المثال.]
بعبارة أخرى:في روبي، تكون المتغيرات دائمًا خاصة، والطريقة الوحيدة للوصول إليها هي عبر طريقة getter و/أو setter، أو، كما يطلق عليها في Ruby، قارئ و/أو كاتب السمات.
والآن أواصل الكتابة عنه متغيرات سريعة, ، ولكن في مثال Java لدينا الحقول الثابتة, ، أي. فصل المتغيرات.حسنًا، في روبي، على عكس Java، تعتبر الفئات كائنات أيضًا.وهي أمثلة على Class
class وهكذا، تمامًا مثل أي كائن آخر، يمكن أن يكون لديهم متغيرات الحالة.لذلك، في روبي، ما يعادل متغير الفئة هو في الواقع مجرد متغير مثيل قياسي ينتمي إلى كائن يصادف أنه فئة.
(توجد أيضًا متغيرات التسلسل الهرمي للفئة، يُشار إليها بعلامة مزدوجة @@sigil
.هذه أشياء غريبة حقًا، وربما ينبغي عليك تجاهلها.تتم مشاركة متغيرات التسلسل الهرمي للفئة عبر التسلسل الهرمي للفئة بأكملها، أي.الفئة التي ينتمون إليها، وجميع فئاتها الفرعية وفئاتها الفرعية وفئاتها الفرعية ...وأيضا كافة مثيلات كل تلك الفئات.في الواقع، فهي تشبه المتغيرات العالمية أكثر من متغيرات الفئة.ينبغي حقا أن يتم استدعاؤهم $$var
بدلاً من @@var
, ، نظرًا لأنها ترتبط ارتباطًا وثيقًا بالمتغيرات العامة مقارنة بمتغيرات الحالة.إنها ليست عديمة الفائدة تمامًا ولكنها نادرًا ما تكون مفيدة.)
لذلك، قمنا بتغطية الجزء "الحقل" (حقل Java == متغير مثيل روبي)، وقمنا بتغطية الأجزاء "العامة" و"الخاصة" (في روبي، تكون متغيرات المثيل دائمًا خاصة، إذا كنت تريد جعلها عامة، استخدم طريقة getter/setter العامة) وقمنا بتغطية الجزء "الثابت" (حقل Java الثابت == متغير مثيل فئة Ruby).ماذا عن الجزء "الأخير"؟
في Java، "النهائي" هو مجرد طريقة مضحكة لتهجئة "const"، والتي تجنبها المصممون لأن const
الكلمات الرئيسية في لغات مثل C وC++ معطلة بمهارة ولم يرغبوا في إرباك الناس.روبي يفعل تحتوي على ثوابت (يُشار إليها بالبدء بحرف كبير).لسوء الحظ، فهي ليست ثابتة حقًا، لأن محاولة تعديلها، أثناء إصدار تحذير، تنجح بالفعل.لذا، فهي أقرب إلى اتفاقية منها إلى قاعدة يفرضها المترجم.ومع ذلك، فإن التقييد الأكثر أهمية للثوابت هو أنها عامة دائمًا.
لذا فإن الثوابت تكاد تكون مثالية:لا يمكن تعديلها (حسنًا، هم لا ينبغي يتم تعديلها)، أي.هم final
, ، أنهم ينتمون إلى فئة (أو وحدة)، أي.هم static
.لكنهم دائما كذلك public
, ، لذا للأسف لا يمكن استخدامها كنموذج private static final
مجالات.
وهذه هي بالضبط النقطة التي يأتي فيها التفكير في المشكلات بدلاً من الحلول.ما هو الذي تريد؟تريد الدولة ذلك
- ينتمي إلى فئة،
- يمكن قراءتها فقط وليس كتابتها
- تتم تهيئته مرة واحدة فقط و
- يمكن أن تكون خاصة أو عامة.
يمكنك تحقيق كل ذلك، ولكن بطريقة مختلفة تمامًا عن Java:
- متغير مثيل الفئة
- لا توفر طريقة الضبط، بل فقط طريقة getter
- استخدم روبي
||=
مهمة مركبة لتعيين مرة واحدة فقط - طريقة جيتر
الشيء الوحيد الذي يجب أن تقلق بشأنه هو أنك لا تقوم بالتعيين @my_public_device
في أي مكان، أو الأفضل من ذلك، لا تصل إليه على الإطلاق.استخدم دائمًا طريقة getter.
نعم هذه يكون ثغرة في التنفيذ.غالبًا ما يُطلق على روبي اسم "لغة البالغين" أو "لغة البالغين الموافقين"، مما يعني أنه بدلاً من مطالبة المترجم بفرض أشياء معينة، ما عليك سوى وضعها في الوثائق والثقة ببساطة في أن زملائك المطورين قد تعلموا أن لمس الآخرين خصوصيات الناس وقحة..
تماما مختلف نهج الخصوصية هو النهج المستخدم في اللغات الوظيفية:استخدام الإغلاق.عمليات الإغلاق هي كتل من التعليمات البرمجية التي تغلق على بيئتها المعجمية، حتى بعد أن تكون تلك البيئة المعجمية خارج النطاق.تحظى هذه الطريقة في تنفيذ الدولة الخاصة بشعبية كبيرة في Scheme، ولكن تم نشرها مؤخرًا بواسطة Douglas Crockford et al.لجافا سكريبت.إليك مثال في روبي:
class DeviceController
class << self
my_public_device, my_private_device = Device['mydevice'], Device['mydevice']
define_method :my_public_device do my_public_device end
define_method :my_private_device do my_private_device end
private :my_private_device
end # <- here the variables fall out of scope and can never be accessed again
end
لاحظ الفرق الدقيق ولكن المهم بين الإصدارات الموجودة أعلى إجابتي:عدم وجود @
سيجيل.هنا، نحن نخلق محلي المتغيرات، لا مثال المتغيرات.بمجرد انتهاء نص الفصل، تخرج هذه المتغيرات المحلية من النطاق ولا يمكن الوصول إليها مرة أخرى أبدًا. فقط لا يزال بإمكان الكتلتين اللتين تحددان طريقتي getter الوصول إليهما، لأنهما تغلقان على نص الفصل.الآن، هم حقًا خاص و هم final
, ، لأن الشيء الوحيد في البرنامج بأكمله الذي لا يزال بإمكانه الوصول إليهم هو الملف النقي getter طريقة.
ربما لا تكون هذه روبي اصطلاحية، ولكن بالنسبة لأي شخص لديه خلفية Lisp أو JavaScript، يجب أن يكون واضحًا بدرجة كافية.كما أنها أنيقة جدًا.
نصائح أخرى
أقرب شيء يمكنني التفكير فيه في متغير نهائي هو وضع المتغير في السؤال كمتغير مثيل من الوحدة النمطية:
class Device
# Some static method to obtain the device
def self.get_device(dev_name)
# Need to return something that is always the same for the same argument
dev_name
end
end
module FinalDevice
def get_device
# Store the device as an instance variable of this module...
# The instance variable is not directly available to a class that
# includes this module.
@fin ||= Device.get_device(:my_device).freeze
end
end
class Foo
include FinalDevice
def initialize
# Creating an instance variable here to demonstrate that an
# instance of Foo cannot see the instance variable in FinalDevice,
# but it can still see its own instance variables (of course).
@my_instance_var = 1
end
end
p Foo.new
p (Foo.new.get_device == Foo.new.get_device)
هذا المخرجات:
#<Foo:0xb78a74f8 @my_instance_var=1>
true
الخدعة هنا هي أنه من خلال تثبيت الجهاز في وحدة نمطية، يمكنك فقط الوصول إلى الجهاز من خلال هذه الوحدة. من الفصل Foo
, ، لا توجد طريقة لتعديل أيّ الجهاز الذي تحصل عليه، دون التمثيل مباشرة على Device
الطبقة أو FinalDevice
وحدة. ال freeze
اتصل FinalDevice
قد يكون أو لا يكون مناسبا، اعتمادا على احتياجاتك.
إذا كنت ترغب في إجراء ملحق عام وخاص، يمكنك تعديل Foo
مثله:
class Foo
include FinalDevice
def initialize
@my_instance_var = 1
end
def get_device_public
get_device
end
private
def get_device_private
get_device
end
private :get_device
end
في هذه الحالة ربما تحتاج إلى تعديل FinalDevice::get_device
لاتخاذ حجة كذلك.
تحديث: أشار @ لبنان ذلك @fin
كما أعلن في FinalDevice
في الواقع يمكن الوصول إليها بمثيل Foo
. وبعد لقد افترضني بضغط أنه لأنه لم يكن في إخراج النص الافتراضي بواسطة Foo#inspect
, لم يكن في الداخل Foo
.
يمكنك علاج هذا من خلال جعل أكثر صراحة @fin
مثال على متغير FinalDevice
وحدة:
class Device
def self.get_device(dev_name)
dev_name
end
end
module FinalDevice
def get_device
FinalDevice::_get_device
end
protected
def self._get_device
@fin ||= Device.get_device(:my_device).freeze
end
end
class Foo
include FinalDevice
def get_device_public
get_device
end
def change_fin
@fin = 6
@@fin = 8
end
private
def get_device_private
get_device
end
private :get_device
end
f = Foo.new
x = f.get_device_public
f.change_fin
puts("fin was #{x}, now it is #{f.get_device_public}")
المخرجات بشكل صحيح:
fin was my_device, now it is my_device
class DeviceController
MY_DEVICE = Device.get_device("mydevice")
end
و نعم، require 'device'
إذا لزم الأمر.
على الرغم من أن لا شيء سوف يمنعك من إعادة تعريف ثابت في مكان آخر، إلا أن تحذير :)
ثابت ثابت في روبي:
class DeviceController
@@my_device = Device.get_device("mydevice")
end
ثابت العام في روبي:
class DeviceController
def self.my_device; @@my_device; end
@@my_device = Device.get_device("mydevice")
end
لا يمكن ل Ruby عدم وجود "نهائي" :)