التغليف الداخلي للحقول (Self Encapsulate Fields)
ملاحظة قبل البدء: تختلف هذه التقنية عن تقنية تغليف الحقول (Encapsulate Field) من حيث أنّها تُستخدَم لتغليف الحقول الخاصّة (أي المُحدَّدة بالكلمة المفتاحيّة private).
المشكلة
الوصول المباشر إلى الحقول الخاصّة (private fields) داخل الصنف (class).
الحل
إنشاء تابعي الوصول getter و setter للحقل الخاصّ ومنع الوصول إليه إلا عبرهما.
مثال
قبل إعادة التصميم
يحتوي الصنف Range
على الحقلين low
و high
من النوع الخاص (private) ونلاحظ الوصول إليهما مباشرةً داخل التابع includes
في الشيفرة الآتية:
في لغة Java:
class Range {
private int low, high;
boolean includes(int arg) {
return arg >= low && arg <= high;
}
}
في لغة C#:
class Range
{
private int low, high;
bool Includes(int arg)
{
return arg >= low && arg <= high;
}
}
في لغة PHP:
private $low;
private $high;
function includes($arg) {
return $arg >= $this->low && $arg <= $this->high;
}
في لغة TypeScript:
class Range {
private low: number
private high: number;
includes(arg: number): boolean {
return arg >= low && arg <= high;
}
}
بعد إعادة التصميم
أُضيف تابع الوصول getter
للحقلين low
و high
بهدف إجراء التغليف الداخليّ لهما، وأصبح الوصول لهما في التابع includes
عبر تابعي الوصول المُنشَأين، لتصبح الشيفرة كما يلي:
في لغة Java:
class Range {
private int low, high;
boolean includes(int arg) {
return arg >= getLow() && arg <= getHigh();
}
int getLow() {
return low;
}
int getHigh() {
return high;
}
}
في لغة C#:
class Range
{
private int low, high;
int Low {
get { return low; }
}
int High {
get { return high; }
}
bool Includes(int arg)
{
return arg >= Low && arg <= High;
}
}
في لغة PHP:
private $low;
private $high;
function includes($arg) {
return $arg >= $this->getLow() && $arg <= $this->getHigh();
}
function getLow() {
return $this->low;
}
function getHigh() {
return $this->high;
}
في لغة TypeScript:
class Range {
private low: number
private high: number;
includes(arg: number): boolean {
return arg >= getLow() && arg <= getHigh();
}
getLow(): number {
return low;
}
getHigh(): number {
return high;
}
}
لم إعادة التصميم؟
قد لا يوفِّر الوصول المباشر إلى الحقل الخاصِّ المرونة الكافية ببعض الأحيان، فلربما تحتاج إلى تهيئة قيمة الحقل (initiate) عند أول استعلامٍ (query) يُجرَى على الحقل، أو تنفيذِ عمليّات معيّنة على القيم الجديدة المُسندة إلى الحقل حال إسنادها، أو القيام بكلِّ هذا في الأصناف الفرعيّة (subclasses) لهذا الصنف.
فوائد تطبيق الحل
- تحقيق الوصول غير المباشر للحقل (عبر تابعي الوصول getter و setter)، وبهذا تضمن مرونةً أكبر للتعامل مع الحقل الخاصّ، إذ أصبح بالإمكان:
- إجراء العمليات المعقَّدة عند الحصول على قيمة الحقل أو تعديلها، ومن السهل كذلك القيام بالتهيئة الكسولة (lazy initialization) والتأكد من قيمة الحقل (value validation) داخل تابعي الوصول getter وsetter.
- إعادة تعريف التابعين getter و setter في الأصناف الفرعيّة (subclasses).
- قد لا ترى بضرورة إنشاء تابع setter للحقل المطلوب، وعندها سيقتصر إسناد القيم لهذا الحقل على الباني (constructor) فقط، أي سيصبح الحقل غير قابلٍ للتغيير طيلة حياة الكائن (object).
مساوئ تطبيق الحل
لا شكَّ بأنّ الشيفرة تصبح أبسط بالاعتماد على الوصول المباشر للحقل بدلًا من تغليفه، ولكنّه ليس الخيار الأفضل من ناحية المرونة (flexibility).
آلية الحل
- إنشاء تابعٍ جالب
getter
(وربما ضابطsetter
إن دعت الحاجة) للحقل، وإمّا أن يكون مُحدِّد الوصول للتابع من النوع المحمي (protected) أو العام (public). - تبديل كلِّ وصولٍ مباشرٍ للحقل إلى استدعاءٍ للتابع
getter
أوsetter
بحسب الحاجة.