برنامه نویسی ۱۳۵۰ بازدید

کپسوله‌سازی به مجموعه‌ای از ابزارها گفته می‌شود که می‌توانند دسترسی به داده‌ها یا متدها را محدود سازند و بدین ترتیب از دستکاری آن‌ها جلوگیری کنند. کپسوله‌سازی یکی از ستون‌های برنامه نویسی شی گرا در C++‎ و سایر زبان‌های شی‌گرا به حساب می‌آید. در این مقاله قصد داریم نمونه‌هایی از کپسوله‌سازی در ++C و C را مورد بررسی قرار دهیم.

کپسوله‌سازی در ++C

داده‌ها و متدها به طور پیش‌فرض در class و به صورت private هستند. این بدان معنی است که آن‌ها تنها از سوی شیء/کلاس مربوطه قابل دسترسی و تغییر هستند. در ++C می‌توان سطح دسترسی را با استفاده از ارائه کلیدواژه‌های مناسب، بر روی مقادیر دیگری نیز تعیین کرد.

در ++C چند قید برای تعیین سطح دسترسی داده‌ها وجود دارند:

  • Public – از سوی کلاس، فرزندان کلاس و از هر جایی خارج از کلاس قابل دسترسی است.
  • Protected – تنها از سوی کلاس و کلاس‌های فرزند آن قابل دسترسی است.
  • Private – تنها از سوی کلاس قابل دسترسی است.

برای این که مثال‌ها خلاصه‌تر باشند، تنها دو سطح خصوصی و عمومی در ادامه ارائه شده‌اند.

مثال ساده‌ای از کپسوله‌سازی

درون کلاس class Contact، متغیرها و متدهایی که به وسیله public تعریف شده‌اند می‌توانند در main یعنی خارج از کلاس مورد دسترسی قرار گیرند. متغیرها و متدهایی که با استفاده از private تعریف می‌شوند، تنها از سوی کلاس قابل خواندن و تغییر هستند.

#include <iostream>
using namespace std;

class Contact
{
    private:
        int mobile_number;           // private variable
        int home_number;             // private variable
    public:
        Contact()                    // constructor
        {
            mobile_number = 12345678;
            home_number = 87654321;
        }
        void print_numbers()
        {
            cout << "Mobile number: " << mobile_number;
            cout << ", home number: " << home_number << endl;
        }
};

int main()
{
    Contact Tony;
    Tony.print_numbers();
    // cout << Tony.mobile_number << endl;
    // will cause compile time error
    return 0;
}

اگر تلاش کنید private int mobile_number را در main ویرایش کنید، موجب بروز خطای کامپایل می‌شود، زیرا دسترسی به داده‌های private در کلاس محدود شده است.

دور زدن کپسوله‌سازی به کمک «دوستان» (روش مجاز)

زبان ++C کلیدواژه friend را در اختیار برنامه‌نویس قرار می‌دهد که می‌تواند استثناهایی در قواعد کلی محدودیت دسترسی به داده‌ها ارائه کند. اگر تابع یا کلاس به صورت یک friend برای یک class Contact تعریف شده باشد، می‌تواند به داده‌های protected یا private دسترسی داشته باشد.

دو قاعده اصلی در مورد رابطه دوستی وجود دارد: این رابطه به ارث نمی‌رسد و تغییرپذیر نیست. ضمناً ایجاد رابطه دوستی سطح دسترسی را به طور کلی تغییر نمی‌دهد. داده‌های خصوصی همچنان تنها در خود کلاس قابل دسترسی هستند و رابطه دوستی یک استثنا محسوب می‌شود.

#include <iostream>
using namespace std;

class Contact
{
    private:
        int mobile_number;           // private variable
        int home_number;             // private variable
    public:
        Contact()                    // constructor
        {
            mobile_number = 12345678;
            home_number = 87654321;
        }
        // Declaring a global 'friend' function
        friend void print_numbers( Contact some_contact );
};

void print_numbers( Contact some_contact )
{
    cout << "Mobile number: " << some_contact.mobile_number;
    cout << ", home number: " << some_contact.home_number << endl;
}

int main()
{
    Contact Tony;
    print_numbers(Tony);
    return 0;
}

در این مثال، تابع یک تابع عادی است و نه یک متد از class Contact. تنها دلیل این که ()print_numbers می‌تواند به داده‌های private دسترسی داشته باشد این است که به وسیله یک تابع friend تعریف شده است. اگر تعریف friend حذف شود، کد کامپایل نخواهد شد.

نکته: دوستان نباید مورد سوءاستفاده قرار گیرند. افزودن رابطه دوستی باید به عنوان یک استثنا نگریسته شود و نه یک رویه عمومی.

کپسوله‌سازی در ++C

دور زدن کپسوله‌سازی با Typecast و اشاره‌گرها (روش کاملاً غیر مجاز)

قبل از هر چیز باید بگوییم که استفاده از اشاره‌گرها و Typecast کردن به این روش یک رویه بد محسوب می‌شود. این روش ممکن است داده‌های مورد نظر را به دست بدهد و شاید هم ندهد. خواندن آن دشوار است و نگهداری آن نیز دشوارتر است؛ اما در هر حال این نیز یک روش محسوب می‌شود.

++C ابزارهای قدرتمند زیادی را از C به ارث برده است که یکی از آن‌ها Typecast است. به صورت پیش‌فرض همه متغیرهای class و متدهای آن به صورت private هستند. همزمان سطح دسترسی پیش‌فرض به مقادیر ذخیره شده در struct به صورت public است. این امکان کاملاً مهیا است که یک struct یا کلاس کاملاً public با همان طرح‌بندی class Contact ساخته شود و از Typecast کردن برای دسترسی به داده‌های خصوصی سوءاستفاده شود.

#include <iostream>
using namespace std;

class Contact
{
    private:
        int mobile_number;           // private variable
        int home_number;             // private variable
    public:
        Contact()                    // constructor
        {
            mobile_number = 12345678;
            home_number = 87654321;
        }
        // Declaring a global 'friend' function
        friend void print_numbers( Contact some_contact );
};

void print_numbers( Contact some_contact )
{
    cout << "Mobile number: " << some_contact.mobile_number;
    cout << ", home number: " << some_contact.home_number << endl;
}

int main()
{
    Contact Tony;
    print_numbers(Tony);
    return 0;
}

کپسوله‌سازی در C

کپسوله‌سازی به طور معمول به عنوان یکی از مفاهیم کلیدی شیءگرایی نگریسته می‌شود. با این وجود صرفاً به زبان‌های شیءگرا محدود نمی‌شود. کپسوله‌سازی در زبان C علی‌رغم نبود کلیدواژه‌های private و public برای مدت مدیدی مورد استفاده قرار می‌گرفته است.

متغیرهای private

بر حسب کپسوله‌سازی همه داده‌ها در C می‌توانند از طریق مجزاسازی تعاریف از main که با بهره‌گیری از هدرها و فایل‌های منبع متفاوت حاصل می‌شود قابل اجرا است. در مثال زیر در فایل private_var.c یک structure تعریف شده است. از آنجا که struct در C نیازمند تخصیص و آزادسازی حافظه است، چند تابع کمکی نیز اضافه شده است:

#include "private_var.h"
#include <stdio.h>
#include <stdlib.h>

struct Contact
{
    int mobile_number;
    int home_number;
};

struct Contact * create_contact()
{
    struct Contact * some_contact;
    some_contact = malloc(sizeof(struct Contact));
    some_contact->mobile_number = 12345678;
    some_contact->home_number = 87654321;
    return( some_contact );
}

void delete_contact( struct Contact * some_contact )
{
    free(some_contact);
}

در فایل هدر متناظر structure به نام Contact تعریف شده است، اما طرح‌بندی آن افشا نشده است.

#ifndef PRIVATE_VAR
#define PRIVATE_VAR

struct Contact;

struct Contact * create_contact();
void delete_contact( struct Contact * some_contact );

#endif /* PRIVATE_VAR */

فایل main.c چیزی در مورد ساختار درونی struct نمی‌داند و اگر تلاش کند که داده‌های خصوصی را بخواند یا بنویسد خطایی تولید خواهد شد.

#include "private_var.h"
#include <stdio.h>

int main()
{
    struct Contact * Tony;
    Tony = create_contact();
    // printf( "Mobile number: %d\n", Tony->mobile_number);
    // will cause compile time error
    delete_contact( Tony );
    return 0;
}

دسترسی به متغیرهای خصوصی با استفاده از اشاره‌گرها

Typecast کردن می‌تواند به عنوان ابزاری برای دور زدن کپسوله‌سازی در C و همچنین ++C مورد استفاده قرار گیرد؛ اما این روش قبلاً نمایش یافته است. دانستن این که داده‌های struct به همان ترتیبی که اعلان می‌شوند، طرح‌بندی خواهند شد موجب می‌شود که اشاره‌گرها و عملگرهای حسابی اشاره‌گرها برای رسیدن به این وضعیت مطلوب باشند.

