معماری MVP و MVC در اندروید - قسمت دوم

دوشنبه ۲۴ اردیبهشت ۹۷ توسط فاطمه بهاروند

MVP and MVC

این دومین مقاله از مجموعه مقالاتی است که الگوهای معماری (MVC(Model View Controller و (MVP(Model View Presenter را در زمینه ی توسعه ی اندروید مورد بررسی قرار می دهد.

MVC یا MVP:

در قسمت اول این مقالات ما به این نتیجه رسیدیم که الگوی معماری MVP به دلیل برقراری ارتباط قوی بین Activity و Fragment(که presenterهای MVP هستند) و بخش های مختلف چارچوب اندروید برای توسعه ی اندروید مناسب تر است.

با این حال از آنجایی که اصطلاحات MVC و MVP مبهم هستند، بسیاری از برنامه نویسان تمایلی ندارند که بین این دو الگوی معماری تمایزی قائل شوند. من با کسانی که تفاوت بین این دو الگو را رد می کنند موافقم، چرا که کار خود را بدون هیچ توضیح اضافی نظیر "در برنامه ی من از MVP استفاده شده است" یا با گفتاری کاملا مشابه مانند "در برنامه ی من از MVC استفاده شده است" به پایان خواهند برد.

جملات بالا به این معنا هستند که شما می توانید از MVC یا MVP به عنوان نام "نماینده" هر دو الگو استفاده نمایید. ما از کدام یک باید استفاده کنیم؟ من فکر می کنم "Controller" توصیفی تر از "Presenter" است. علاوه بر این خارج از جامعه ی اندروید MVC بیشتر از MVP مورد استفاده قرار می گیرد. بنابراین از حالا شروع می کنیم و الگوی MVC را برای پیاده سازی انتخاب می کنیم.

همانطور که پیش از این گفتم پیچیدگی خاصی در ارتباط با پیاده سازی Model در اندروید وجود ندارد. شما می توانید ContentProvider خود را بنویسید یا اگر برای نیاز شما خیلی پیچیده است می توانید یک Model مستقل برای برنامه ی خود ایجاد کنید(با استفاده از الگوی طراحی Singleton که در آن برخی رابط های ModelMvc اجرا شده اند). از آنجایی که هیچ مورد خاصی در Modelهای اندروید یافت نمی شود، من تصمیم گرفتم در این برنامه ی آموزشی از ContentProvider خارجی موجود به عنوان Model استفاده کنم تا بتوانم تمرکز خود را بر روی بخش های پیچیده ی MVC در اندروید بگذارم. اگر شما به یک آموزش خوب در مورد ContentProvider نیاز دارید، ولفرام ریتمیر(Wolfram Rittmeyer) یک مجموعه مقاله ی عالی در این باره نوشته است.

شما ممکن است متعجب شوید که کلاس Android View در viewهای MVC هیچ کاری برای انجام دادن ندارد، بنابراین بیایید با اصطلاحات یکپارچه و یکنواخت شروع کنیم.

تفاوت بین View در MVC و Android:

در حال حاضر چارچوب اندروید شامل یک Component به نام View است که کلاس پایه برای تمام بلاک های معماری UI محسوب می شود. در حالی که بین Android View و MVC view شباهت هایی وجود دارد اما آن ها یکسان نیستند. رابطه ی بین این دو می تواند اینگونه توصیف شود:

  • به طور کلی Android View همان MVC view نیست.
  • برای هر MVC view باید یک Android View وجود داشته باشد تا بخش "قابل مشاهده برای کاربر" در MVC view به نمایش گذاشته شود.("root" در Android View از سلسه مراتب UI است).
  • MVC view می تواند شامل MVC viewهای تو در تو باشد.

در حال حاضر این موضوع می تواند کمی گیج کننده باشد اما مثال هایی فراهم شده است که مفهوم عبارات بالا را برای شما روشن خواهد کرد.

از آنجایی که ما در معنی "View" با ابهام مواجه شدم، من باید صریحا به یک نوع "View" اشاره کنم خواه "Android View" باشد یا "MVC view". به یاد داشته باشید که زمانی که می گویم Android View منظور من کلاس View و تمام زیر کلاس های آن (مانند widgets, layouts,...) است.

پیاده سازی MVC view:

خوب ما MVC view را چگونه باید تعریف کنیم؟ در این برنامه آموزشی MVC view هر کلاسی که رابط MVC view را اجرا کند در بر میگیرد(کد جاوا بی نیاز از توصیف است):

/**
 * MVC view interface.
 * MVC view is a "dumb" component used for presenting information to the user.<br>
 * Please note that MVC view is not the same as Android View - MVC view will usually wrap one or
 * more Android View's while adding logic for communication with MVC Controller.
 */
public interface ViewMvc {

    /**
     * Get the root Android View which is used internally by this MVC View for presenting data
     * to the user.<br>
     * The returned Android View might be used by an MVC Controller in order to query or alter the
     * properties of either the root Android View itself, or any of its child Android View's.
     * @return root Android View of this MVC View
     */
    public View getRootView();

    /**
     * This method aggregates all the information about the state of this MVC View into Bundle
     * object. The keys in the returned Bundle must be provided as public constants inside the
     * interfaces (or implementations if no interface defined) of concrete MVC views.<br>
     * The main use case for this method is exporting the state of editable Android Views underlying
     * the MVC view. This information can be used by MVC controller for e.g. processing user's
     * input or saving view's state during lifecycle events.
     * @return Bundle containing the state of this MVC View, or null if the view has no state
     */
    public Bundle getViewState();

}

توجه داشته باشید که این رابط بسیار معمولی است و اهمیت چندانی ندارد، بنابراین هر کلاس موجود را می تواند به راحتی گسترش داده یا جمع کرد تا بتوانند به عنوان MVC view استفاده شوند. از سوی دیگر این رابط هیچ ابزاری برای تمایز بین viewهای مختلف MVC ندارد، برای ساخت هر MVC view نیاز داریم تا آن را به عنوان زیر مجموعه ی رابط ViewMVC تعریف کنیم، Controllerهای MVC نیز به این زیر رابط ها وابسته هستند. این وابستگی نمونه های MVC را نقض نمی کند، Controllerها نیاز دارند تا به Viewهای خاصی از MVC وابسته باشند. هرچند این وابستگی باید تنها به وابستگی رابط کاربری ختم شود.

بیایید نگاه دقیق تری به رابط کاربری یکی از viewهای MVC در برنامه ی آموزشی SmsDetailsViewMvc بیندازیم:

/**
 * This interface corresponds to "details" screen of the app, where details of a single SMS
 * message should be displayed
 */
public interface SmsDetailsViewMvc extends ViewMvc {

    interface ShowDetailsViewMvcListener {
        /**
         * This callback will be invoked when "mark as read" button is being clicked
         */
        void onMarkAsReadClick();
    }

    /**
     * Hide "mark as read" button
     */
    void markAsReadNotSupported();

    /**
     * Show details of a particular SMS message
     * @param smsMessage a message to show
     */
    void bindSmsMessage(SmsMessage smsMessage);


    /**
     * Set a listener that will be notified by this MVC view
     * @param listener listener that should be notified; null to clear
     */
    void setListener(ShowDetailsViewMvcListener listener);

}

لطفا توجه داشته باشید که تنها با نگاه کردن به این رابط کاربری ما خیلی سریع می توانیم دریابیم که MVC view چه کاری انجام می دهد. این قدرت یک انتزاع خوب است که جزئیات پیاده سازی را پنهان نموده و اجازه می دهد جدای از چرایی انجام کار، چگونگی آن را نیز درک کنیم.

انتزاع بالا برای نگهداری دراز مدت بسیار مهم است. در مورد آزمایش A/B فکر کنید. با این انتزاع همه ی آنچه که شما بدان نیاز دارید در چندین پیاده سازی از این رابط کاربری مهیا شده است. تغییرات UI را مدنظر قرار دهید. با این انتزاع این تغییرات تا زمانی که رابط پایدار بماند در انزوا می مانند. در مورد تغییرات قوانین کاری بیاندیشید با این انتزاع می توانید با خیال راحت تمام منطق UI را نادیده بگیرید و تنها در مورد رابط اطلاعات کسب کنید.

من حتی می گویم این انتزاع مهمترین ویژگی MVC است.

در حقیقت کلاس عملیاتی که رابط SmsDetailsViewMvc را اجرا می کند SmsDetailsViewMvcImpl است.

/**
 * An implementation of {@link SmsDetailsViewMvc} interface
 */
public class SmsDetailsViewMvcImpl implements SmsDetailsViewMvc {

    private View mRootView;
    private ShowDetailsViewMvcListener mListener;
    private boolean mMarkAsReadSupported = true;

    private TextView mTxtAddress;
    private TextView mTxtDate;
    private TextView mTxtBody;
    private Button mBtnMarkAsRead;

    public SmsDetailsViewMvcImpl(LayoutInflater inflater, ViewGroup container) {
        mRootView = inflater.inflate(R.layout.mvc_view_sms_details, container, false);

        initialize();

        mBtnMarkAsRead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mListener != null) {
                    mListener.onMarkAsReadClick();
                }
            }
        });
    }

    private void initialize() {
        mTxtAddress = (TextView) mRootView.findViewById(R.id.txt_sms_address);
        mTxtDate = (TextView) mRootView.findViewById(R.id.txt_sms_date);
        mTxtBody = (TextView) mRootView.findViewById(R.id.txt_sms_body);
        mBtnMarkAsRead = (Button) mRootView.findViewById(R.id.btn_mark_as_read);
    }

    @Override
    public void markAsReadNotSupported() {
        mMarkAsReadSupported = false;
    }

    @Override
    public void bindSmsMessage(SmsMessage smsMessage) {
        mTxtAddress.setText(smsMessage.getAddress());
        mTxtDate.setText(smsMessage.getDate());
        mTxtBody.setText(smsMessage.getBody());

        mRootView.setBackgroundColor(smsMessage.isUnread() ?
                mRootView.getResources().getColor(android.R.color.holo_green_light) :
                mRootView.getResources().getColor(android.R.color.white));

        mBtnMarkAsRead.setVisibility(smsMessage.isUnread() && mMarkAsReadSupported ?
                View.VISIBLE : View.GONE);

    }

    @Override
    public void setListener(ShowDetailsViewMvcListener listener) {
        mListener = listener;
    }

    @Override
    public View getRootView() {
        return mRootView;
    }

    @Override
    public Bundle getViewState() {
        return null;
    }

}

