כשהתחלתי את דרכי כמפתחת אנדרואיד, המילים "ארכיטקטורת יישומים ניידים" גרמו לי לתמיהה עמוקה, גוגל ומאמרים על Habré הובילו אותי לדיכאון גדול עוד יותר - אני מסתכל על הספר ולא רואה כלום. אני חושב שאם אתם קוראים את המאמר הזה, כבר למדתם את התמונה הזו יותר מפעם אחת וניסיתם להבין מה קורה: הבעיה של הבנת הגישה האדריכלית בפיתוח מובייל נעוצה לדעתי במופשטות של הארכיטקטורה עצמה. לכל מפתח יש חזון משלו כיצד ליישם נכון דפוס זה או אחר. דוגמאות פחות או יותר הגונות ליישום MVP נמצאו במגזר דובר האנגלית של האינטרנט, וזה לא מפתיע. בואו נסתכל בקצרה מה זה מה ונעבור לדוגמא. מודל - רמת נתונים. אני לא אוהב להשתמש במונח "לוגיקה עסקית", אז ביישומים שלי אני קורא לזה Repository והוא מתקשר עם מסד הנתונים והרשת. תצוגה - רמת תצוגה. זה יהיה Activity , Fragment או Custom View אם אתה לא אוהב לרקוד עם טמבורין וליצור אינטראקציה עם מחזור החיים. הרשו לי להזכיר לכם שבתחילה כל אפליקציות האנדרואיד כפופות למבנה ה- MVC , כאשר הבקר הוא פעילות או קטע . מגיש הוא שכבה בין View למודל. View משדר את האירועים המתרחשים לו, המציג מעבד אותם, במידת הצורך, ניגש למודל ומחזיר נתונים ל-View לצורך עיבוד. ביחס לאנדרואיד ודוגמה ספציפית, אדגיש את החלק החשוב – חוזה. זהו הממשק שמתאר את כל האינטראקציות בין הרכיבים לעיל. לסיכום החלק התיאורטי:
- הצג יודע על מגיש;
- המגיש יודע על View ו-Model (Repository);
- דגם בפני עצמו;
- החוזה קובע את האינטראקציות ביניהם.
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 דופק על המציג ואומר: "הלחצן נלחץ."
- המגיש פונה למאגר: "הורד לי את השטויות האלה."
- המאגר טוען ומעביר את ה"חומר" למציג.
- המגיש פונה לתצוגה: "הנה הנתונים בשבילך, צייר אותם"
GO TO FULL VERSION