Javaオブジェクト向け設計ベストプラクティス-列挙設計


列挙タイプに対する印象の多くはC言語から来ており、C言語では列挙タイプはHardCode(ハードコーディング)タイプであり、その使用価値は大きくない.そのため、Javaでは 5の前に、列挙は捨てられた.しかしJavaは 5以降の発見バージョンは列挙をサポートし始め、列挙の導入はJava世界に論争をもたらした.
筆者は列挙の導入に比較的賛成し,汎用的な静的プログラミング言語としてはヘナ百川であるべきである(そのため筆者はJavaへのパッケージ閉鎖に賛成する. 7)、マルチパス機能.
列挙の使い方が分からない場合は、筆者の以前のネットワークリソースを参考に、基本的な使い方を理解することをお勧めします.アドレス:http://mercyblitz.blog.ccidnet.com/blog-htm-do-showone-uid-45914-type-blog-itemid-189396.html
列挙は特殊な(制限された)クラスであり、以下の特徴を有する.
  • 可列性
  • 定数
  • 強タイプ
  • クラスの特性
  • 質問を残します.これらの列挙の特徴をどのように利用して、設計のためにより良いサービスを提供しますか?これらの特徴から、それぞれのデザインテクニックをご紹介します.
    一、列挙性
    デザインでは、列挙使用シーンを明確にしなければなりません.内部メンバーの列挙はすべて列挙可能であり、あるいは固定的である.このハードコーディングの形式は、不自由に見えるが、これが列挙である.動的(非列)なメンバーが必要な場合は、列挙は使いにくいです.
    JDKは多くの良好な列挙可能な設計列挙を提供している.たとえば、時間単位java.util.concurrent.TimeUnitとスレッドステータスはjava.lang.Thread.Stateを列挙します.
     
    ゲームの難易度の列挙があると仮定して、3種類の難易度NORMALがあります , MEDIUM, HARD
    /**
    
     *         :NORMAL , MEDIUM, HARD
    
     * 
    
     * @author mercyblitz
    
     */
    
    public enum Difficulty {
    
    NORMAL, MEDIUM, HARD //  :      ,         
    
    }
    
     
     
    他のメンバーを追加する場合は、列挙クラスにハードコーディングで追加するしかありません.列挙Difficultyに戻ると、低バージョンは必ずバイナリ互換性に影響します.静的言語では避けられない,列挙の短所とは考えられない.
     
    二、定数性
    定数性として定着したのは,列挙が変えられないためであり,メンバーが変わらないことをどのように証明するのか.上記のDifficultyの列挙を例にとると、簡単なコードでそのプロトタイプが得られ、以下のようになります.
    	package org.mercy.design.enumeration;
    	
    	import java.lang.reflect.Field;
    	
    	/**
    	 * Difficulty    
    	 * @author mercy
    	 */
    	public class MetaDifficulty {
    	
    		public static void main(String[] args) {
    			MetaDifficulty instance = new MetaDifficulty();
    			//             
    			Class<Difficulty> classDifficulty = Difficulty.class;
    			for (Field field : classDifficulty.getFields()) {
    				instance.printFieldSignature(field);
    				System.out.println();
    			}
    		}
    	
    		/**
    		 *        (signature)
    		 * 
    		 * @param field
    		 */
    		private void printFieldSignature(Field field) {
    			StringBuilder message = new StringBuilder("    ")
    									.append(field.getName())
    									.append("    :")
    									.append(field.toString())
    									.append("\t");
    			System.out.print(message);
    		}
    
    }
     
     
    printFieldSignatureメソッドは、Difficultyを列挙するフィールドを出力します.その結果、次のようになります.
    フィールド  NORMAL の署名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.NORMAL
    フィールド  MEDIUM の署名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.MEDIUM
    フィールド  HARD の署名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.HARD
     
    この結果は2つの結論を得た.1つは、各列挙メンバーが列挙されたフィールドである.二つ目は、各メンバーがpublic static final.static final  変数はすべてJavaの定数であり、定数プールに保存されます.定数と命名規則に基づいて、列挙された各メンバー(前述)を大文字で命名することを推奨します.
    定数性はデータの一貫性を提供し、他の場所で修正される心配がなく、スレッドの安全を保証します.したがって、設計中にスレッドのセキュリティの問題を心配する必要はありません.
    列挙タイプが定数であれば,==記号を用いて比較できると判断する.しかし、メンバーを列挙してクローン(Clone)することができれば、==比較は失効し、一貫性が保証されない.クラスの定義方法に従って列挙を考慮すると、列挙クラスはjava.lang.Objectクラスを統合しているため、protectedが継承されます. java.lang.Object clone()メソッド,すなわちcloneをサポートするが,反射的な手段で呼び出す必要がある.Java言語仕様では、各列挙はjava.lang.Enum抽象ベースクラスを継承していると述べています.
    真偽を確認するためのテストコードを提供します.
    	/**
    	 *        java.lang.Enum<E>    ?
    	 * 
    	 * @param klass
    	 * @return
    	 */
    	private boolean isEnum(Class<?> klass) {
    		Class<?> superClass = klass.getSuperclass();
    		while (true) { //     
    			if (superClass != null) {
    				if (Enum.class.equals(superClass))
    					return true;
    			} else {
    				break;
    			}
    			superClass = superClass.getSuperclass();
    		}
    		return false;
    	}
     
        クライアントコード呼び出し:instance.isEnum(Difficulty.class); 結果はtrueを返し、Difficulty列挙がjava.lang.Enum抽象ベースクラスを継承していることを証明します.
    ではjava.lang.Enumはcloneを上書きする方法がありますか?ソースコードを確認します.
    /**
    
         * Throws CloneNotSupportedException.  This guarantees that enums
    
         * are never cloned, which is necessary to preserve their "singleton"
    
         * status.
    
         *
    
         * @return (never returns)
    
         */
    
        protected final Object clone() throws CloneNotSupportedException{
    
    throw new CloneNotSupportedException();
    
    }
    
     
     
     
     
    明らかにjava.lang.Enumベースクラスfinalはcloneメソッドを定義しています.すなわち、列挙はクローンをサポートせず、Java docはモノマー性を維持することに言及した.これもオブジェクト向け設計の原則の一つである-単一状態性を維持するオブジェクトにとってclone法をできるだけ支持しないか、露出しない.
     
    三、強いタイプ
    前の2つの特性は、前Java 5 時代には、定数フィールドを利用しても需要を達成することができます.例えばDifficultyクラスのフィールドをこのように設計することができます.
     
    public static final int NORMAL = 1;
    
    public static final int MEDIUM = 2;
    
    public static final int HARD = 3;
     
     
    このような設計には、3つのフィールドがintオリジナルであるため、欠点があります.
    たとえば、ゲームの難易度を設定する方法があります.定義は次のとおりです.
    public void setDifficulty(int difficulty)

       
      intタイプをパラメータとして使用すると、問題が発生する可能性があります.パラメータがNORMAL、MEDIUM、HARD以外のint数であれば許容できます.設計範囲のチェックなど、規則的な方法でこの問題を回避できます.
    /**
    
     *       
    
     * @param difficulty    
    
     * @throws IllegalArgumentException
    
     *                    NORMAL、MEDIUM Hard    ,    IllegalArgumentException
    
     */
    
    public void setDifficulty(int difficulty)
    
     throws IllegalArgumentException 
     
     
    可能な実装方法では,3回のメンバー比較により,不器用で足を止めることができる.
    Javaを使用している場合 5以前のバージョンでは、著者はより良い実装方法を提供しています.
    package org.mercy.design.enumeration;
    
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;
    /**
     * Difficulty JDK1.4   Difficulty  
     * @author mercy
     */
    public class Difficulty14 {
    	//    
    	public static final int NORMAL = 1;
    	public static final int MEDIUM = 2;
    	public static final int HARD = 3;
    	
    	private final static Set difficulties;
    
    	static {
    		// Hash       
    		HashSet difficultySet = new HashSet();
    		difficultySet.add(Integer.valueOf(NORMAL));
    		difficultySet.add(Integer.valueOf(MEDIUM));
    		difficultySet.add(Integer.valueOf(HARD));
    		//                
    		difficulties= Collections.unmodifiableSet(difficultySet);
    	}
    	
    	/**
    	 *       
    	 * @param difficulty    
    	 * @throws IllegalArgumentException
    	 *                   NORMAL、MEDIUM Hard    ,    IllegalArgumentException
    	 */
    	public void setDifficulty(int difficulty) 
    	throws IllegalArgumentException {
    		if(!difficulties.contains(Integer.valueOf(difficulty)))
    			throw new IllegalArgumentException("    ");
    		//    ...
    	}
    }
     
     
    上記のコードでは,範囲チェックが提供されているにもかかわらず,パラメータ範囲は巨大(無数といえる)であり,ランタイムチェックである.setDifficultyのパラメータはintなので、クライアント呼び出し時にコンパイラはintやより狭い範囲のshort、byteなどのタイプを受け入れることができます.それは良い実践に反します.オブジェクト向け設計では、実行時ではなくコンパイル時にタイプ範囲を決定することが望ましいです.
    もう1つの良好なオブジェクト向けの実践-オリジナル型ではなくオブジェクトタイプを利用する(プログラミング言語がサポートされている場合).では、intタイプの代わりにjava.lang.Integerを使用し、Integerがfinalクラスである場合、多態を心配する必要がない場合、強いタイプを提供することができるのではないでしょうか.確かに、強いタイプコンストレイントが提供され、タイプ範囲がよりよくロックされます(finalのため).しかし,Integerの範囲はある程度無限であると考えられるが,switch文はサポートされていない( intのみサポート  、 short、byte、Java 5  列挙タイプ).だからIntegerはまだ適切ではありません.
    列挙の定数性と列挙性は、Difficultyシーンで特に適しています.
     
    四、クラスの特性
    各列挙はjava.lang.Enunベースクラスを継承していることが知られており、定数性もあればクラスの特徴もある.名前やordinalフィールドのステータスなど、制限されたクラスですが、JVMで処理されます.しかし、開発者はクラスの特徴を十分に利用し、優美な設計をすることができます.
    列挙もクラスである以上,クラスの設計に従う.Difficultyクラスを拡張し,オブジェクト向けに列挙を設計する.
    1.  パッケージ設計
    需要-Difficultyの永続化がある場合は、d ifficult iesデータベーステーブルに格納し、一意のid整数値を提供します.オブジェクト向けのパッケージは、列挙にも適用されます.例は次のとおりです.
    public enum Difficulty {
    	//   :      ,         
    	NORMAL(1), MEDIUM(2), HARD(3); 
    	/**
    	 * final            。
    	 */
    	final private int id;
    	
    	Difficulty(final int id){
    		this.id=id;
    	}
    	/**
    	 *   ID
    	 * @return
    	 */
    	public int getId() {
    		return id;
    	}
    }
     
     
    getIdメソッドを呼び出すことで、列挙メンバーのID値を取得できます.
    2.  ちゅうしょうせっけい
    異なるゲームの難易度レベルでは、異なるタスクの難易度値が異なります(多くの場合、列挙オブジェクト自体ではなく値で表されます).Difficultyを例に、難易度値を計算する抽象的な方法を定義します.
    	/**
    	 *           
    	 * 
    	 * @param mission
    	 * @return
    	 * @throws IllegalArgumentException
    	 *               <code>mission</code>     。
    	 */
    	public abstract int getValue(int mission) throws IllegalArgumentException;

     
    3.  たじょうたいせっけい
    Difficulty列挙で抽象メソッドgetValueを定義すると、そのサブクラスはこのメソッドを実装する必要があります.ただし、列挙は継承できません.デフォルトjava.lang.Enumクラスを除いて、他のクラスも継承できません.したがって列挙はfinalのバージョンであり、抽象的な方法を実現することはできませんか?
    列挙の特殊なここでは、列挙はextendsキーワードを表示的に継承することはできないが、その各メンバーは自分のサブクラスである.では、Difficultyを例にとると、java.lang.Enumのクラス階層関係になります. -> Difficulty -> HARD.このように、各列挙メンバーはfinalのクラス内蔵クラスを定義することに相当する.
    Difficulty列挙に戻り、抽象的な方法を実現するには、次のようにします.
     
     
    NORMAL(1) {
    		@Override
    		public int getValue(int mission) 
    	throws IllegalArgumentException {
    			return mission + this.getId();
    		}
    	},
    	MEDIUM(2) {
    		@Override
    		public int getValue(int mission) 
    	throws IllegalArgumentException {
    			return mission * this.getId();
    		}
    	},
    	HARD(3) {
    		@Override
    		public int getValue(int mission) 
    	throws IllegalArgumentException {
    			return mission << this.getId();
    		}
    	};
     
    4.  デザインの継承
    上記の実装では,getValueを実装する各メソッドが利用するgetId()メソッドが観察される.では、列挙クラス自体も特殊なベースクラスであり、テンプレートメソッドを定義できることを再説明します.
    5.  シリアル設計
    いくつかの設計では、列挙をシリアル化する必要があります.Difficulty 14の例に戻ると、3つのメンバー変数が定数であり、staticの変数はシリアル化されない.static修飾を外すと意味が変わります.列挙とは異なり、すべてのカスタム列挙はjava.lang.Enumのサブクラスであるため、すべての列挙はシーケンス化可能である(java.lang.Enum java.io.Serializable)を実現しました.これも定数に対する列挙の利点の一つである.
    インプリメンテーションでは、次のような方法が提供されます.
    private void readObject(ObjectInputStream in) throws IOException
    
    private void readObjectNoData() throws ObjectStreamException 
     
     
    要するに、列挙はクラスのように、オブジェクト向けの特徴を実現することもできるが、列挙ではできるだけ少ない状態を保ち、職責が単一の設計を行うべきであることは言うまでもない.
     
    この文章はあなたにデザインのインスピレーションをもたらしたのではないでしょうか.筆者の知識と経験は限られています.皆さんの指摘と相互学習を歓迎します.