Android tərtibatçısı kimi səyahətimə başlayanda “Mobil Tətbiq Arxitekturası” sözləri məni dərin çaşqınlığa sürüklədi, Google və Habré haqqında məqalələr məni daha da depressiyaya saldı – kitaba baxıram və heç nə görmürəm. Düşünürəm ki, əgər siz bu məqaləni oxuyursunuzsa, siz artıq bu şəkli bir dəfədən çox öyrənmisiniz və nə baş verdiyini anlamağa çalışmısınız: Kiçiklər üçün Android-də MVP - 1Mobil inkişafda memarlıq yanaşmasını anlamaq problemi, mənim fikrimcə, memarlığın özünün mücərrədliyindədir. Hər bir tərtibatçının bu və ya digər nümunəni necə düzgün həyata keçirəcəyinə dair öz baxışı var. MVP tətbiqinin az-çox layiqli nümunələri İnternetin ingilisdilli sektorunda tapıldı, bu təəccüblü deyil. Nəyin nə olduğuna qısaca nəzər salaq və bir nümunəyə keçək. Model - məlumat səviyyəsi. Mən “biznes məntiqi” terminindən istifadə etməyi sevmirəm, ona görə də tətbiqlərimdə onu Repozitoriya adlandırıram və o, verilənlər bazası və şəbəkə ilə əlaqə saxlayır. Baxış — ekran səviyyəsi. Əgər qavalla rəqs etməyi və həyat dövrü ilə əlaqə saxlamağı sevmirsinizsə, bu Fəaliyyət , Fraqment və ya Xüsusi Görünüş olacaq . Nəzərinizə çatdırım ki, əvvəlcə bütün Android proqramları MVC strukturuna tabedir , burada Nəzarətçi Fəaliyyət ya Fraqmentdir . Təqdimatçı Görünüş və Model arasında bir təbəqədir. View, baş verən hadisələri ötürür, aparıcı onları emal edir, lazım gələrsə, Modelə daxil olur və məlumatları göstərmək üçün Görünüşə qaytarır. Android və xüsusi bir nümunə ilə əlaqədar olaraq, vacib hissəni - Müqaviləni vurğulayacağam. Bu, yuxarıda göstərilən komponentlər arasındakı bütün qarşılıqlı əlaqəni təsvir edən interfeysdir. Nəzəri hissəni ümumiləşdirmək üçün:
  • View Aparıcı haqqında bilir;
  • Təqdimatçı Görünüş və Model (Repository) haqqında bilir;
  • Özü ilə model;
  • Müqavilə onların arasındakı qarşılıqlı əlaqəni tənzimləyir.
Əslində, nümunənin özü, eksperimentin sadəliyi üçün bir düyməni basmaqla verilənlər bazasından bir sıra yükləyəcəyik və onu TextView-də göstərəcəyik . Məsələn, məlumat bazasında şəhərin ən yaxşı restoranlarının siyahısı var. Müqavilə ilə başlayaq: Bir interfeys yaradaq MainContract:
public interface MainContract {
    interface View {
        void showText();
    }

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

    interface Repository {
        String loadMessage();
    }
}
Hələlik biz sadəcə olaraq gələcək tətbiqimizin 3 komponentini və onların nə edəcəyini vurğulayırıq. Sonra Repozitoriyanı təsvir edəcəyik:
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 "Сосисочная у Лёхи»;
    }
}
Bununla hər şey aydındır, sadəcə məlumatların yüklənməsi və boşaldılması. Növbəti aparıcıdır:
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()");
    }
}
Dəflə rəqs etmək və həyat dövrü haqqında yazdığımı xatırlayırsanmı? Təqdimatçı Görünüşü yaşadığı müddətcə yaşayır, mürəkkəb istifadəçi ssenariləri hazırlayarkən, mən sizə məsləhət görürəm ki, Təqdimatçıda bütün Baxış geri çağırışlarını təkrarlayın və lazımi məqamlarda onlara zəng edin, Fəaliyyət/Fraqmentin həyat dövrünü təkrarlayın, zamanında nə olduğunu başa düşmək üçün. hal-hazırda "interlayerdə" asılı olan məlumatlarla etmək lazımdır. Və nəhayət, Baxın:
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()");
    }
}
Nə baş verir?
  • Görünüş kimi tanınan fəaliyyət onCreate()metodda Presenter instansiyasını yaradır və özünü onun konstruktoruna ötürür.
  • Təqdimatçı yaradıldıqda, o, açıq şəkildə Görünüş qəbul edir və Repozitor nümunəsi yaradır (yeri gəlmişkən, onu Singleton etmək olar)
  • Düymə basılanda Görünüş aparıcını döyür və deyir: “Düymə basıldı”.
  • Təqdimatçı Repozitoriyaya müraciət edir: "Bu axmaqlığı mənim üçün yükləyin."
  • Repozitoriya "əşyaları" yükləyir və Təqdimatçıya çatdırır.
  • Təqdimatçı Baxışa dönür: “Budur məlumat sizin üçündür, onu çəkin”
Budur, uşaqlar. PS Komponentlər arasında məsuliyyətləri aydın şəkildə ayırmaq vacibdir. Məsələn, mənim təlim layihələrimdən birində düyməni basarkən verilənlər bazasındakı məlumatları dəyişmək lazım idi. Model bir POJO sinfi tərəfindən təsvir edilmişdir, mən ekranda obyekt haqqında məlumat üçün cavabdeh olan görünüşün yeri haqqında məlumat verdim, Təqdimatçı siyahıda bu obyekti axtardı və onu Repozitoriyaya yazmaq üçün göndərdi. Hər şey məntiqli görünür? Ancaq mentorum bunları qeyd etdi: Repozitoriya YALNIZ yazmaq və oxumaqla məşğul olmalıdır, lazımi məlumatları POJO-dan çıxarmamalı və nə yazacağına qərar verməməlidir. Təqdimatçı ona yalnız qeyd etmək üçün məlumat verməli və başqa heç nə verməlidir. Memarlığın həyata keçirilməsi üçün ciddi çərçivə yoxdur: təcrübə edin, cəhd edin, şəxsən sizin üçün əlverişli olanı axtarın. Və kodun nəzərdən keçirilməsində böyük yoldaşlarınızı göstərməyi unutmayın =) Nümunə GitHub-da mövcuddur: https://github.com/admitrevskiy/MVP_Example