15

元のコードは次のとおりです。

public class FruitGrower {
    public void growAFruit(String type) {
        if ("wtrmln".equals(type)) {
            //do watermelon growing stuff
        } else if ("ppl".equals(type)) {
            //do apple growing stuff
        } else if ("pnppl".equals(type)) {
            //do pineapple growing stuff
        } else if ("rng".equals(type)) {
            //do orange growing stuff
        } else {
            // do other fruit growing stuff
        }
    }
}

これは私がそれを変更した方法です:

public class FruitGrower {
    enum Fruits {
        WATERMELON {
            @Override
            void growAFruit() {
                //do watermelon growing stuff
            }
        },

        APPLE {
            @Override
            void growAFruit() {
                //do apple growing stuff
            }
        },

        PINEAPPLE {
            @Override
            void growAFruit() {
                //do pineapple growing stuff
            }
        },

        ORANGE {
            @Override
            void growAFruit() {
                //do orange growing stuff
            }
        },

        OTHER {
            @Override
            void growAFruit() {
                // do other fruit growing stuff
            }
        };
        static void grow(String type) {
            if ("wtrmln".equals(type)) {
                WATERMELON.growAFruit();
            } else if ("ppl".equals(type)) {
                APPLE.growAFruit();
            } else if ("pnppl".equals(type)) {
                PINEAPPLE.growAFruit();
            } else if ("rng".equals(type)) {
                ORANGE.growAFruit();
            } else {
                OTHER.growAFruit();
            }
        };
        abstract void growAFruit();
    }


    public void growAFruit(String type) {
        Fruits.grow(type);
    }
}

enumsコードはより長く、コードほど明確ではないかもif-elseしれませんが、その方が良いと思います.なぜ私が間違っているのか(または間違っていないのか)教えてもらえますか?

UPD - ソース コードをより問題に特化したものに変更しました。質問を言い換えます: if-else の代わりに enum を使用することに懸念はありますか?

4

14 に答える 14

19

列挙型の使用を改善することについては、すでに良い答えがあります。文字列定数よりも優れている理由については、次のとおりです。

最大のメリットは、コンパイル時のエラーチェックだと思います。私がと呼んgrowAFruit("watermelon")だ場合、コンパイラは何かが間違っていることを知りません。また、スペルが正しいので、コードを表示しているときに間違いとして目立つことはありません。しかし、もし私がそうするとしたらWATERMELEN.growAFruit()、コンパイラーは私がスペルを間違えたことをすぐにあなたに伝えることができます。

また、--- sの大きなブロックでgrowAFruitはなく、単純で読みやすいメソッドの束として定義することもできます。これは、果物が数十個ある場合、または、、、などを追加し始めるとさらに明らかになります。列挙型がないと、大きなif-elseブロック全体をコピーすることになり、ケースを追加するのを忘れると、落下します。デフォルトの場合まで、または何もしませんが、列挙型を使用すると、コンパイラはメソッドが実装されていないことを通知できます。ifthenelseharvestAFruit()packageAFruit()sellAFruit()

さらにコンパイラチェックの良さ:メソッドと関連する文字列定数もある場合は、またはgrowVegetableの呼び出しを妨げるものは何もありません。定数がある場合、それが果物か野菜かを知る唯一の方法は、関連するメソッドのソースコードを読むことです。繰り返しになりますが、列挙型を使用すると、コンパイラは、何か間違ったことをしたかどうかをすぐに知ることができます。growVegetable("pineapple")growFruit("peas")"tomato"

もう1つの利点は、関連する定数をグループ化し、適切なホームを提供することです。public static final代替案は、それらを使用するクラスでスローされたフィールドの束、または定数のインターフェイスをスタックしたものです。定数でいっぱいのインターフェイスは意味がありません。それがすべてである場合、列挙型を定義する必要があるのは、インターフェイスを書き出すよりもはるかに簡単だからです。また、クラスまたはインターフェースでは、誤って複数の定数に同じ値を使用する可能性があります。

