Skip to content
This repository has been archived by the owner on Oct 30, 2022. It is now read-only.

Comparison of various architectures.

KunMinX edited this page Nov 4, 2018 · 8 revisions

Common structures for projects

The following is a comparison of common MVC, MVP, Clean and AAC architectures.

First, a table shows the redundancy of the architectures:

The requirement is to write three pages, List Fragment, Detail Fragment, Preview Fragment, each with at least three Note services and three User services. Q: how many categories should the above structure be written?

Architecture Related classes Count of Classes
MVC Fragment:3, Controller:3, Model:2 8
MVP Fragment:3, Presenter:3, Model:3, interface Contract:1 10
Clean Fragment:3, ViewModel:3, Usecase:18, Model:3 27
AAC Fragment:3, ViewModel:3, Model:3 9

Defects in MVC architecture

  • View, Control and Model depend on each other, resulting in code coupling.

  • It is difficult to divide the work, it is difficult to assign View, Control and Model to different people.

  • It is difficult to maintain, and there is no middleware interface to buffer, so it is difficult to replace the underlying implementation.

public class NoteListFragment extends BaseFragment {

    ...

    public void refreshList() {
        new Thread(new Runnable() {
            @Override
            public void run() {

                //View relies directly on model. So View must wait for model to write well before it can start.

                mNoteList = mDataManager.getNoteList();
                mHandler.sendMessage(REFRESH_LIST, mNoteList);
            }
        }).start();
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg) {
                case REFRESH_LIST:
                    mAdapter.setList(mNoteList);
                    mAdapter.notifyDataSetChanged();
                    break;
                default:
            }
        }
    };
   
    ...
}

Characteristics and limitations of MVP architecture

  • The MVP architecture is characterized by interface oriented programming. Middleware interfaces are used to connect View, Presenter, Model, and can be replaced seamlessly when there is a new underlying implementation.

  • In addition, MVP's View and Model do not have dependencies, so it can be said to decouple the View and Model code.

public class NoteListContract {

    interface INoteListView {

        void showDialog(String msg);

        void showTip(String tip);

        void refreshList(List<NoteBean> beans);
    }

    interface INoteListPresenter {

        void requestNotes(String type);

        void updateNotes(NoteBean... beans);

        void deleteNotes(NoteBean... beans);
    }

    interface INoteListModel {

        List<NoteBean> getNoteList();

        int updateNote(NoteBean bean);

        int deleteNote(NoteBean bean);
    }
}

But the MVP architecture has its limitations. According to my understanding, the original intention of MVP design is to let the world have no hard to replace View and Model . The assumption behind the original intention is that "the upper logic is stable, but the underlying reaches are frequently replaced." Under the guidance of this assumption, only Presenter has the independent will and decision power to control UI logic and business logic, while View and Model are only external tools.

public class NoteListPresenter implements NoteListContract.INoteListPresenter {

    private NoteListContract.INoteListModel mDataManager;
    private NoteListContract.INoteListView mView;

