(轉)Android中如何優雅的定義常量

寫Android數據庫經常出錯,總想定義好相關的常量,在寫insert,delete的時候就可以直接選擇字段常量,減少出錯的機會,找了這篇博文寫的不錯的分享一下,原帖:https://blog.csdn.net/lilinjie_blog/article/details/81396593

在Java中定義常量的方式


1.在類中定義

public final class Constants {
    private Constants() {}
    public static final int A = 0;
    public static final int B = 1;
    public static final int C = 2;
    public static final int D = 3;
}


2.在接口中定義

public interface Constants {
    int A = 0;
    int B = 1;
    int C = 2;
    int D = 3;
}

那麼這兩種方式區別在哪裏呢? 
1.類定義常量,需要定義成final且定義一個private的構造方法, 這樣做是爲了不讓其他類繼承, 禁止實例化此類,調用時直接以”類.常量”的方式調用。 
2. 接口中定義的”變量”,其實就是常量,接口中的”變量”默認都是 “public static final”類型,即爲常量, 因此接口可以省略”public static final”而直接寫成 “type variable”。 
3.用如上的類定義常量,不能實例化也不能被繼承, 看起了完美無缺。 
4.接口定義常量, 雖不能實例化, 確可以被其他類實現。因此有這麼一種設計模式”The constant interface pattern”,所謂的”常量接口模式”就使用這些常量的類實現這個接口,以避免用類名修飾常量名。其實這是對接口的不良使用。接口中定義的常量應爲所有類頻繁使用的常量,但並不是每個類都使用了接口中定義的所有常量,因而導入了不必要的常量到這個類中,並且導入後這個類的子類也會導入基礎的常量, 這樣會導致混亂,應當避免此種用法。並且Effective Java中第19條也明確指出接口應該只被用來定義類型,他們不應該被用來導出常量。但是呢我們大可不必拘泥於接口這一含義他是對行爲的一種抽象。如果該接口僅僅是作爲定義常量使用,那麼我們可以直接通過”接口名.變量名”調用。這樣也就避免了實現改接口,從而將接口中不必要的常量導入其中,但是這也取決於我們合理的使用。 
5. 在interface和class中定義相同的常量,interface生成的class文件比class生成的class文件會更小,而且更簡潔, 效率更高。 
小結: 
1. 不要使用”常量接口模式”,此模式會導致類中的常量混亂,增加維護難度。 
2. 不要使用靜態導入,import static **。我非常不贊同這種使用常量的方法,因爲import static會導致可維護性下降,維護的人看代碼時, 不是那麼清楚或者不那麼迅速的知道這個常量位於哪個文件中。 建議使用常量的地方直接 “接口.常量名” 的方式使用。 
3. 對於用是用interface定義常量還是使用class定義常量,看個人喜好,個人覺得interface定義常量更爲優美, 代碼更簡潔,生成的class文件更小, JVM不要考慮類的附加特性, 如方法重載等, 因而更爲高效。雖然這是一種反模式的用法, 很多人不喜歡這種用法,如果我們知道它的優缺點,延長避短, 也是無可厚非的。還有一點是不要把這種用法用成”常量接口模式” , 個人覺得”常量接口模式”確實是一種對interface的”pool use”。

使用枚舉代替常量,簡化工作!


需求:有一個任務(Task),服務端定義了一個int status來標記任務的狀態,包括 未開始、進行中、已完成 這三個狀態,分別用 0、1、2 來標記。我們拿到這個status之後,需要根據狀態的不同,顯示不同的文本(需求仍在挖掘中)。需求很明確,一般我們會這麼做: 
在全局的常量類中定義定義3個靜態常量表示狀態常量:

public class Constants {
    public static final int STATUS_UN_START = 0;
    public static final int STATUS_PROGRESSING = 1;
    public static final int STATUS_COMPLETED = 2;
}


接下來在strings.xml定義狀態值:未開始、進行中、已完成

<resources>
    <string name="status_un_start">未開始</string>
    <string name="status_progressing">進行中</string>
    <string name="status_completed">已完成</string>
</resources>

最後,在代碼中拿到status之後,我們會這麼做:

 

 switch (status) {
            case Constants.STATUS_UN_START:
                textView.setText(getResources().getString(R.string.status_un_start));
                break;
            case Constants.STATUS_PROGRESSING:
                textView.setText(getResources().getString(R.string.status_progressing));
                break;
            case Constants.STATUS_COMPLETED:
                textView.setText(getResources().getString(R.string.status_completed));
                break;
            default:
                break;
        }


這樣寫,所有的狀態和狀態值都統一了,規範的很,效果也不錯,毫無違和感! 
突然有一天,產品汪找到你:Hi,我們把文本前面的顏色設置成動態的吧,不同的狀態顏色不一樣,你根據效果圖改一下吧…”。還好這個簡單,分分鐘的事。在colors.xml中,加幾個色值:

<resources>
    <color name="status_un_start">#3F51B5</color>
    <color name="status_progressing">#303F9F</color>
    <color name="status_completed">#FF4081</color>
</resources>

然後修改switch語句,這樣:

