JavaRush /בלוג Java /Random-HE /MVP באנדרואיד לקטנטנים

MVP באנדרואיד לקטנטנים

פורסם בקבוצה
כשהתחלתי את דרכי כמפתחת אנדרואיד, המילים "ארכיטקטורת יישומים ניידים" גרמו לי לתמיהה עמוקה, גוגל ומאמרים על Habré הובילו אותי לדיכאון גדול עוד יותר - אני מסתכל על הספר ולא רואה כלום. אני חושב שאם אתם קוראים את המאמר הזה, כבר למדתם את התמונה הזו יותר מפעם אחת וניסיתם להבין מה קורה: MVP באנדרואיד לקטנטנים - 1הבעיה של הבנת הגישה האדריכלית בפיתוח מובייל נעוצה לדעתי במופשטות של הארכיטקטורה עצמה. לכל מפתח יש חזון משלו כיצד ליישם נכון דפוס זה או אחר. דוגמאות פחות או יותר הגונות ליישום MVP נמצאו במגזר דובר האנגלית של האינטרנט, וזה לא מפתיע. בואו נסתכל בקצרה מה זה מה ונעבור לדוגמא. מודל - רמת נתונים. אני לא אוהב להשתמש במונח "לוגיקה עסקית", אז ביישומים שלי אני קורא לזה Repository והוא מתקשר עם מסד הנתונים והרשת. תצוגה - רמת תצוגה. זה יהיה Activity , Fragment או Custom View אם אתה לא אוהב לרקוד עם טמבורין וליצור אינטראקציה עם מחזור החיים. הרשו לי להזכיר לכם שבתחילה כל אפליקציות האנדרואיד כפופות למבנה ה- MVC , כאשר הבקר הוא פעילות או קטע . מגיש הוא שכבה בין View למודל. View משדר את האירועים המתרחשים לו, המציג מעבד אותם, במידת הצורך, ניגש למודל ומחזיר נתונים ל-View לצורך עיבוד. ביחס לאנדרואיד ודוגמה ספציפית, אדגיש את החלק החשוב – חוזה. זהו הממשק שמתאר את כל האינטראקציות בין הרכיבים לעיל. לסיכום החלק התיאורטי:
  • הצג יודע על מגיש;
  • המגיש יודע על View ו-Model (Repository);
  • דגם בפני עצמו;
  • החוזה קובע את האינטראקציות ביניהם.
למעשה, הדוגמה עצמה, למען פשטות הניסוי, על ידי לחיצה על כפתור, נטען שורה ממסד הנתונים ונציג אותה ב- TextView . לדוגמה, המאגר מכיל רשימה של המסעדות הטובות ביותר בעיר. נתחיל עם החוזה: בואו ניצור ממשק MainContract:
public interface MainContract {
    interface View {
        void showText();
    }

    interface Presenter {
        void onButtonWasClicked();
        void onDestroy();
    }

    interface Repository {
        String loadMessage();
    }
}
לעת עתה, אנו פשוט מדגישים את 3 המרכיבים של האפליקציה העתידית שלנו ומה הם יעשו. בהמשך נתאר את המאגר:
public class MainRepository implements MainContract.Repository {

    private static final String TAG = "MainRepository";
    @Override
    public String loadMessage() {
        Log.d(TAG, "loadMessage()");
        /** Здесь обращаемся к БД or сети.
         * Я специально ничего не пишу, чтобы не загромождать пример
         * DBHelper'ами и прочими не относяшимеся к теме an objectми.
         * Поэтому я буду возвращать строку Сосисочная =)
         */
        return "Сосисочная у Лёхи»;
    }
}
הכל ברור איתו, רק טעינה ופריקה של נתונים. הבא הוא מגיש:
public class MainPresenter implements MainContract.Presenter {
    private static final String TAG = "MainPresenter";

    //Компоненты MVP applications
    private MainContract.View mView;
    private MainContract.Repository mRepository;

    //Сообщение
    private String message;


