تغییر رفتار کلاس در زمان اجرا با استفاده از الگوی آذین گر در ++C

wrapper classدر برنامه نویسی شی گراء یکی از چالش های اساسی ، تغییر رفتار برخی از کلاسها با گسترش نرم افزار در آینده است. بنابراین توسعه دهندگان همیشه به دنبال روش هایی برای رفع این چالش در نرم افزار می باشند که البته با کمترین تغییرات در سورس کلاسها همراه باشد، چرا که بدین ترتیب نیازی به تست های رگرسیون زیادی نخواهند داشت و با درصد بالایی میتوان پایداری نرم افزار را تضمین کرد. اکنون با این مقدمه کوتاه یک سناریو ساده اما کاربردی را در جهت رفع این چالش مطرح میکنم، تصور کنید شما نرم افزاری دارید که در یکی از بخشها از کلاسهایی بدین شکل با استفاده از وراثت استفاده کرده اید...class IbaseInterface { public: virtual void func()=0; void invoke(); virtual ~IbaseInterface(){} }; class Cfunctor : public IbaseInterface { public: Cfunctor(){} Cfunctor(int i) : m_i(i){} Cfunctor(const Cfunctor& lhs); Cfunctor& operator=(Cfunctor& lhs); ~Cfunctor(){} virtual void func() /*override*/ { std::cout << &quotfunc invoke from class Cfunctor and i is :&quot << m_i << '\n'; } protected: int m_i; }; void IbaseInterface::invoke(){ func(); };بنابراین می توانید از کلاس Cfunctor به راحتی به شکل زیر استفاده کنید...void* p_mem = std::malloc(sizeof(Cfunctor)+sizeof(CfunctorNew)); IbaseInterface* myBase = new(p_mem) Cfunctor; myBase->invoke();همانطور که مشاهده میکنید، می توانید با استفاده از یک اشاره گر کلاس پایه به کلاس های فرزند هم دسترسی داشته باشید، و به راحتی متد کلاس پایه invoke را فراخوانی کنید و متد فوق هم متد کلاس فرزند را فراخوانی خواهد کرد.بدین ترتیب شما از کلاس Cfunctor یک شی جدید ایجاد میکنید و در متدها و شی های کلاس های مختلفی در سطح پروژه استفاده خواهید کرد،ولی قطعا با پیشرفت نرم افزار و رو به جلو بودن تکنیک های نرم افزاری لازم هست که در آینده عملکرد جدیدی را به کلاس Cfunctor اضافه کنید منتهی به علت های مختلفی به راحتی امکان ویرایش وبه روز رسانی کلاس فوق برای شما میسر نیست.به همین علت می توانید با استفاده از یک الگوی طراحی مناسب، مانند کلاس wrapper استفاده نمایید و کلاسی به شکل زیر ایجاد نمایید و عملکردهای جدید را به این کلاس اضافه کنید...class CfunctorNew : protected Cfunctor { public: CfunctorNew(){} CfunctorNew(int i) : Cfunctor(i){} CfunctorNew(const CfunctorNew& lhs); Cfunctor& operator=(Cfunctor& lhs); ~CfunctorNew(){} void func() /*override*/ { Cfunctor::func(); std::cout << &quotfunc invoke from class derived2&quot << '\n'; } }; بدین ترتیب شما تمامی عملکردها کلاس قدیمی Cfunctor را در کلاس جدید override کردید و همچنین امکانات جدیدی هم به کلاس CfunctorNew هم افزوده اید. ولی یک مشکل وجود دارد، شما به راحتی نمی توانید تمام متدهای سرویس گیرنده کلاس را تغییر دهید که از این به بعد از کلاس جدید استفاده نمایند.به این علت که باید در مکان های زیادی از سورس نرم افزار تغییر ایجاد کنید که این هم یعنی مجبور هستید تمام تست های رگرسیون برای بخش های تغییر کرده را مجددا انجام دهید، بنابراین این روش که تمام سرویس گیرندهای کلاس قدیمی را به کلاس جدید تغییر دهید کار بسیار دشوار در خیلی از مواقع خطرناک پر ریسک خواهد بود.اکنون برای رفع این چالش می توانید از تکنیک Sub Object در زبان سی پلاس پلاس استفاده نمایید، وبا کمترین هزینه همه سرویس دهندها را بدون اینکه تغییری دهید مجبور کنید از کلاس پوشاننده جدید استفاده نمایند بدون اینکه سایر بخشها متوجه این تغییر شده باشند و به راحتی میتوان پایداری سیستم را تضمین کرد.پس به روش زیر عمل میکنیم....متد invoke که در اینترفیس پایه پیاده سازی شده است همانطور که در بالا تعریف شده است، را به این ترتیب تغییر خواهیم داد.void IbaseInterface::invoke(){ new(this) CfunctorNew(20); this->func(); };همانطور که مشاهده میکنید بدون اینکه در تعریف آبجکت ها از کلاس Cfunctor تغییری دهیم در سرویس گیرنده ها و همچنین با کمترین تغییر در ساختار کلاس های پایه و کلاس های فرزند، فقط با اضافه کردن یک خط دستور کلاس پایه را در زمان چندریختی زمان اجرا مجبور کردیم که اشاره گر implicit this کلاس پایه را به کلاس CfunctorNew تغییر دهد. و هیچ بخشی از نرم افزار هم متوجه این تغییر نخواهند شد.امکان فوق العاده ای که در زبانهای شی گرا سطح بالا وجود ندارد، به همین علت توسعه دهنده مجبور هست که کدهای بیشتری را تغییر دهد و به نرم افزار اصلی اضافه نماید.هدف از مطرح کردن این سناریو ، این هست که به علت مشخص نبود فیچر های نرم افزار در آینده توسعه دهندگان همیشه باید روش هایی را استفاده کنند که ضمن حفظ backward compatibility بتوانند با کمترین هزینه کدهای جدید را جایگزین کدهای قدیمی کنند تا بتوانند فیچرهای جدید را به نرم افزار اضافه نمایند مخصوصا اگر این نرم افزارها در حوزه های امنیتی و حیاتی مشغول فعالیت باشند که این حساسیت بسیار بیشتر هم می باشد.امیدوارم موفق باشید و تشکر از مطالعه این نوشته.