switch (status) {
            case Constants.STATUS_UN_START:
                view.setBackgroundResource(R.color.status_un_start);
                textView.setText(getResources().getString(R.string.status_un_start));
                break;
            case Constants.STATUS_PROGRESSING:
                view.setBackgroundResource(R.color.status_progressing);
                textView.setText(getResources().getString(R.string.status_progressing));
                break;
            case Constants.STATUS_COMPLETED:
                view.setBackgroundResource(R.color.status_completed);
                textView.setText(getResources().getString(R.string.status_completed));
                break;
            default:
                break;
        }


So easy,很快解決了問題!慢慢的,隨着開發的深入,你發現許多地方需要這麼顯示,於是就封裝了一個自定義ViewGroup,一切都朝着好的方向發展。但除此之外,在其他地方,也需要判斷這個狀態值,以需要獲取該狀態下的屬性,萬一哪天產品汪跟你說:“我要多加一個狀態,未知(UN_KONW)。服務端和iOS已經同步了,你這邊趕緊吧”,意味着之前涉及狀態判斷的都需要找到加上,豈不傻眼了? 
所以,大多數人還是會選擇將這一部分寫到全局的靜態方法中,這樣加字段、加狀態再也不怕了,在對應文件中加幾個常量【status、顏色、狀態文本】,改一下方法就搞定!由於這塊不是本文重點,就不貼代碼了。至此,我們的程序一步步完善了,一些基本的套路已經無法難倒我們了,完美!功能基本實現了,但是回過頭來想想,這樣做並不優雅!一方面需求變動時我們還是需要改很多地方,另一方面我們並不能很直觀的看出狀態值status與狀態文本、色值之間的關係,比如 0代表未開始,顏色爲黃色。當然,可以向上文一樣,依靠命名規範去解決這個問題。可是,在命名規範的基礎上,有沒有更好的辦法進一步強化他們之間的關係呢?當然有!接下來,就到了今天的主題,一起來藉助枚舉完成這一任務。

—————–枚舉登場—————————–

剛開始,我們可以定義一個枚舉類,這麼寫

public enum TaskStatus {

    UN_START(0, "未開始"),
    PROGRESSING(1, "進行中"),
    COMPLETED(2, "已完成");

    private int status;
    private String desc;

    TaskStatus(int status, String desc) {
        this.status = status;
        this.desc = desc;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public static TaskStatus getStatus(int status) {
        for (TaskStatus taskStatus : values()) {
            if (status == taskStatus.status) {
                return taskStatus;
            }
        }
        return UN_START;
    }
}

用的時候簡直不要太簡單(status是從服務端獲取的狀態值):

 TaskStatus taskStatus = TaskStatus.getStatus(status);
 textView.setText(taskStatus.getDesc());

然後後來產品說加一個色值,你很坦然就加上了。這麼寫:

public enum TaskStatus {

    UN_START(0, "未開始", "#3F51B5"),
    PROGRESSING(1, "進行中", "#303F9F"),
    COMPLETED(2, "已完成", "#FF4081");
    private int status;
    private String desc;
    private String color;

    TaskStatus(int status, String desc, String color) {
        this.status = status;
        this.desc = desc;
        this.color = color;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public static TaskStatus getStatus(int status) {
        for (TaskStatus taskStatus : values()) {
            if (status == taskStatus.status) {
                return taskStatus;
            }
        }
        return UN_START;
    }
}

用的時候毫不影響:

 

TaskStatus taskStatus = TaskStatus.getStatus(status);
 textView.setText(taskStatus.getDesc());
 view.setBackgroundColor(Color.parseColor(taskStatus.getColor()));   

重點來了,該到加未知(UN_KNOW)狀態的時候了,按照上文的做法,我們要改、加的東西很多,但現在只需要加一行代碼,是的,一行代碼:

public enum TaskStatus {
    UN_KNOWN(-1, "未知", "#34D6B5"),
    UN_START(0, "未開始", "#3F51B5"),
    PROGRESSING(1, "進行中", "#303F9F"),
    COMPLETED(2, "已完成", "#FF4081");
    private int status;
    private String desc;
    private String color;

    TaskStatus(int status, String desc, String color) {
        this.status = status;
        this.desc = desc;
        this.color = color;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public static TaskStatus getStatus(int status) {
        for (TaskStatus taskStatus : values()) {
            if (status == taskStatus.status) {
                return taskStatus;
            }
        }
        return UN_KNOWN;
    }
}

就是這麼簡單!同時,這麼寫的話,就不需要在 string.xml、colors.xml、常量類中加一堆值了,直接放在枚舉類中很直觀有木有?!當然,有時候我們需要在某個地方使用常量來判斷,下圖是常見的做法。現在我們把狀態值寫到枚舉裏面了,還能這麼直觀的用嗎?答案是肯定的!

   