    @Override
    public void requestNotes(String type) {
        Observable.create(new ObservableOnSubscribe<List<NoteBean>>() {
            @Override
            public void subscribe(ObservableEmitter<List<NoteBean>> e) throws Exception {
                List<NoteBean> noteBeans = mDataManager.getNoteList();
                e.onNext(noteBeans);
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<List<NoteBean>>() {
                    @Override
                    public void accept(List<NoteBean> beans) throws Exception {

                        //Presenter directly interfered with what UI did after getting the data, 
                        //so that there was no logic decoupling.

                        //Normally, decoupling means that the functional boundaries of the presenter
                        //are limited to returning the result data, and the UI processes the UI logic 
                        //according to the response code.

                        mView.refreshList(beans);
                    }
                });
    }

    ...
}

However, such a hypothesis is not practical at most times. Visualization requirements change frequently, and when it comes to visual interaction, there must be modifications to the UI logic, which means that Views and Presenters are involved in each other, increasing the cost of communication between View and Presenter writers.

In the long run, the two are hard to grow. Presenter writers are easily dragged down by various non-native tasks, and View writers don't try to be independent, encapsulating UIs as adaptable components through patterns such as polymorphism, anyway... Presenter comes with all kinds of if else.

Features and defects of Clean architecture

image

To address the ambiguity of Presenter's functional boundaries, in the Clean architecture, the functions of business logic are transferred to the domain layer, managed by Usecase professionally. Presenter is reduced to ViewModel, which acts as a proxy data request and a buffer for linking data callbacks.

The Clean architecture is characterized by one-way dependence and data driven programming. View > ViewModel > Usecase > Model.

The one-way dependence of View on ViewModel is achieved through databinding characteristics. The ViewModel is only responsible for proxy data requests, and when the Usecase processes the business returns the result data, the result data is assigned to the observable data binding data, and the View changes depending on the data.

public class NoteListViewModel {

    private ObservableList<NoteBean> mListObservable = new ObservableArrayList<>();

    private void requestNotes(String type) {
        if (null == mRequestNotesUsecase) {
            mRequestNotesUsecase = new ProveListInitUseCase();
        }

        mUseCaseHandler.execute(mRequestNotesUsecase, new RequestNotesUsecase.RequestValues(type),
                new UseCase.UseCaseCallback<RequestNotesUsecase.ResponseValue>() {
                    @Override
                    public void onSuccess(RequestNotesUsecase.ResponseValue response) {

                        //After the observable data of viewModel changes, 
                        //databinding will automatically update the UI display.

                        mListObservable.clear();
                        mListObservable.addAll(response.getNotes());
                    }

                    @Override
                    public void onError() {

                    }
                });
    }

    ...
}

But the Clean architecture is also flawed. The granularity is too fine. A Usecase can handle only one type of request. The data requested by View contains several types and requires at least several Usecase to be prepared. Usecase is tailored to the current view's data needs, so the reuse rate of Usecase is extremely low, and the project will dramatically increase the class and duplicate the code.

image

public class RequestNotesUseCase extends UseCase<RequestNotesUseCase.RequestValues, RequestNotesUseCase.ResponseValue> {

    private DataManager mDataManager;

    @Override
    protected void executeUseCase(final RequestValues values) {
        List<NoteBean> noteBeans = mDataManager.getNotes();
        ...
        getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans));
    }

    //Each new usecase class needs to be manually configured with a list of request and response parameters.

    public static final class RequestValues implements UseCase.RequestValues {
        private String type;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }
    }

    public static final class ResponseValue implements UseCase.ResponseValue {

        public List<NoteBean> mBeans;

        public ResponseValue(List<NoteBean> beans) {
            mBeans = beans;
        }
    }
}

Characteristics of AAC architecture

AAC is also a data driven programming. Instead of relying on MVVM features, it writes an observer callback directly in the View to receive the resulting data and process UI logic.

public class NoteListFragment extends BaseFragment {

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        viewModel.getNote().observe(this, new Observer<NoteBean>() {
            @Override
            public void onChanged(@Nullable NoteBean bean) {
                //update UI
            }
        });
    }

    ...
}

You can completely understand it as a B / S architecture: sending data requests from the Web front end to the Web back end, the back end responds to the result data to the front end after processing, and the front end processes UI logic according to requirements. That is to say, AAC has completely pressed the business to the Model level.

The origin and characteristics of ViaBus architecture

The owner is currently using the Clean architecture, so I decided to skip AAC and write a "message driven programming" architecture based on an understanding of mobile data interaction.

Because of the request and response from the bus to proxy data, it is named ViaBus.

image

Unlike previous architectures, ViaBus clearly defines what UI is and what is business.

The role of UI is visual interaction. For this purpose, UI's responsibility is to request data and process UI logic . The function of a business is to supply data, so the scope of business responsibility is to receive requests, process data, and return the result data.

The UI doesn't need to know where the data came from or through, it just sends a request to the bus, and if the business registers a "request handler," then someone will do it. Business doesn't need to know what the UI will do when it gets the data. It just sends back the results to the bus. If the UI registers an "observer responder," then someone will take it and act on the response code.

In this way, with the help of static bus, UI and business are completely decoupled, which fundamentally solves the problem of mutual involvement. In addition, unlike the above architecture, each view corresponds to a Presenter or ViewModel. In ViaBus, the UI in a module can share multiple "business handler" instances, increasing code reuse by one order of magnitude.