    //Обрати внимание на аргументы конструктора - мы передаем экземпляр View, а Repository просто создаём конструктором.
    public MainPresenter(MainContract.View mView) {
        this.mView = mView;
        this.mRepository = new MainRepository();
        Log.d(TAG, "Constructor");
    }

    //View сообщает, что кнопка была нажата
    @Override
    public void onButtonWasClicked() {
        message = mRepository.loadMessage();
        mView.showText(message);
        Log.d(TAG, "onButtonWasClicked()");
    }

    @Override
    public void onDestroy() {
        /**
         * Если бы мы работали например с RxJava, в этом классе стоило бы отписываться от подписок
         * Кроме того, при работе с другими методами асинхронного андроида,здесь мы боремся с утечкой контекста
         */

        Log.d(TAG, "onDestroy()");
    }
}
אתה זוכר שכתבתי על ריקוד עם טמבורין ועל מחזור החיים? המגיש חי כל עוד ה-View שלו, בעת פיתוח תרחישי משתמש מורכבים, אני ממליץ לך לשכפל את כל ה-View callbacks במציג ולהתקשר אליהם ברגעים המתאימים, תוך שכפול מחזור החיים של הפעילות/שבר, כדי להבין בזמן מה צריך להיעשות עם הנתונים התלויים כרגע ב"שכבת הביניים". ולבסוף, צפה:
public class MainActivity extends AppCompatActivity implements MainContract.View {

    private static final String TAG = "MainActivity";

    private MainContract.Presenter mPresenter;

    private Button mButton;

    private TextView myTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Создаём Presenter и в аргументе передаём ему this - эта Activity расширяет интерфейс MainContract.View
        mPresenter = new MainPresenter(this);

        myTv = (TextView) findViewById(R.id.text_view);
        mButton = (Button) findViewById(R.id.button);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.onButtonWasClicked();
            }
        });
        Log.d(TAG, "onCreate()");
    }

    @Override
    public void showText(String message) {
        myTv.setText(message);
        Log.d(TAG, "showMessage()");
    }

    //Вызываем у Presenter метод onDestroy, чтобы избежать утечек контекста и прочих неприятностей.
    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresenter.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
}
מה קורה?
  • Activity, הידועה גם בשם View, onCreate()יוצרת מופע Presenter בשיטה ומעבירה את עצמה לבנאי שלה.
  • כאשר מגיש נוצר, הוא מקבל בפירוש View ויוצר מופע Repository (אגב, ניתן להפוך אותו לסינגלטון)
  • כשלוחצים על כפתור, ה-View דופק על המציג ואומר: "הלחצן נלחץ."
  • המגיש פונה למאגר: "הורד לי את השטויות האלה."
  • המאגר טוען ומעביר את ה"חומר" למציג.
  • המגיש פונה לתצוגה: "הנה הנתונים בשבילך, צייר אותם"
זהו, חבר'ה. נ.ב. חשוב להגדיר בבירור תחומי אחריות בין רכיבים. לדוגמה, באחד מפרויקטי ההדרכה שלי, בלחיצה על כפתור, היה צורך לשנות נתונים במסד הנתונים. המודל תואר על ידי מחלקה של POJO, העברתי מידע על מיקום התצוגה, האחראית על מידע על האובייקט על המסך, Presenter חיפשה את האובייקט הזה ברשימה ושלחה אותו לכתיבה ל-Repository. הכל נראה הגיוני? אבל המנטור שלי ציין את הדברים הבאים: המאגר צריך לעשות רק כתיבה וקריאה, הוא לא צריך לשלוף את המידע הדרוש מה-POJO ולהחליט מה הוא צריך לכתוב. על המגיש לתת לו רק את המידע שיש להקליט ותו לא. אין מסגרת קפדנית ליישום ארכיטקטורה: ניסוי, נסה, חפש מה נוח לך באופן אישי. ואל תשכחו להראות לחברים הבכירים שלכם בבדיקת קוד =) הדוגמה זמינה ב-GitHub: https://github.com/admitrevskiy/MVP_Example
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION