Lambda 表達式的設(shè)計原則
Java 編程最基本的原則就是要追求高內(nèi)聚和低耦合的解決方案和代碼模塊設(shè)計,這里我們主要討論在 Lambda 表達式的環(huán)境下來設(shè)計我們程序時的兩點原則:單一原則 和 開放閉合原則。
1. 單一原則
程序中的類或者方法只有一個改變的理由
當(dāng)需要修改某個類的時候原因有且只有一個。換句話說就是讓一個類只做一種類型責(zé)任,當(dāng)這個類需要承當(dāng)其他類型的責(zé)任的時候,就需要分解這個類。 類被修改的幾率很大,因此應(yīng)該專注于單一的功能。如果你把多個功能放在同一個類中,功能之間就形成了關(guān)聯(lián),改變其中一個功能,有可能中止另一個功能,這時就需要新一輪的測試來避免可能出現(xiàn)的問題,非常耗時耗力。
我們來看一個質(zhì)數(shù)統(tǒng)計的例子:
//這是一個違反單一原則的例子
public SRPDemo{
public static long countPrimes(int maxNum){
long total = 0;
for(int i = 1; i<maxNum;i++){
boolean isPrime = true;
for( int j=2; j<i;j++){
if(i%j ==0){
isPrime = false;
}
}
if(isPrime){
total = total +1;
}
}
return total;
}
?
public static void main(String ...s){
System.out.println(countPrimes(100));
}
}
輸出結(jié)果:
26
上面的例子違反了單一原則,一個方法包含了兩重職責(zé):
- 計數(shù);
- 判斷是否為一個質(zhì)數(shù)。
我們把上面的代碼重構(gòu),將兩種職責(zé)拆分到兩個方法中:
//符合單一原則的例子
public SRPDemo{
//計數(shù)
public static long countPrimes(int maxNum){
long total = 0;
for(int i= 1;i<maxNum;i++){
if(isPrime(i))
total = total+1;
}
return total;
}
//判斷是否為一個質(zhì)數(shù)
public static boolean isPrime(int num){
for(int i = 2;i<num; i++){
if(num%i ==0){
return false;
}
}
return true;
}
?
public static void main(String ...s){
System.out.println(countPrimes(100));
}
}
我們現(xiàn)在使用集合流來重構(gòu)上面代碼:
public SRPDemo{
public static long countPrimes(int maxNum){
return IntStream.range(1,maxNum).filter(MultipleInterface::isPrime).count();
}
public static boolean isPrime(int num){
return IntStream.range(2,num).allMatch(x -> num%x != 0);
}
?
public static void main(String ...s){
System.out.println(countPrimes(100));
}
}
可見,我們使用集合流在一定程度上可以輕松地幫我們實現(xiàn)單一原則。
2. 開放閉合原則
軟件應(yīng)該是擴展開放,修改閉合
- 通過增加代碼來擴展功能,而不是修改已經(jīng)存在的代碼;
- 若客戶模塊和服務(wù)模塊遵循同一個接口來設(shè)計,則客戶模塊可以不關(guān)心服務(wù)模塊的類型,服務(wù)模塊可以方便擴展服務(wù)(代碼);
- 開放閉合原則支持替換的服務(wù),而不用修改客戶模塊。
我們來看一個發(fā)送消息的例子,假設(shè)我們現(xiàn)在有一個消息通知模塊用來發(fā)送郵件和短信:
//這是一個違反開放閉合原則的例子
public class OCPDemo{
//發(fā)送郵件
public boolean sendByEmail(String addr, String title, String content) {
System.out.println("Send Email");
return true;
}
//發(fā)送短信
public boolean sendBySMS(String addr, String content) {
System.out.println("Send sms");
return true;
}
}
想必很多人都會這么寫,這么寫有一個問題,如果哪一天需求變更要求增加微信消息通知,這個時候不僅需要增加一個 sendWechat
的方法,還需要在調(diào)用它的地方進行修改,所以違反了 OCP 原則?,F(xiàn)在我們來做一下修改:
//一個滿足開放閉合原則的例子
public class OCPDemo{
@Data
public static class Message{
private String addr;
private String title;
private String content;
private int type;
}
public boolean send(Message message){
switch (message.getType()){
case 0: {
System.out.println("Send Email");
return true;
}
case 1:{
System.out.println("Send sms");
return true;
}
case 2:{
System.out.println("Send QQ");
return true;
}
default:return false;
}
}
}
我們創(chuàng)建了一個 Message
對象來描述發(fā)送消息的所有信息,并增加了一個 type
字段用來區(qū)分發(fā)送渠道。在遇到類似的情況窩子需要在 send
方法中增加一個對應(yīng) 渠道類型 type
的處理邏輯就可以了,對存量代碼無需求改。滿足了 OCP 原則。
現(xiàn)在我們再來看下使用函數(shù)式接口怎么來優(yōu)化我們的程序:
@Data
public class OCPDemo{
@Data
public static class Message{
private String addr;
private String title;
private String content;
}
?
private Message message;
?
public boolean send(Function<Message , Boolean> function){
return function.apply(message);
}
?
public static void main(String ...s){
Message message = new Message();
message.setTitle("this is a qq msg");
OCPDemo demo = new OCPDemo();
demo.setMessage(message);
demo.send((msg)->{
System.out.println("send qq:\t"+msg.getTitle());
return true;
});
}
}
輸出:
send qq: this is a qq msg
此時,我們運用函數(shù)接口 Function
來處理 Message
,省去了消息類型的判斷,僅當(dāng)調(diào)用的時候決定使用哪種渠道發(fā)送,當(dāng)然我們可以把發(fā)送邏輯都寫在一個工具類里面,利用 Lambda 引用來調(diào)用。
3. 小結(jié)
本節(jié)主要討論的是我們?nèi)绾卧谖覀兊某绦蛟O(shè)計中來使用 Lambda 表達式時所涉及的兩條原則 —— 單一原則 和 開放閉合原則。
這里關(guān)注的是程序整體,而不是具體的某一個方法。其前提是對于 Lambda 表達式的深度理解和熟練運用,為了說明問題,例子大多相對簡單,想了解更詳細的設(shè)計原則還是需要閱讀相關(guān)的專著(比如 S.O.L.I.D 原則),并在日常的編碼過程中不斷實踐和思考。