همانطور که می بینید MVC view بسیار ساده است که این امر همیشه یک مزیت است، بیایید ببینیم در اینجا چه خبر است.

  • Constructor: مهمترین جنبه ی ساخت viewهای MVC ارزیابی اولیه root" view" در اندروید است. معمولا این کار را با inflate نمودن آن از فایل لایه ی XML انجام می دهیم. هنگامی که Root Android View مقدار دهی اولیه می شود، شما با مقدار دهی به سایر Viewهای اندروید موجود در سلسله مراتب inflate ادامه خواهید داد. در این مورد به سادگی مرجع های سلسله مراتبی Viewهای اندروید که از آن استفاده خواهیم کرد را به دست آورده و یک click listener با یکی از آن ها ثبت کرده ایم.
  • ()getViewState: این MVC view ورودی"stateful" کاربران را ذخیره نمی کند(رخدادهای دکمه "stateful" نیستند)، بنابراین هیچ وضعیتی وجود ندارد که Controllerهای MVC به آن علاقمند باشند. در چنین مواردی متد ()getViewState باید مقدار null را برگرداند.
  • (bindSmsMessage(SmsMessage: این MVC view داده را به کاربر ارائه می دهد. این وظیفه Controllerها است که داده ها را واکشی کرده و برای bind، MVC view می نماید.( MVC view از وجود model خبر ندارد.)
  • ()setListener: یک listener را با این MVC view اعمال می کنیم تا از اعمال کاربر مطلع شود.

لطفا توجه داشته باشید که اجرای MVC view به Activity و Fragment بستگی ندارد، آن ها می توانند درون هر کدام از آن ها به کار گرفته شده یا حتی در viewهای MVC مورد استفاده قرار گیرند. ایده ی استفاده از عناصر UI به سطح بعدی محول شده و متعاقبا تغییرات UI بیش از پیش بهینه شده و کمتر مستعد خطا می باشند. از این رو در گام بعدی طراحان UI/UX از شما می خواهند ظاهر برنامه را تغییر دهید، تنها کاری که نیاز است انجام دهید اجرای متفاوت MVC viewهای شما می باشد.

اما مزیت اصلی این رویکرد توسط آن دسته توسعه دهندگانی که کد خود را به صورت یکسان آزمایش می کنند احساس می شود.(من حتما مطالب زیادی را در این مورد خواهم نوشت)، شما اکنون می توانید به سادگی UI برنامه ی خود را دور بزنید.

در اصطلاح "dirtiness metrics"(معیارهای آلودگی) برای منطق UI بیان شده است که Activityها در اندروید عناصر UI محسوب نمی شوند. پیاده سازی بالا از MVC view دارای نمره ی صفر از "dirtiness points"(نقاط ضعف) است چرا که هیچ وابستگی مستقیم یا پیوسته ای در Activity و Fragment وجود ندارد.

نتیجه گیری:

در این مقاله ما یک اجرا از MVC view را ملاحظه کردیم. یک UI Controller مستقل که مسئول ارائه ی خروجی برنامه به کاربر و دریافت ورودی از کاربر در قالب یک شی عمومی که رابط listener را اجرا می کند است. این شی می تواند یک MVC Controller یا یک MVC view دیگر باشد. viewهای MVC به Activity یا Fragment وابسته نیستند، بنابراین شما می توانید به سادگی بدون هیچگونه تاثیری از جانب منطق کاری دوباره از آن ها استفاده نموده، به روز رسانی کنید یا کاملا آن ها را جایگزین نمایید.

در مقاله ی بعدی(مقاله ی آخر) پیاده سازی MVC Controller در اندروید را مورد بررسی قرار خواهیم داد.


کلیدواژه: اندروید Android MVP MVC معماری MVP در اندروید معماری MVC در اندروید

منابع: www.techyourchance.com

ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.