今天看書的時候摘錄下一句很有意思的話,共勉之。
Adding features means adding new code instead of modifying the old code.
原文鏈接--S is for Single Responsibility Principle——by Donn Felker
(譯者注:這次原文很長濒析。我建議先看代碼部分惫谤。Uncle Bob的書的確值得多讀汰蓉。實現(xiàn)這個系列的最好方法:找一個以前寫的老Activity
康铭,審視命名規(guī)范蹋宦,內(nèi)存泄漏披粟,是否符合設(shè)計原則,這樣進步飛快)妆档。
這是SOLID原則五部曲的第一步僻爽。
SOLID是面向?qū)ο蟮奈鍌€設(shè)計原則的縮寫:
- 單一職責(zé)原則 (Single Responsibility Principle)
- 開/閉原則 (Open-Close Principle)
- Liskov 替換原則 (Liskov Substitution Principle)
- 接口分離原則 (Interface Segregation Principle)
- 依賴翻轉(zhuǎn)原則 (Dependency Inversion Principle )
接下來幾周,我們會深入了解各個原則贾惦,解釋它們的含義胸梆,如何與Android結(jié)合。所有課程結(jié)束后须板,你會抓住原則的精髓碰镜,了解到作為Android攻城獅,在日常的開發(fā)中運用這些原則是如此重要习瑰。
SOLID的歷史
SOLID是Rober Martin(Uncle Bob)在2000年與Michael Feathers共同提出的绪颖。結(jié)合運用這五項基本原則,能快速構(gòu)建出可維護甜奄,高拓展性的系統(tǒng)柠横。
如果不熟悉Rober Martin或者Michael Feathers,高度推薦他們寫的書:《敏捷軟件開發(fā)课兄,原則 模式與實踐》牍氛,《代碼整潔之道》是軟件社區(qū)的精神食糧。Michael Feathers的《修改代碼的藝術(shù)》是我如果作為開發(fā)組長烟阐,必須要求每個開發(fā)成員都讀的書搬俊。它能幫助你整理優(yōu)化舊代碼的思路,重構(gòu)出更易維護的代碼蜒茄。更重要的是唉擂,它們能改變你對“優(yōu)雅”的準確定義,比如檀葛,你的代碼有單元測試嘛玩祟?沒有?呵呵驻谆。
閱讀這些書的確對我的職業(yè)有巨大的影響卵凑,我極度推薦開發(fā)者去閱讀,買本實體書放柜子里胜臊,經(jīng)常重溫勺卢。
我記得自己使用SOLID原則是在2003年.NET的項目上,那時我的代碼缺乏組織架構(gòu)引導(dǎo)象对,搞得一團糟黑忱。這并不僅僅發(fā)生在.NET身上,新生的技術(shù)往往會經(jīng)歷混沌,例如Android甫煞。最終新技術(shù)會因擁抱SOLID而變得更成熟菇曲。
最近Rober Martin的演講 - Clean Architecture又一次沖擊了Android社區(qū),正是解釋基礎(chǔ)原理的時候抚吠,下面讓我們進入正題常潮。
第一部分-單一職責(zé)原則
單一職責(zé)原則很容易理解,它說的是
A class should have only one reason to change.
以RecycleView和Adapter作為例子楷力,如你所知喊式,RecycleView
是一個展示數(shù)據(jù)的可拓展的View
像捶。為了顯示數(shù)據(jù)师脂,需要使用RecycleView.Adapter
抵窒。
Adapter
從數(shù)據(jù)集中取出數(shù)據(jù)调窍,綁定到View
中。最昂貴的開銷莫過于onBindViewHolder
方法(有時可能是ViewHolder
白筹,為了簡潔我們只關(guān)注onBindViewHolder
)劝篷。RecycleView.Adapter
有一個職責(zé):把數(shù)據(jù)適配到View
中颠焦,并展示在屏幕上何址。
假設(shè)類和Adapter
寫成這樣:
public class LineItem {
private String description;
private int quantity;
private long price;
// ... getters/setters
}
public class Order {
private int orderNumber;
private List<LineItem> lineItems = new ArrayList<LineItem>();
// ... getters/setters
}
public class OrderRecyclerAdapter extends RecyclerView.Adapter<OrderRecyclerAdapter.ViewHolder> {
private List<Order> items;
private int itemLayout;
public OrderRecyclerAdapter(List<Order> items, int itemLayout) {
this.items = items;
this.itemLayout = itemLayout;
}
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
return new ViewHolder(v);
}
@Override public void onBindViewHolder(ViewHolder holder, int position) {
// TODO: bind the view here
}
@Override public int getItemCount() {
return items.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView orderNumber;
public TextView orderTotal;
public ViewHolder(View itemView) {
super(itemView);
orderNumber = (TextView) itemView.findViewById(R.id.order_number);
orderTotal = (ImageView) itemView.findViewById(R.id.order_total);
}
}
}
在上述例子中里逆,onBindViewHolder
沒有具體實現(xiàn),一種我看過很多次的寫法是這樣子:
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Order order = items.get(position);
holder.orderNumber.setText(order.getOrderNumber().toString());
long total = 0;
for (LineItem item : order.getItems()) {
total += item.getPrice();
}
NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.US);
String totalValue = formatter.format(cents / 100.0); // Must divide by a double otherwise we'll lose precision
holder.orderTotal.setText(totalValue)
holder.itemView.setTag(order);
}
這樣的代碼違反了單一職責(zé)原則用爪。
為什么运悲?
因為Adapter.onBindViewHolder
不僅把數(shù)據(jù)類型適配到View
上,還計算了價格的和格式化项钮。這違反了單一職責(zé)原則。Adapter
應(yīng)該只做前者的工作希停,而Adapter.onBindViewHolder
卻額外多做了兩項工作烁巫。
這會有什么問題嘛?
一個包含多種職責(zé)的類會引發(fā)各種問題宠能。首先亚隙,計算訂單的邏輯與Adapter
耦合了。如果你在其他地方需要同樣的計算邏輯违崇,就只能復(fù)制粘貼一份阿弃。一旦這樣做,你的應(yīng)用就會陷入重復(fù)邏輯的泥沼羞延,一旦在一個地方更新代碼渣淳,很容易忘記更新另一個地方,你懂的伴箩。
第二個問題和第一個類似入愧,把格式化數(shù)字耦合到Adapter
中,萬一方法需要移動或修改呢?在一個類中做了過多工作棺蛛,會導(dǎo)致同一個地方容易引發(fā)各種Bug怔蚌。
幸運的是,這個簡單的例子可以通過把計算的邏輯遷移到Order
中解決旁赊,格式話邏輯移動到合適的Format
類中桦踊,依此類推。因此终畅,Order
就可以使用Format
啦籍胯。
更新后的Adapter.onBindViewHolder
長這樣
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Order order = items.get(position);
holder.orderNumber.setText(order.getOrderNumber().toString());
holder.orderTotal.setText(order.getOrderTotal()); // A String, the calculation and formatting moved elsewhere
holder.itemView.setTag(order);
}
我很肯定你會說,這很簡單啊声离。是不是所有情況都如此簡單呢芒炼?用一句軟件工程的術(shù)語說,看情況吧....
讓我們往深層次挖掘
“職責(zé)”的含義
Uncle Bob的理解無可比擬术徊,這里引述他的原話:
In the context of the Single Responsibility Principle (SRP) we define a responsibility as “a reason for change”. If you can think of more than one motive for changing a class, then that class has more than one responsibility.
有時候很難看透本刽,尤其是你面對這個代碼庫很長時間了。這時赠涮,應(yīng)該想到:
You can’t see the forest for the trees.
在軟件工程里子寓,你著重于實現(xiàn)而沒能落眼于抽象,例如——這個花費你巨大精力寫出來的龐然大物笋除,很難看出來它可能具有多重職責(zé)斜友。
更大的挑戰(zhàn)在于,知道時候使用SRP垃它,什么時候不用鲜屏。考慮一下Adapter
的代碼国拇,可以找到各種不同修改代碼的理由和需求洛史。
public class OrderRecyclerAdapter extends RecyclerView.Adapter<OrderRecyclerAdapter.ViewHolder> {
private List<Order> items;
private int itemLayout;
public OrderRecyclerAdapter(List<Order> items, int itemLayout) {
this.items = items;
this.itemLayout = itemLayout;
}
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
return new ViewHolder(v);
}
@Override public void onBindViewHolder(ViewHolder holder, int position) {
Order order = items.get(position);
holder.orderNumber.setText(order.getOrderNumber().toString());
holder.orderTotal.setText(order.getOrderTotal()); // Move the calculation and formatting elsewhere
holder.itemView.setTag(order);
}
@Override public int getItemCount() {
return items.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView orderNumber;
public TextView orderTotal;
public ViewHolder(View itemView) {
super(itemView);
orderNumber = (TextView) itemView.findViewById(R.id.order_number);
orderTotal = (ImageView) itemView.findViewById(R.id.order_total);
}
}
}
Adapter
映射了View
,把數(shù)據(jù)與視圖綁定酱吝,構(gòu)造ViewHolder
等等也殖,這個類擁有多種職責(zé)。
應(yīng)該把這些職責(zé)分開嘛务热?
最終取決于應(yīng)用的迭代忆嗜。如果需要修改View
的結(jié)構(gòu)和邏輯,如同Uncle Bob所說崎岂,由于兩個更改會互相影響捆毫,改變View
的結(jié)構(gòu)同時Adapter
同樣需要修改,這樣的設(shè)計就是過于剛性该镣。
然而冻璃,應(yīng)用的需求如果不經(jīng)常變更响谓,就沒有理由去分離多重職責(zé)。在這個例子中省艳,我們無需做無用的工作娘纷。
所以,我們應(yīng)該做什么跋炕?
一個死板的例子
假設(shè)新產(chǎn)品上市免費試用赖晶,View
需要展示"Free"圖片而不是價格文字,這個邏輯寫在哪里辐烂?一方面遏插,你需要TextView
,另一方面纠修,你需要ImageView
胳嘲。這里有兩個地方需要修改:
-
View
中 - 展示的邏輯
在大多數(shù)應(yīng)用中,這會寫在Adapter
中扣草,不幸的是了牛,當View
改變時時,Adapter
必須同時進行修改辰妙。如果把邏輯也寫在Adapter
中鹰祸,將迫使邏輯也要改變,這增加了Adapter
的職責(zé)密浑。
這正是MVP模式帶來的解耦方案蛙婴,提高了可拓展性,可聚合的程度和可測試性尔破,使類不會變得過于笨重街图。例如,會給View
定義一系列用于交互的Interface
懒构,presenter
會負責(zé)邏輯處理台夺。在MVP中,P只會負責(zé)展示邏輯痴脾。
把邏輯從Adapter
移到Presenter
中的確更符合單一職責(zé)原則。
也不完全是這樣...
如果你深入了解RecycleView.Adapter
梳星,你會發(fā)現(xiàn)Adapter
做了很多事:
- 解析視圖
- 創(chuàng)建
ViewHolder
- 回收
ViewHodler
- 提供數(shù)據(jù)集等等
你會想赞赖,為什么不把這些東西抽出來,讓單一職責(zé)原則實現(xiàn)呢冤灾?我又要引用Uncle Bob的解釋了:
An axis of change is only an axis of change if the changes actually occur. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom.
Adapter
真的做了許多工作前域,事實上,它就是被這樣設(shè)計的韵吨。畢竟RecycleView.Adapter
是適配者模式的簡單應(yīng)用匿垄。保持解析視圖和ViewHolder
的機制的確有道理,這就是這個類的職責(zé)的最好實現(xiàn)。然而椿疗,可以使用MVP或者其他重構(gòu)手段轉(zhuǎn)移邏輯代碼使其符合SRP漏峰。
結(jié)論
單一職責(zé)原理是SOLID中最簡單的一個。再重復(fù)一次:
A class should have only one reason to change.
也有人說届榄,這是實踐起來最難的原則之一浅乔。如果過度實踐SRP,過度的分析增加了代碼的復(fù)雜度铝条。我的建議是:以面向?qū)ο蟮乃枷肟创a靖苇,隔離你的感情,用全新的目光再度審視老代碼班缰,你就會發(fā)現(xiàn)以前從未知道的東西贤壁。也許需要實踐SRP,也許你早已做得足夠好了埠忘。
當應(yīng)用需要修改的時候脾拆,強烈建議在未應(yīng)用SRP的地方時間SRP。
享受生活给梅,享受編程假丧。
期待下一篇,開/閉原則动羽。