Reflection (תכנות)

מתוך המכלול, האנציקלופדיה היהודית
קפיצה לניווט קפיצה לחיפוש

במדעי המחשב, reflection (ריפלקשן; מילולית מאנגלית: השתקפות או התבוננות) היא היכולת של תוכנת מחשב לבחון אובייקטים או לשנות את התנהגותם בזמן ריצה. בחינת האובייקטים מבוצעת באמצעות תהליך הנקרא type introspection - "הסתכלות פנימה על טיפוסים", ושינוי ההתנהגות של אובייקטים כוללת את היכולת לשנות ערכים, metadata, תכונות (properties) ופונקציות.

השימוש הנפוץ ביותר ב-reflection הוא בשפות עילית הרצות על גבי מכונה וירטואלית, כדוגמת Smalltalk. כמו כן השימוש ב-reflection נפוץ בשפות תסריט ובשפות בעלות טיפוסיות סטטית כגון: ML, Java, ו-Haskell.

לשפות תכנות התומכות ב-reflection קוראים "שפות רפלקטיביות" (reflective languages). לתכנות הכולל שימוש ב-reflection קוראים "תכנות מונחה-reflection-oriented programming) "reflection).

רקע היסטורי

התכנות של מחשבים מוקדמים בוצע בשפת הסף הטבעית שלהם. שפות סף כאלה הן רפלקטיביות מטבען, מכיוון שהן פועלות על ידי הגדרת ההוראות בצורה של נתונים. פונקציונליות רפלקטיבית כגון שינוי ההוראות או ניתוח שלהן הייתה דבר שבשגרה. כאשר התכנות התקדם לשפות תכנות עיליות יותר כדוגמת שפת C, השימוש ב-reflection נעלם, עד שהופיעו שפות תכנות שמערכת הטיפוסים שלהן כוללת תמיכה ב-reflection.

שימושים

ניתן להשתמש ב-reflection כדי לבחון את ביצוע התוכנית ולהשפיע עליה בזמן ריצה. רכיב בתוכנה מונחית-reflection יכול לפקח אחר הביצוע של יחידת קוד, ולשנות את עצמו בהתאם למטרה שרוצים להשיג בהקשר ליחידת קוד זו. בדרך כלל זה נעשה על ידי הקצאת קוד בזמן ריצה.

בשפות תכנות מונחות-עצמים כמו Java, שימוש ב-reflection מאפשר בדיקה בזמן ריצה של מחלקות, ממשקים, שדות ומתודות, מבלי לדעת את השמות שלהם בזמן הידור. כמו כן זה מאפשר יצירת מופעים חדשים של אובייקטים וקריאה למתודות.

בנוסף לכך, ניתן להשתמש ב-reflection על מנת לסגל תוכניות למצבים שונים בצורה דינאמית. לדוגמה, נחשוב על יישום המשתמש לסירוגין בשתי מחלקות שונות, X ו-Y, לצורך ביצוע של משימות דומות. בלי תכנות מונחה-reflection, יכול להיות שהיישום יהיה מתוכנת בצורה קשיחה (hard-coded) לקרוא לשמות של מתודות של מחלקה X ומחלקה Y. לעומת זאת, על ידי שימוש בפרדיגמת התכנות מונחה-reflection, ניתן לממש את היישום כך שישתמש ב-reflection כדי לקרוא למתודות במחלקות X ו-Y, מבלי לקודד מראש בצורה קשיחה את שמות המתודות.

על מנת לנצל את היתרונות שבכתיבת קוד גנרי יותר באמצעות תכנות מונחה-reflection, כמעט תמיד נדרשות מומחיות ותשתיות תוכנה נוספות. מידת השימוש ב-reflection קובעת עד כמה ניתן יהיה להימנע משימוש ב-hard coding (קידוד קשיח).

לעיתים קרובות משתמשים ב-reflection בבדיקות תוכנה, כאשר רוצים ליצור מופעים של "אובייקטי דמה" (mock objects) בזמן ריצה.