それらはまた反復可能です。列挙型のすべての値を取得するには、を呼び出すだけですがFruit.values()、定数を使用する場合は、独自の配列を作成して設定する必要があります。または、例のようにリテラルを使用するだけの場合、有効な値の信頼できるリストはありません。

ボーナスラウンド:IDEサポート

  • 列挙型を使用すると、IDEのオートコンプリート機能と自動リファクタリングを使用できます
  • Eclipseで列挙値を使用して「参照の検索」などを使用できますが、文字列リテラルを検索するにはプレーンテキスト検索を実行する必要があります。これにより、通常、多くの誤検知が返されます(静的な最終定数を使用する場合はイベント)。 、誰かが文字列リテラルをどこかで使用した可能性があります)

列挙型を使用しない主な理由は、コンパイル時に可能なすべての値がわからない場合です(つまり、プログラムの実行中に値を追加する必要があります)。その場合、それらをクラス階層として定義することをお勧めします。また、無関係な定数の束を列挙型にスローして、それを1日と呼ばないでください。値を接続するある種の共通のスレッドがあるはずです。より適切な場合は、いつでも複数の列挙型を作成できます。

于 2011-06-18T06:20:35.143 に答える
17

列挙型が最適ですが、次のようにコードを劇的に改善できます。

public static String grow(String type) {
    return Fruits.valueOf(type.toUpperCase()).gimmeFruit();
};

ああ、デフォルトのケースが必要なので、少し難しくなります。もちろん、これを行うことができます:

public static String grow(String type) {
    try{
        return Fruits.valueOf(type.toUpperCase()).gimmeFruit();
    }catch(IllegalArgumentException e){
        return Fruits.OTHER.gimmeFruit();
    }
};

しかし、それはかなり醜いです。私は次のようなことをしたいと思います:

public static String grow(String type) {
    Fruits /*btw enums should be singular */ fruit = Fruits.OTHER;
    for(Fruits candidate : Fruits.values()){
        if(candidate.name().equalsIgnoreCase(type)){
            fruit = candidate;
            break;
        }
    }
    return fruit.gimmeFruit();
};

また、すべての enum メソッドが値を返す場合は、個々の項目ではなく、コンストラクターで値を初期化し、Enum クラスで定義されたメソッドで値を返すように設計をリファクタリングする必要があります。

public enum Fruit{
    WATERMELON("watermelon fruit"),
    APPLE("apple fruit")
    // etc.

    ;
    private final String innerName;
    private Fruit(String innerName){ this.innerName = innerName; }
    public String getInnerName(){ return this.innerName; }
}
于 2011-05-25T14:44:19.870 に答える
6

よりクリーンにするための変更は半分しか行っていません。grow メソッドは次のように変更する必要があります。

static String grow(Fruits type) {
    return type.gimmeFruit();
}

そして、次のFruitsように名前を変更する必要がありますFruit: リンゴは果物であり、果物ではありません。

本当に文字列型を保持する必要がある場合は、各型に関連付けられた Fruit を返すメソッドを (たとえば、enum クラス自体で) 定義します。ただし、ほとんどのコードでは String ではなく Fruit を使用する必要があります。

于 2011-05-25T14:45:55.963 に答える
4

Map<String, Fruit>(または)が必要だと思います<String, FruitGrower>

このマップは、列挙型のコンストラクターまたは静的初期化子によって自動的に満たされる可能性があります。(一部の果物にエイリアス名がある場合は、同じ列挙型定数に複数の名前をマップすることもできます。)

メソッドgrowは次のようになります。

static void grow(String type) {
   Fruit f = map.get(type);
   if (f == null) {
       OTHER.growFruit();
   }
   else {
       f.growFruit();
   }
}

もちろん、ここの文字列は本当に必要ですか? 常に列挙型オブジェクトを使用するべきではありませんか?

于 2011-06-16T14:05:33.087 に答える
3

あなたは正しい方向に進んでいると思います。文字列切り替えブロックを取り除くために、HashMapに余分なバイトをいくつか投入します。これにより、見た目がすっきりし、コードが少なくなり、パフォーマンスが少し向上する可能性があります。

public enum Fruit {