     //使用常量
        if(status==Constants.STATUS_PROGRESSING){
            //do something
        }
        //使用枚舉
        if(status=TaskStatus.PROGRESSING.getStatus()){
            //do something
        }

聲明一點,我所說的使用枚舉替換常量,是針對類似於“常量之間存在關聯”的情況,如狀態值status與狀態文本、色值,此時使用枚舉能大大簡化我們的工作,並不是說以後所有常量都寫成枚舉,畢竟官方是不推薦使用枚舉的: 
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android. 
但也只是儘量避免使用!所以在實際開發中,還需要根據實際使用場景去斟酌,杜絕濫用。像本文描述的場景,建議使用,戰鬥力翻倍!

Android使用枚舉註解代替枚舉
需求:我們僅僅需要根據一組狀態中的某一狀態執行相應的操作,不需要多個常量之間相互對應。你自然會想到枚舉的簡化版:

public enum TaskStatus {
    UN_KNOW,
    UN_START,
    PROGRESSING,
    COMPLETED
}


然後定義了一個任務輔助類:

import com.ruicb.enumdemo.TaskStatus;
public class TaskHelper {

    public static void doSth(TaskStatus status){
        switch (status){
            case UN_KNOW:
                //do something
                break;
            case UN_START:
                break;
            case PROGRESSING:
                break;
            case COMPLETED:
                break;
        }
    }
}

然後如此調用:

TaskHelper.doSth(TaskStatus.COMPLETED);

這樣使用枚舉,十分優雅:保證了類型安全:調用者無法隨意傳一個 int 值;代碼可讀性非常高;但是…枚舉對於性能的損耗,事實上你也知道,官方是不建議使用枚舉的,這個在文章開頭簡單說了。想詳細瞭解的,可以參考該博文:http://blog.csdn.net/hp910315/article/details/48975655。總之,與靜態常量相比,枚舉會增大應用程序編譯後的 dex 文件,同時應用在運行時的內存佔用也會升高。在資源有限的移動設備上,大量的使用枚舉無疑是致命的。 
替代方案 
既然如此,我們不使用枚舉不就行了嗎?反正只是簡單的單一狀態,我單獨定義一個常量類,實現分組:

/**
 * 任務狀態常量
 */
public class TsakContants {
    public static final int UN_KNOW = -1;
    public static final int UN_START = 0;
    public static final int PROGRESSING = 1;
    public static final int COMPLETED = 2;
}

然後修改 doSth() 方法:

public class TaskHelper {

    public static void doSth(int status){
        switch (status){
            case TsakContants.UN_KNOW:
                //do something
                break;
            case TsakContants.UN_START:
                break;
            case TsakContants.PROGRESSING:
                break;
            case TsakContants.COMPLETED:
                break;
        }
    }
}

調用起來也沒有區別:

TaskHelper.doSth(TsakContants.COMPLETED);

其次代碼可讀性很差,IDE 只是提示傳入 int 類型的參數。此時如果不看方法體,調用者根本不知道該傳什麼值。那麼問題來了?在一組常量的情況下,我們使用枚舉太重,使用常量不安全、可讀性差,我們該怎麼辦?當然是使用註解了!哈哈,兜了一圈終於輪到註解,是時候爲其正名啦。

@IntDef(定義int型) @StringDef(定義String類型) 是Android提供的註解

@IntDef({
        TaskStatus.UN_KNOW,
        TaskStatus.UN_START,
        TaskStatus.PROGRESSING,
        TaskStatus.COMPLETED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TaskStatus {
    int UN_KNOW = -1;
    int UN_START = 0;
    int PROGRESSING = 1;
    int COMPLETED = 2;
}

修改 doSth() 方法:

public class TaskHelper {

    public static void doSth(@TaskStatus int status){
        switch (status){
            case TaskStatus.UN_KNOW:
                //do something
                break;
            case TaskStatus.UN_START:
                break;
            case TaskStatus.PROGRESSING:
                break;
            case TaskStatus.COMPLETED:
                break;
        }
    }
}


可以看出,doSth() 方法的參數是 int 類型的,但是使用 @TaskStatus 進行了註解,這樣外界就無法傳遞 TaskStatus 之外的成員作爲參數了。 
調用

TaskHelper.doSth(TaskStatus.UN_START);

在調用時,IDE 會提示 @TaskStatus int status,提醒我們傳入 TaskStatus 類型的值。同時,調用者如果再隨便傳入一個 int 值,雖然可以運行,但代碼會爆紅,lint 檢查將會給與警告。

總結
現在看完本篇文章之後再思考,你應該清楚什麼場景下該使用那種方案了吧。在類中定義常量可能更規範一點,但是書寫起來以及class文件大小也要考慮。在接口中定義常量更加方便,class文件也相對較小,但是避免寫成常量接口模式,要合理使用。所以兩者的取捨看個人喜好吧,沒有誰對誰錯。此外如果需要定義一組常量,如果我們使用枚舉,閱讀性很好,但是性能缺損。如果單純的使用常量,類型安全、可讀性差,這時候我們可以考慮Android中提供的枚舉註解,相當的優雅。此外如果定義多組常量,我們可以考慮枚舉定義,這樣閱讀性和擴展性也是非常好的,雖然有性能缺損,但是我們也要度之。在性能還行的情況下,考慮使用。
--------------------- 
作者:lilinjie_blog 
來源:CSDN 
原文:https://blog.csdn.net/lilinjie_blog/article/details/81396593 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章