כמו כן, reflection היא האסטרטגיה הראשית ב-metaprogramming – כתיבת תוכנות מחשב שכותבות או משנות תוכנות אחרות.

מימוש

שפת תכנות הכוללת תמיכה ב-reflection, מספקת מספר שירותים ייעודיים הזמינים בזמן ריצה, שאחרת היו קשים מאד למימוש בשפה שהיא low-level יותר. בין היתר, שירותים אלו מאפשרים:

  • לגלות ולשנות יחידות קוד (כדוגמת בלוקים של קוד, מתודות, פרוטוקולים, וכדומה) בזמן ריצה, כ"אובייקטים מן השורה" (first-class objects).
  • להמיר מחרוזת המתאימה לשם הסימבולי של מחלקה או פונקציה, לקישור (reference) או קריאה לאותה מחלקה או פונקציה.
  • להעריך מחרוזת בזמן ריצה, כאילו הייתה הצהרה בקוד מקור.
  • ליצור מפרש חדש עבור ה-bytecode של השפה, על מנת לתת משמעות או שימוש חדשים עבור מבנה תכנותי מסוים.

שירותים אלה ניתנים למימוש בדרכים שונות. שפות העוברות הידור מסתמכות על מערכת סביבת ההרצה (runtime) שלהן כדי לקבל מידע אודות קוד המקור. לדוגמה, קובץ הרצה מהודר בשפת Objective-C, שומר את שמות כל המתודות בבלוק בתוך הקובץ, ובכך מספק טבלה להתאמה בין שמות המתודות למתודות עצמן בתוכנית המהודרת.

בשפה מהודרת התומכת ביצירת פונקציות בזמן ריצה, כדוגמת Common Lisp, סביבת ההרצה חייבת לכלול מהדר או מפרש. כמו כן, ניתן לממש reflection גם בשפות שאינן כוללות מנגנונים מובנים לתמיכה ב-reflection, על ידי שימוש במערכת ל-program transformation, כדי להגדיר שינויים אוטומטיים בקוד המקור.

דוגמאות

קטעי הקוד הבאים יוצרים מופע foo של המחלקה Foo, ואז קוראים למתודה hello. עבור כל שפת תכנות, מוצגות הקריאות הרגילות וגם הקריאות שעושות שימוש ב-reflection.

Java

// Using Java package: java.lang.reflect

// Without reflection
new Foo().hello();

// With reflection
Class<?> clazz = Class.forName("Foo");
clazz.getMethod("hello").invoke(clazz.newInstance());

JavaScript

// Without reflection
new Foo().hello()

// With reflection

// assuming that Foo resides in this
new this['Foo']()['hello']()

// or without assumption
new (eval('Foo'))()['hello']()

// or simply
eval('new Foo().hello()')

PHP

// without reflection
$foo = new Foo();
$foo->hello();

// with reflection
$reflector = new ReflectionClass('Foo');
$foo = $reflector->newInstance();
$hello = $reflector->getMethod('hello');
$hello->invoke($foo);

// using callback
$foo = new Foo();
call_user_func(array($foo, 'hello'));

// using variable variables syntax
$className = 'Foo';
$foo = new $className();
$method = 'hello';
$foo->$method();

Ruby

# without reflection
obj = Foo.new
obj.hello

# with reflection
class_name = "Foo"
method = :hello
obj = Kernel.const_get(class_name).new
obj.send method

Objective-C

// Foo class
@interface Foo : NSObject
- (void)hello;
//...
@end

// without reflection
Foo *obj = [[Foo alloc] init];
[obj hello];

// with reflection in OPENSTEP, loading class and call method using variables.
NSString *className = @"Foo";
SEL selector = @selector(hello);
id obj = [[NSClassFromString(className) alloc] init];
[obj performSelector:selector]; // This will emit an warning when compiling with Objective-C ARC.

ראו גם

קישורים חיצוניים