    APPLE("ppl") {
        public void grow() {
            // TODO
        }
    },
    WATERMELON("wtrmln") {
        public void grow() {
            // TODO
        }
    },

    // SNIP extra vitamins go here

    OTHER(null) {
        public void grow() {
            // TODO
        }
    };

    private static Map<String, Fruit> CODE_LOOKUP;
    static {
        // populate code lookup map with all fruits but other
        Map<String, Fruit> map = new HashMap<String, Fruit>();
        for (Fruit v : values()) {
            if (v != OTHER) {
                map.put(v.getCode(), v);
            }
        }
        CODE_LOOKUP = Collections.unmodifiableMap(map);
    }

    public static Fruit getByCode(String code) {
        Fruit f = CODE_LOOKUP.get(code);
        return f == null ? OTHER : f;
    }

    private final String _code;

    private Fruit(String code) {
        _code = code;
    }

    public String getCode() {
        return _code;
    }

    public abstract void grow();
}

そして、それはあなたがそれを使う方法です:

Fruit.getByCode("wtrmln").grow();

シンプルで、FruitGrowerは必要ありませんが、必要だと思われる場合は、FruitGrowerを選択してください。

于 2011-06-22T09:11:15.543 に答える
3

ここで列挙型を使用するかどうかはわかりません。ここで何かが欠けている可能性があります (?) が、私のソリューションは次のようになり、果物の種類ごとに個別のクラスがあり、すべてが 1 つの属の果物クラスに基づいていると思います。

// Note: Added in response to comment below
public enum FruitType {
    WATERMELON,
    WHATEVERYOUWANT,
    ....
}


public class FruitFactory {

    public Fruit getFruitToGrow(FruitType type) {

        Fruit fruitToGrow = null;

        switch(type){
            case WATERMELON:
                fruitToGrow = new Watermelon();
                break;
            case WHATEVERYOUWANT:
                ...
            default:
                fruitToGrow = new Fruit();
        }

        return fruitToGrow;
    }
}


public class Fruit(){
    public void grow() {
        // Do other fruit growing stuff
    }
}



// Add a separate class for each specific fruit:
public class Watermelon extends Fruit(){
    @override
    public void grow(){
        // Do more specific stuff... 
    }
}
于 2011-06-20T19:11:43.337 に答える
2

あなたの質問への答えは、上記のように、列挙型、またはさらに良いことに、ファクトリとポリモーフィズムを使用することです。ただし、スイッチ(if-elseステートメントが実際に実行していること)を完全に削除したい場合は、制御の反転を使用するのが最善ではないにしても、良い方法です。したがって、次のようにスプリングを使用することをお勧めします。

public interface Fruit {
   public void grow();
}

public class Watermelon implements Fruit {

   @Override
   public void grow()
   {
       //Add Business Logic Here
   }
}

次に、次のようにフルーツロケーターインターフェイスを作成します。

public interface FruitLocator {

Fruit getFruit(String type);
}

メインクラスに、そのセッターであるFruitLocatorオブジェクトへの参照を持たせ、getFruitコマンドを呼び出すだけです。

private FruitLocator fruitLocator;

public void setFruitLocator (FruitLocator fruitLocator)
{
    this.fruitLocator = fruitLocator;
}

public void growAFruit(String type) {
    fruitLocator.getFruit(type).grow();
}

ここで注意が必要な部分があります。FruitGrowerクラスをSpringBeanとして定義し、FruitLocatorとFruitsを定義します。

<bean id="fruitGrower" class="yourpackage.FruitGrower">     
    <property name="fruitLocator" ref="fruitLocator" />
</bean>

<bean id="fruitLocator"
    class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    <property name="serviceLocatorInterface"
        value="yourpackage.FruitLocator" />
    <property name="serviceMappings" ref="locatorProperties" />
</bean>
<bean id="locatorProperties"
    class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="classpath:fruits.properties" />
</bean>

    <bean id="waterMelon" class="yourpackage.WaterMelon">       
</bean>

次のように、クラスパスにfruits.propertiesファイルを作成し、type-beanマッピングを追加するだけです。

