Lambda 表達(dá)式的設(shè)計(jì)原則
Java 編程最基本的原則就是要追求高內(nèi)聚和低耦合的解決方案和代碼模塊設(shè)計(jì),這里我們主要討論在 Lambda 表達(dá)式的環(huán)境下來設(shè)計(jì)我們程序時(shí)的兩點(diǎn)原則:單一原則 和 開放閉合原則。
1. 單一原則
程序中的類或者方法只有一個(gè)改變的理由
當(dāng)需要修改某個(gè)類的時(shí)候原因有且只有一個(gè)。換句話說就是讓一個(gè)類只做一種類型責(zé)任,當(dāng)這個(gè)類需要承當(dāng)其他類型的責(zé)任的時(shí)候,就需要分解這個(gè)類。 類被修改的幾率很大,因此應(yīng)該專注于單一的功能。如果你把多個(gè)功能放在同一個(gè)類中,功能之間就形成了關(guān)聯(lián),改變其中一個(gè)功能,有可能中止另一個(gè)功能,這時(shí)就需要新一輪的測試來避免可能出現(xiàn)的問題,非常耗時(shí)耗力。
我們來看一個(gè)質(zhì)數(shù)統(tǒng)計(jì)的例子:
//這是一個(gè)違反單一原則的例子
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
上面的例子違反了單一原則,一個(gè)方法包含了兩重職責(zé):
- 計(jì)數(shù);
- 判斷是否為一個(gè)質(zhì)數(shù)。
我們把上面的代碼重構(gòu),將兩種職責(zé)拆分到兩個(gè)方法中:
//符合單一原則的例子
public SRPDemo{
//計(jì)數(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;
}
//判斷是否為一個(gè)質(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));
}
}
可見,我們使用集合流在一定程度上可以輕松地幫我們實(shí)現(xiàn)單一原則。
2. 開放閉合原則
軟件應(yīng)該是擴(kuò)展開放,修改閉合
- 通過增加代碼來擴(kuò)展功能,而不是修改已經(jīng)存在的代碼;
- 若客戶模塊和服務(wù)模塊遵循同一個(gè)接口來設(shè)計(jì),則客戶模塊可以不關(guān)心服務(wù)模塊的類型,服務(wù)模塊可以方便擴(kuò)展服務(wù)(代碼);
- 開放閉合原則支持替換的服務(wù),而不用修改客戶模塊。
我們來看一個(gè)發(fā)送消息的例子,假設(shè)我們現(xiàn)在有一個(gè)消息通知模塊用來發(fā)送郵件和短信:
//這是一個(gè)違反開放閉合原則的例子
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;
}
}
想必很多人都會(huì)這么寫,這么寫有一個(gè)問題,如果哪一天需求變更要求增加微信消息通知,這個(gè)時(shí)候不僅需要增加一個(gè) sendWechat
的方法,還需要在調(diào)用它的地方進(jìn)行修改,所以違反了 OCP 原則。現(xiàn)在我們來做一下修改:
//一個(gè)滿足開放閉合原則的例子
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)建了一個(gè) Message
對(duì)象來描述發(fā)送消息的所有信息,并增加了一個(gè) type
字段用來區(qū)分發(fā)送渠道。在遇到類似的情況窩子需要在 send
方法中增加一個(gè)對(duì)應(yīng) 渠道類型 type
的處理邏輯就可以了,對(duì)存量代碼無需求改。滿足了 OCP 原則。
現(xiàn)在我們?cè)賮砜聪率褂煤瘮?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í),我們運(yùn)用函數(shù)接口 Function
來處理 Message
,省去了消息類型的判斷,僅當(dāng)調(diào)用的時(shí)候決定使用哪種渠道發(fā)送,當(dāng)然我們可以把發(fā)送邏輯都寫在一個(gè)工具類里面,利用 Lambda 引用來調(diào)用。
3. 小結(jié)
本節(jié)主要討論的是我們?nèi)绾卧谖覀兊某绦蛟O(shè)計(jì)中來使用 Lambda 表達(dá)式時(shí)所涉及的兩條原則 —— 單一原則 和 開放閉合原則。
這里關(guān)注的是程序整體,而不是具體的某一個(gè)方法。其前提是對(duì)于 Lambda 表達(dá)式的深度理解和熟練運(yùn)用,為了說明問題,例子大多相對(duì)簡單,想了解更詳細(xì)的設(shè)計(jì)原則還是需要閱讀相關(guān)的專著(比如 S.O.L.I.D 原則),并在日常的編碼過程中不斷實(shí)踐和思考。