前言
當專案需求不斷擴張的時候,當開發團隊人員不斷增加,當新技術不斷湧現,當軟體質量不斷提高,我還是不能和你分手,不能和你分手。我對唱出聲的同學不發表任何意見。如果你真的碰到上述問題而沒有演進你的架構,可能你碰到的問題都是屬於靈異事件。那這裡的核心點是架構,那它又是個什麼玩意?它能帶來什麼好處?
架構
到底什麼是架構?我現在的水平還不能告訴你,沒資格。我能告訴你自己在學習過程中的領悟,我想架構就是一種約定,怎樣的約定?為了更好的研發效率、擴充套件性和穩定性,更精準的風險防控能力。這樣說可能有點抽象。
作為一名資深宅男,床到門就是世界上最遙遠的距離,上一次廁所好像經歷了九九八十一難,但我們從不寂寞,你說是吧,Mac。額,好像扯遠了,我用完東西可能一直放在我喜歡的地方,只是在其他人眼中顯得有點亂,在我眼裡是很有規律的。因為我一直遵循著這個規定,每本書都有我給它定義的位置。但是我老媽不知道啊,雖然整理的很乾淨,但是書放錯了位置,老媽可能是無意識的,就算下次我問她,她也記不清了。為了找到這本書,我也只能遍歷一遍了。如果我們雙方都遵循我們倆一起定義的一個規定,一個約定,達成一種共識。那麼就很簡單了,書永遠在那,就算是添新書還是移除,雙方都有章可循。
由此可見,架構的好處就是除了引進新的技術,自己或者說產品本身都有更好的體驗外,還由於做了統一規範,讓結構更加清晰,健壯,團隊協作更加方便。
以上並不是今天的主題,架構的理解也僅限於我的理解,相對於Android,我們知道的架構更多的可能叫MVC、MVP和MVVM等。關於這個。。。對方不想和你說話,並向你扔了一個連結。一篇好的文章是由文章本身內容和優秀的評論組成的,所以,大家慢慢欣賞,算了,買一送一,喜歡研究架構的同學戳這裡,谷歌爸爸的。這裡,我直接引出今天的主題Clean架構。
Clean架
大多數情況下,碰到問題,我們要從3個方向去思考,what、how、why。引用某大佬的一句話:未經思考的人生是不值得去過的人生。所以,列位看官不要僅僅滿足於快餐文化,經常多動腦子,看問題千萬不要流於表面!額,又扯遠了。。。我先說說what。
What
概念一直都是枯燥乏味的,如果不喜歡的朋友,可以直接跳到How或者本章節的總結部分,但是概念也是最基礎的,最基礎是不是最重要我不發表意見。
秉承我們No picture,say a J8的優雅傳統,先上個毫無意義的結構圖。
這張圖可能對於你們小白看來覺得很不可思議,但對於我們專業來說,也是一臉懵逼(手動滑稽),我簡單的解釋下:
- Enterprise Business Rules:業務物件
- Application Business Rules:用於處理我們的業務物件,業務邏輯所在,也稱為Interactor
- Interface Adapters: 介面轉換,拿到我們需要的資料,表現層(Presenters)和控制層(Controllers)就在這一層
- Frameworks and Drivers: 這裡是所有具體的實現了:比如:UI,工具類,基礎框架等等。
關於上面的圖,米娜可以戳這裡。
我解釋後估計你還是一頭霧水,我們再來看一個圖:
好像相對於上面那張圖更好理解,知道為什麼嗎?因為字少了好多。哈哈。接下來的內容以及我的開源專案中都是以此為基礎來寫的。分別來解釋下。
表現層 (Presentation Layer)
我們這裡的表現層以MVP為基礎,個人覺得Clean本身也是MVP的基礎上更加抽象,更加獨立。熟悉的MVP的同學非常清楚這一層是幹嘛用的。老規矩,先上張圖。
是不是很眼熟?P層使得V層(Fragment和Activity)內部除UI邏輯再無其它邏輯。而我的開源專案中的Presenter由多個Interactor組成。底下會介紹。
領域層 (Domain Layer)
圖上很明顯了,這裡主要是interactor的實現類和業務物件。講道理這裡應該只屬於java模組,但是有時候我們的業務物件,可能要實現第三方庫中的實體類介面,不得不改為Android模組,暫時沒想到很好的辦法,有知道的大佬可以指教一下。
資料層 (Data Layer)
這是一種Repository模式,具體的可以看這裡。以我現在的見解,只能說只要專案複雜而需要分層,那麼就應該用這個模式,它讓clean架構的clean更加亮眼。
這個本來就是概念,我相信大家也不願意看,所以就簡單介紹。如果想詳細瞭解,可以戳這裡。
總結
現在談談自己的看法,後者是相對前者較為具體的一種符合Android的結構。在這插一個clean架構的依賴性規則:內層不能依賴外層。三者也都分別解釋了是幹什麼用的,那麼為什麼有分為這三者,它們又有什麼聯絡?我是個俗人,那就應該用俗話來講,從資料層利用Repository模式讓領域層感覺不到資料訪問層的存在,即原始資料是獨立的,業務規則不繫結具體哪一種資料,通俗點講就是你要什麼資料?我給你取,但你不需要知道我從哪裡取的;因此領域層對資料層怎麼實現的是一無所知,而領域層主要工作就是你給了我資料,那我就要用,怎麼用?都是我來決定;用完之後再回撥給表現層渲染UI。因此大多數的業務邏輯都在領域層,可以說是一個APP的核心。我認為這裡透露著一個很重要的設計理念就是資料驅動UI,我都想給自己點個贊,哈哈。其實,到這裡,你心裡已經有點13數的話,可以跳到Why,因為怎麼用已經是具體的東西,而架構本身就是一種共識,是抽象的,從Java角度講你可以多個類去實現這個介面。下面的使用只是我對Clean架構理解的一點程式碼體現。
How
下面的例子是從我開源庫CrazyDaily中選取的,以知乎日報為例。
資料層 (Data Layer)
資料層就是從我們的倉庫(Repository)中取資料,可以從雲端、磁碟或者記憶體中取。
public interface ZhihuService {
String HOST = "http://news-at.zhihu.com/api/4/";
@GET("news/latest")
Flowable<ZhihuNewsEntity> getZhihuNewsList();
@GET("news/{id}")
Flowable<ZhihuNewsDetailEntity> getZhihuNewsDetail(@Path("id") long id);
}
public class ZhihuDataRepository implements ZhihuRepository {
...
@Inject
public ZhihuDataRepository(HttpHelper httpHelper) {
mZhihuService = httpHelper.getZhihuService();
}
@Override
public Flowable<ZhihuNewsEntity> getZhihuNewsList() {
return mZhihuService.getZhihuNewsList()
...
}
...
}複製程式碼
這裡比較尷尬的是隻提供了雲端的資料,採用的是retrofit+okhttp的框架獲取。比較正確的方式應該是給ZhihuDataRepository提供一個Factory而不是HttpHelper,Factory根據不同的條件獲取相應的資料。比如像這樣:
@Inject
UserDataRepository(UserDataStoreFactory dataStoreFactory,
UserEntityDataMapper userEntityDataMapper) {
this.userDataStoreFactory = dataStoreFactory;
this.userEntityDataMapper = userEntityDataMapper;
}複製程式碼
UserDataStoreFactory是從不同地方獲取資料的一個工廠類,UserEntityDataMapper是我們的資料包裝類,不知道還記得上面的Interface Adapters嗎?細心的朋友可以關注到ZhihuDataRepository實現了ZhihuRepository,但是ZhihuRepository並非資料層的東西,而是領域層的東西,很顯然,以介面進行關聯,但內容獨立,沒錯,這就是傳說中的依賴倒置原則。
領域層 (Domain Layer)
public interface ZhihuRepository {
Flowable<ZhihuNewsEntity> getZhihuNewsList();
Flowable<ZhihuNewsDetailEntity> getZhihuNewsDetail(long id);
}
public abstract class UseCase<T, Params> {
...
public UseCase() {
...
}
protected abstract Flowable<T> buildUseCaseObservable(Params params);
public void execute(Params params, DisposableSubscriber<T> subscriber) {
...
}
...
}
public class ZhihuNewsListUseCase extends UseCase<ZhihuNewsEntity, Void> {
private final ZhihuRepository mZhihuRepository;
@Inject
public ZhihuNewsListUseCase(ZhihuRepository zhihuRepository) {
mZhihuRepository = zhihuRepository;
}
@Override
protected Flowable<ZhihuNewsEntity> buildUseCaseObservable(Void aVoid) {
return mZhihuRepository.getZhihuNewsList()
...
}
}複製程式碼
真的很完美,跟資料層一毛線關係都沒有,利用介面(ZhihuRepository)來控制資料層(ZhihuDataRepository)。真的感覺架構越來越有意思了。我可以在這裡處理我們大部分的業務邏輯。
表現層 (Presentation Layer)
@ActivityScope
public class HomePresenter extends BasePresenter<HomeContract.View> implements HomeContract.Presenter {
private ZhihuNewsListUseCase mZhihuUseCase;
...
@Inject //多個UseCase
public HomePresenter(ZhihuNewsListUseCase zhihuUseCase ...) {
mZhihuUseCase = zhihuUseCase;
...
}
@Override
public void getZhihuNewsList() {
mZhihuUseCase.execute(new BaseSubscriber<ZhihuNewsEntity>() {
@Override
public void onNext(ZhihuNewsEntity zhihuNewsEntity) {
mView.showZhihu(zhihuNewsEntity);
}
});
}
}
public interface HomeContract {
interface View extends IView {
void showZhihu(ZhihuNewsEntity zhihuNewsEntity);
...
}
interface Presenter extends IPresenter<View> {
void getZhihuNewsList();
...
}
public class HomeActivity extends BaseActivity<HomePresenter> implements HomeContract.View {
@Override
protected void initData() {
mPresenter.getZhihuNewsList();
...
}
@Override
public void showZhihu(ZhihuNewsEntity zhihuNewsEntity) {
...
}
...
}複製程式碼
說實話真的不想貼程式碼,太麻煩了,但不貼,不太好理解,而與我們相濡以沫的也就是程式碼了,眼睛莫名一酸。表現層更多的是MVP的概念,所以。。
簡單的理下邏輯,Activity發起獲取資料訊號(View)->呼叫Presenter介面(Presenter)->呼叫UseCase介面(Domain)->呼叫Repository介面(Data)->拿到原始資料->回撥Repository(Data)->回撥UseCase(Domain)->回撥Presenter(Presenter)->回撥Activity(View)
如果這都看到不懂,買塊豆腐撞死算了。
Why
其實How並不是很重要,但卻佔了很大篇幅。
你認為Why是最重要的嗎?這個問題留在心中,先來看看Why。
- Easy to maintain
- Easy to test
- Very cohesive
- Decoupled
看見高內聚,低耦合我就頭疼。說說其它吧,易於維護,這樣的結構已經能說明了,易於測試也好理解,各個模組獨立,來看看這:
- 展示層 (Presentation Layer) : 使用android instrumentation和espresso進行整合和功能測試
- 領域層 (Domain Layer) :使用JUnit和Mockito進行單元測試
- 資料層 (Data Layer) :使用Robolectric(因為依賴於Android SDK中的類)進行整合測試和單元測試
說來慚愧,嫌麻煩,我在開源專案裡並沒有寫測試,相信我以後會加的,我也不是那種不負責任的人,獻上大佬的一個關於clean的開源專案Android-CleanArchitecture。
題外騷話
關於clean的差不多到這裡結束了,如果有什麼問題,可以聯絡我,一起討論。先喝一碗雞湯為敬,學會平靜的對待生活中的不完美之處,適應自己的情緒,瞭解如何讓它們自然宣洩出去。說來也好笑,我的一個庫MTRVA也受到了我學習架構時的影響,它是RecyclerViewAdapter的相當於Presenter層的一個東西,我們總是免不了在Adapter中對資源或者說資料的處理,這就跟Activity一鍋粥一樣,它讓你只關注UI邏輯。有人說,我的介面簡單,啥都不用算,給我list,setAdapter,搞定。額。。。那確實不用。同理,簡單的專案也並不需要什麼複雜的架構,相信從頭看到尾的同學,很明顯的看出clean架構的缺點,就是繁瑣。架構本然就是在需求中不斷演進。祝你能夠搭建屬於你們的架構。最後,感謝一直支援我的人!
傳送門
Github:github.com/crazysunj/
部落格:crazysunj.com/