wtrmln=waterMelon        

これで、必要な数のフルーツを追加できます。新しいフルーツクラスを作成し、それをBeanとして定義して、プロパティファイルにマッピングを追加するだけです。コード内のif-elseロジックを探すよりもはるかにスケーラブルです。

これは最初は複雑に思えますが、制御の反転について言及しなければ、トピックは不完全になると思います。

于 2011-06-21T08:13:40.170 に答える
2

変数を使用して gimmeFruit 値を格納し、コンストラクターで初期化することによっても改善できます。

(実際にはコンパイルしていないので、構文エラーがあるかもしれません)

public class FruitGrower {
    enum Fruits {
        WATERMELON("watermelon fruit"),
        APPLE("apple fruit"),
        PINEAPPLE("pineapple fruit"),
        ORANGE("orange fruit"),
        OTHER("other fruit")

        private String gimmeStr;

        private Fruits(String gimmeText) {
            gimmeStr = gimmeText;
        }

        public static String grow(String type) {
            return Fruits.valueOf(type.toUpperCase()).gimmeFruit();
        }

        public String gimmeFruit(String type) {
            return gimmeStr;
        }

    }
}

編集: grow メソッドの型が同じ文字列でない場合は、Map を使用して型の一致を Enum に定義し、マップからルックアップを返します。

于 2011-05-25T14:59:35.400 に答える
2

私はショーン・パトリック・フロイドに、列挙型が進むべき道であることを指摘しますが、次のようなスキームを使用することで、コード イベントをより劇的に短縮できることを付け加えたいと思います。

enum Fruits {
   WATERMELON("watermelon fruit"),
   APPLE("apple fruit"); //...

   private final String gimme;

   private Fruits(String gimme) {
      this.gimme = gimme;
   }

   String gimmeFruit() { return this.gimme; }       
}

また、「grow」メソッドも怪しいです。次のようなものではないでしょうか

public static String grow(Fruits f) {
   return f.gimmeFruit();
}
于 2011-05-25T14:50:45.143 に答える
2

特定の文字列を解析する列挙型にメソッドを実装し、対応する列挙型定数を返すことがよくあります。私はいつもこのメソッドに名前を付けていparse(String)ます。

別の入力タイプで列挙型定数を解析するために、このメソッドをオーバーロードすることもあります。

その実装は常に同じです: すべての enum values() を反復処理し、1 つヒットすると戻ります。最後に、フォールスルーとしてリターンを行います - 多くの場合、特定の列挙型定数またはnull. ほとんどの場合、私は null を好みます。

public class FruitGrower {
    enum Fruit {
        WATERMELON("wtrmln") {
            @Override
            void grow() {
                //do watermelon growing stuff
            }
        },

        APPLE("ppl") {
            @Override
            void grow() {
                //do apple growing stuff
            }
        },

        PINEAPPLE("pnppl") {
            @Override
            void grow() {
                //do pineapple growing stuff
            }
        },

        ORANGE("rng") {
            @Override
            void grow() {
                //do orange growing stuff
            }
        },

        OTHER("") {
            @Override
            void grow() {
                // do other fruit growing stuff
            }
        };

        private String name;

        private Fruit(String name) {
            this.name = name;
        }

        abstract void grow();

        public static Fruit parse(String name) {
            for(Fruit f : values()) {
                if(f.name.equals(name)){
                    return f;
                }
            }

            return OTHER; //fallthrough value (null or specific enum constant like here.)
        }
    }


    public void growAFruit(String name) {
        Fruit.parse(name).grow();
    }
}

これが本当に必要ない場合はFruit.OTHER、削除してください。または、「その他の果物」はどのように成長しますか?oOnullその後、フォールスルー値としてメソッドに戻り、 を呼び出す前にparse(String)null チェックを行います。grow()growAFruit(String)

その場合、メソッドに@CheckForNullアノテーションを追加することをお勧めします。parse(String)

于 2011-06-20T16:37:49.013 に答える
2

パブリック API はまったく同じです。まだ同じ if-else ブロックがありますが、現在は列挙型になっています。だから、良くないと思います。複雑さが増すため、どちらかといえば悪いことです。