دسترسی به متغیرهای stcrut محدود شده است. با این وجود، حافظه خودش مخفی یا protected نیست. اشاره‌گرها نیز به موقعیتی از حافظه اشاره می‌کنند و امکان خواندن و تغییر داده‌های ذخیره شده در آن آدرس را دارند. اگر یک اشاره‌گر به همان آدرسی انتساب یافته باشد که struct داده‌هایش را ذخیره می‌کند، داده‌های آن را می‌تواند بخواند. با استفاده از همان تعاریف برای structure یعنی همان فایل c. و همان فایل h. و نسخه تغییر یافته زیر از فایل main.c می‌توان این محدودیت دسترسی را به وسیله اشاره‌گرها دور زد.

#include "private_var.h"
#include <stdio.h>

int main()
{
    struct Contact * Tony;
    Tony = create_contact();

    int * mobile_number_is_here = (int *)Tony;
    printf("Mobile number: %d\n", *mobile_number_is_here);

    int * home_number_is_here = mobile_number_is_here + 1;
    *home_number_is_here = 1;
    printf("Modified home number: %d\n", *home_number_is_here);

    delete_contact( Tony );
    return 0;
}

تابع خصوصی

تابع‌ها به صورت پیش‌فرض extern هستند و از طریق یک واحد ترجمه قابل مشاهده می‌شوند. به بیان دیگر اگر فایل‌ها با همدیگر به صورت یک فایل object منفرد کامپایل شوند، هر فایلی می‌تواند هر تابع تعریف شده از فایل دیگر را اجرا کند. static ساختن یک تابع باعث محدودیت مشاهده آن برای فایلی که در آن تعریف شده می‌شود. از این روند مرحله برای تضمین خصوصی ماندن تابع ضروری است:

  • تابع باید یا در فایل منبع و یا در فایل هدر متناظر خود static باشد.
  • تعریف تابع باید در فایل منبع جداگانه‌ای باشد.

در فایل private_funct.c زیر یک تابع static به نام ()print_numbers تعریف شده‌ است. دقت کنید که تابع ()delete_contact یک فراخوانی موفق به ()print_numbers دارد زیرا در همان فایل قرار دارد.

#include "private_funct.h"
#include <stdio.h>
#include <stdlib.h>

struct Contact
{
    int mobile_number;
    int home_number;
};


struct Contact * create_contact()
{
    struct Contact * some_contact;
    some_contact = malloc(sizeof(struct Contact));
    some_contact->mobile_number = 12345678;
    some_contact->home_number = 87654321;
    return( some_contact );
}

static void print_numbers( struct Contact * some_contact )
{
    printf("Mobile number: %d, ", some_contact->mobile_number);
    printf("home number = %d\n", some_contact->home_number);
}

void delete_contact( struct Contact * some_contact )
{
    print_numbers(some_contact);
    free(some_contact);
}

در هدر متناظر، ()print_numbers به صوت یک تابع static اعلان شده است.

#ifndef PRIVATE_FUNCT_H
#define PRIVATE_FUNCT_H

struct Contact;

struct Contact * create_contact();
static void print_numbers( struct Contact * some_contact );
void delete_contact( struct Contact * my_points );

#endif /* PRIVATE_FUNCT_H */

در نهایت main.c به صورت موفقیت‌آمیزی ()print_numbers را به طور غیر مستقیم از طریق ()delete_contact فراخوانی می‌کند زیر هر دو تابع در سند یکسانی تعریف شده‌اند. با این وجود تلاش برای فراخوانی ()print_numbers به صورت مستقیم از main ناموفق خواهد بود.

#include "private_funct.h"

int main()
{
    struct Contact * Tony;
    Tony = create_contact();
    // print_numbers( Tony );
    // will cause compile time error
    delete_contact( Tony );
    return 0;
}

دسترسی به تابع‌های خصوصی

این امکان وجود دارد که تابع static به نام ()print_numbers را از main با استفاده از goto فراخوانی کنیم یا اشاره‌گر تابع را به main ارسال نماییم. هر دو گزینه نیازمند تغییر دادن فایل منبع private_funct.c یا خود تابع است. ضمناً چنین روش‌هایی در واقع حذف کپسوله‌سازی هستند و نه دور زدن آن.

نتیجه‌گیری

کپسوله‌سازی محدود به زبان‌های شیءگرا نیست. زبان‌های شیءگرای مدرن از کپسوله‌سازی به روشی راحت و طبیعی بهره می‌گیرند. روش‌های مختلفی برای دور زدن کپسوله‌سازی وجود دارد و اجتناب از رویه‌های نامناسب و عدم بهره‌گیری از آن‌ها به نگهداری صحیح کدهای C و ++C کمک می‌کند.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«میثم لطفی» در رشته‌های ریاضیات کاربردی و مهندسی کامپیوتر به تحصیل پرداخته و شیفته فناوری است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار با مجله فرادرس همکاری دارد.