Ví dụ Clean Architecture trong Android
This post hasn't been updated for 7 years
Clean Architecture, một design pattern ngày càng phổ biến và sử dụng rộng rãi trong Android. Đã có bài giới thiệu về chủ đề này, nên tôi sẽ không nói lại nữa. Và chúng ta sẽ đi thẳng vào ví dụ, để hiểu rõ hơn về design pattern này.
Trước hết, tôi xin nhắc lại ưu điẻm của Clean Architecture pattern:
- Độc lập với các framework.
- Testable.
- Đôcc lập với UI.
- Độc lập với database.
- Độc lập với bất kỳ external agent nào khác.
- Tổng quan
Ví dụ sẽ được tách làm 3 package : presentation, storage, domain.
2 package đầu thuộc về outer layer và package cuối cùng sẽ là inner/core layer.
Package Presentation chịu trách nhiệm cho tất cả mọi thứ liên quan đến việc hiển thị trên màn hình - nó bao gồm toàn bộ MVP stack (có nghĩa là nó cũng bao gồm cả các giao diện người dùng và các package Presenter mặc dù chúng thuộc các layer khác nhau).
- Tạo một Interactor mới (inner/core layer)
Bạn có thể bắt đầu từ bất kỳ layer nào của architecture, nhưng lời khuyên là nên bắt đầu từ logic core business đầu tiên. Bạn có thể code, test, và chắc rằng nó hoạt động mà không cần đến activity.
Vì vậy, chúng ta hãy bắt đầu bằng cách tạo ra một INteractor. Các INteractor là nơi logic chính của các use case cư trú. Tất cả Interactors đang chạy trong thread nền vì vậy không nên có bất kỳ ảnh hưởng đến hiệu suất UI. Hãy tạo ra một INteractor mới với tên WelcomingInteractor.
public interface WelcomingInteractor extends Interactor {
interface Callback {
void onMessageRetrieved(String message);
void onRetrievalFailed(String error);
}
}
Callback sẽ trách nhiệm giao tiếp với UI trên các main thread, đặt nó vào giao diện của INteractor này vì vậy không cần phải đặt tên cho nó là WelcomingInteractorCallback - để phân biệt nó từ callbacks khác. Bây giờ chúng ta hãy thực hiện logic lấy một tin nhắn. Chúng ta có một MessageRepository có thể cung cấp các welcome message.
public interface MessageRepository {
String getWelcomeMessage();
}
Bây giờ hãy implement giao diện INteractor business logic trên. Điều quan trọng là implement extend của AbstractInteractor và chú ý tới việc chạy trên background thread.
public class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor {
...
private void notifyError() {
mMainThread.post(new Runnable() {
@Override
public void run() {
mCallback.onRetrievalFailed("Nothing to welcome you with :(");
}
});
}
private void postMessage(final String msg) {
mMainThread.post(new Runnable() {
@Override
public void run() {
mCallback.onMessageRetrieved(msg);
}
});
}
@Override
public void run() {
// retrieve the message
final String message = mMessageRepository.getWelcomeMessage();
// check if we have failed to retrieve our message
if (message == null || message.length() == 0) {
// notify the failure on the main thread
notifyError();
return;
}
// we have retrieved our message, notify the UI on the main thread
postMessage(message);
}
Điều này chỉ cố gắng để tải message và gửi message hoặc hiển thị các lỗi UI. Chúng ta xác định UI thông qua callback mà sẽ được thực hiện ở Presenter. Đó là mấu chốt của business logic ở đay. Tất cả mọi thứ mà chúng ta cần làm là framework dependence.
Dưới đây là 1 số phụ thuộc mà Interactor này có:
import com.kodelabs.boilerplate.domain.executor.Executor;
import com.kodelabs.boilerplate.domain.executor.MainThread;
import com.kodelabs.boilerplate.domain.interactors.WelcomingInteractor;
import com.kodelabs.boilerplate.domain.interactors.base.AbstractInteractor;
import com.kodelabs.boilerplate.domain.repository.MessageRepository;
Như bạn có thể thấy, ở đây không đề cập đến bất kỳ code Android nào. Đó là lợi ích chính của phương pháp này. Bạn có thể thấy được sự độc lập của framework. Ngoài ra, chúng ta không quan tâm đến chi tiết cụ thể của UI hoặc db, chúng ta chỉ cần gọi interface mà ở đâu đó outer layer sẽ implement. Do đó, chúng ta có sự độc lập về UI và db.
Bây giờ chúng ta có thể run và test Interactor không cần chạy emulator. Vì vậy, chúng ta hãy viết một test JUnit đơn giản để đảm bảo nó hoạt động:
...
@Test
public void testWelcomeMessageFound() throws Exception {
String msg = "Welcome, friend!";
when(mMessageRepository.getWelcomeMessage())
.thenReturn(msg);
WelcomingInteractorImpl interactor = new WelcomingInteractorImpl(
mExecutor,
mMainThread,
mMockedCallback,
mMessageRepository
);
interactor.run();
Mockito.verify(mMessageRepository).getWelcomeMessage();
Mockito.verifyNoMoreInteractions(mMessageRepository);
Mockito.verify(mMockedCallback).onMessageRetrieved(msg);
}
Một lần nữa, code Interactor này không có ý tưởng rằng nó sẽ sống bên trong một ứng dụng Android. Điều này chứng tỏ rằng business logic ở đây là Testable, đó là điểm thứ hai để thấy.
- Tạo presenter layer
Code Presenter thuộc outer layer trong Clean. Nó bao gồm các code phụ thuộc vào framework để hiển thị UI cho người dùng. Chúng ta sẽ sử dụng lớp MainActivity để hiển thị welcome message cho người sử dụng khi ứng dụng resume.
public interface MainPresenter extends BasePresenter {
interface View extends BaseView {
void displayWelcomeMessage(String msg);
}
}
Vì vậy, làm thế nào và nơi nào chúng ta bắt đầu Interactor khi một ứng dụng resume? Tất cả mọi thứ mà liên quan đến stritly nên cho vào các lớp Presenter. Điều này giúp tách các mối quan hệ và ngăn ngừa các class Hoạt động cồng kềnh. Điều này bao gồm tất cả các code làm việc với Interactors.
Trong onResume của Mainactivity, chúng ta nên override hàm onResume():
@Override
protected void onResume() {
super.onResume();
// let's start welcome message retrieval when the app resumes
mPresenter.resume();
}
Tất cả Presenter object implement hàm resume() khi chúng extend từ BasePresenter.
Chúng ta start Interactor bên trong MainPresenter class trong hàm resume() :
@Override
public void resume() {
mView.showProgress();
// initialize the interactor
WelcomingInteractor interactor = new WelcomingInteractorImpl(
mExecutor,
mMainThread,
this,
mMessageRepository
);
// run the interactor
interactor.execute();
}
Hàm execute() sẽ chạy hàm run() của WelcomingInteractorImpl bên trong background thread. Hàm run() có thể được thấy khi viết một Interactor mới.
MainPresenter của MainActivity sẽ implement Callback interface:
public class MainPresenterImpl extends AbstractPresenter implements MainPresenter, WelcomingInteractor.Callback {
Đó là cách chúng ta lắng nghe sự kiện từ Interator. Và đây là code từ MainPresenter:
@Override
public void onMessageRetrieved(String message) {
mView.hideProgress();
mView.displayWelcomeMessage(message);
}
@Override
public void onRetrievalFailed(String error) {
mView.hideProgress();
onError(error);
}
Và MainActivity cũng se implement interface View của MainPresenter.
public class MainActivity extends AppCompatActivity implements MainPresenter.View {
@Override
public void displayWelcomeMessage(String msg) {
mWelcomeTextView.setText(msg);
}
- Tạo một storage layer
Đây là nơi respository được implement. Tất cả các code cụ thể của cơ sở dữ liệu nên ở đây. Các repository pattern chỉ abstract nơi dữ liệu đến từ đâu. Business logic chính ở đây là không biết gì về nguồn gốc của dữ liệu - có thể là từ một cơ sở dữ liệu, máy chủ hoặc các tập tin văn bản.
Đối với dữ liệu phức tạp, bạn có thể sử dụng ContentProviders hoặc các công cụ ORM như DBFlow. Nếu cần phải lấy dữ liệu từ các trang web, Retrofit sẽ giúp bạn. Nếu cần lưu trữ giá trị đơn giản, có thể sử dụng SharedPreferences. Nên sử dụng đúng công cụ cho từng công việc.
Database ở đây không hẳn là một database. Nó chỉ đơn giản là một class với một sự mô phong delay:
public class WelcomeMessageRepository implements MessageRepository {
@Override
public String getWelcomeMessage() {
String msg = "Welcome, friend!"; // let's be friendly
// let's simulate some network/database lag
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return msg;
}
}
Chúng ta không cần quan tâm đến những gì là dưới MessageRepository miễn là nó thực hiện đúng giao diện.
- Tổng kết
Quy trình hoạt ddoognj của Clean Architecture sẽ như sau: MainActivity ->MainPresenter -> WelcomingInteractor -> WelcomeMessageRepository -> WelcomingInteractor -> MainPresenter -> MainActivity
- Điều quan trọng cần nhớ về flow control: Outer — Mid — Core — Outer — Core — Mid — Outer.
Nguồn: dmilicic
All Rights Reserved