「果物を育てる」とはどういう意味ですか? 果物の栽培者が行うこと (土、植物の種など) について話しているのでしょうか、それとも果物自体が行うこと (発芽、発芽、開花など) について話しているのでしょうか? 元の例では、アクションは によって定義されてFruitGrowerいますが、変更では によって定義されていFruitます。サブクラス化を検討する場合、これは大きな違いになります。たとえば、MasterFruitGrower果物をよりよく育てるためにさまざまなプロセスを使用する人を定義したいと思うかもしれません。でgrow()操作を定義するとFruit、これを推論するのが難しくなります。

果物の栽培作業はどのくらい複雑ですか? if-else ブロックの行の長さが気になる場合は、果物を育てるメソッド ( growWatermelon()growOrange()、 ...) を個別に定義するか、FruitGrowingProcedure果物の種類ごとにサブクラスを実装してインターフェイスを定義し、それらを格納することをお勧めします。マップ内または の下に設定しFruitGrowerます。

于 2011-06-21T00:03:14.243 に答える
1

異なるフルーツの成長動作が異なる場合、if-elseや列挙型を使用せず、代わりにオブジェクト指向のデザインを使用します。if-else / enumスタイル(あなたの例では同等だと思います)の問題は、さまざまなオブジェクトタイプの動作を1つの場所に収集することです。新しいフルーツを追加する場合は、毎回if-else/enumを編集する必要があります。これは、開放閉鎖原則に違反します。

あなたの4つの果物が3つの行動(例えば、成長、熟成、腐敗)を持っていたかどうかを考えてください。各動作にif-else/enumがあり、それぞれに4つのフルーツ参照が含まれています。次に、5番目のフルーツを追加することを検討します。3つの異なるif-else/enumブロックを編集する必要があります。スイカの振る舞いはリンゴとは何の関係もありませんが、同じコードに含まれています。ここで、新しいフルーツの動作(アロマなど)を追加することを検討してください。同じ問題がある新しいif-else/enumを作成する必要があります。

ほとんどの場合、正しい解決策は、クラス(たとえば、フルーツごとに1つの実装クラスを持つFruitインターフェイス/ベースクラス)を使用し、すべてを1つの場所に収集するのではなく、各クラスに動作を配置することです。したがって、新しいフルーツを追加するときに変更されるコードは、新しいフルーツクラスが作成されることだけです。

既存のコードを取得して、私が話しているものに移行するために使用できる、よく知られたリファクタリングがあります。これは、条件付きをポリモーフィズムに置き換えると呼ばれます。

于 2011-06-22T18:15:49.577 に答える
1

まあ、列挙型コードは確かに長くなります。私が提案すること:

  • ロジックが単純で小さく、1 回しか使用されない場合は、文字列を使用します。
  • 定数を何度も使用する必要がある場合、追加または変更が可能な場合、または複雑なコードにそれらが混在しているため、Enum で明確にした方がよい場合は、Enum を使用します。
于 2011-06-22T14:47:26.710 に答える
1

あなたの質問に答えるために、あなたの特定の問題にはif-elseもenumも好ましいとは言えません。この問題に対処する良い方法は、カプセル化と抽象化を使用し、継承に「型」を処理させることです。

果物を育てる作業は、果物ごとに大きく異なります。各果物を独自のクラスにして、後でより多くの機能が必要になったときに柔軟性を高めることが賢明です。

良い例を次に示します。

// Abstract Base Class (Everything common across all fruits)
public abstract class Fruit {
    public abstract void grow();
}

// Concrete class, for each "type" of fruit
public class Apple extends Fruit {
    @override
    public void grow() {
        // Grow an apple
    }
}

public class Orange extends Fruit {
    @override
    public void grow() {
        // Grow an orange
    }
}

...

製品クラスが定義されると、「ify」型チェックなしで果物を育てるクラスを作成できます。

public class FruitGrower {
    public void growAFruit(Fruit fruit) {
        fruit.grow();
    }
}
于 2011-06-20T22:45:55.873 に答える