另一个好处是它将相关常量组合在一起并给它们一个合适的家。另一种选择是在某个恰好使用它们的类中抛出一堆 public static final 字段,或者卡住常量接口。充满常量的接口甚至没有意义,因为如果这就是您所需要的,那么定义枚举就比编写接口容易得多。此外,在类或接口中,有可能意外地对多个常量使用相同的值。
You've already got good answers about improving your use of Enums. As for why they're better than string constants:
I think the biggest benefit is compile-time error checking. If I were to call growAFruit("watermelon"), the compiler would have no idea anything was wrong. And since I've spelled it correctly, it's not going to stand out as a mistake when you're viewing the code. But if I were to WATERMELEN.growAFruit(), the compiler can tell you immediately that I've misspelled it.
You also get to define growAFruit as a bunch of simple, easy-to-read methods, instead of a big block of if-then-elses. This becomes even more apparent when you have a few dozen fruits, or when you start adding harvestAFruit(), packageAFruit(), sellAFruit(), etc. Without enums you'd be copying your big if-else block all over, and if you forgot to add a case it would fall through to the default case or do nothing, while with enums the compiler can tell you that the method hasn't been implemented.
Even more compiler-checking goodness: If you also have a growVegetable method and the related string constants, there's nothing stopping you from calling growVegetable("pineapple") or growFruit("peas"). If you've got a "tomato" constant, the only way to know if you consider it a fruit or a vegetable is to read the source code of the relevant methods. Once again, with enums the compiler can tell you right away if you've done something wrong.
Another benefit is that it groups related constants together and gives them a proper home. The alternative being a bunch of public static final fields thrown in some class that happens to use them, or stuck a interface of constants. The interface full of constants doesn't even make sense because if that's all you need defining the enum is much easier than writing out the interface. Also, in classes or interfaces there is the possibility to accidentally use the same value for more than one constant.
They're also iterable. To get all the values of an enum you can just call Fruit.values(), whereas with constants you'd have to create and populate your own array. Or if just using literals as in your example, there is no authoritive list of valid values.
Bonus Round: IDE Support
With an enum, you can use your IDE's auto-completion feature and automated refactorings
You can use things like "Find References" in Eclipse with enum values, while you'd have to do a plain text search to find string literals, which will usually also return a lot of false-positives (event if you use static final constants, someone could have used the string literal somewhere)
The main reason not to use an enum would be if you don't know all the possible values at compile time (i.e. you need to add more values while the program is running). In that case you might want to define them as a class heirarchy. Also, don't throw a bunch of unrelated constants into an enum and call it a day. There should be some sort of common thread connecting the values. You can always make multiple enums if that's more appropriate.
But that's pretty ugly. I guess I'd to something like this:
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();
};
Also, if all your enum methods do is return a value, you should refactor your design so that you initialize the values in a constructor and return them in a method defined in the Enum class, not the individual items:
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; }
}
And Fruits should be renamed to Fruit: an apple is a fruit, not a fruits.
If you really need to keep your string types, then define a method (in the enum class itself, for example) returning the Fruit associated to each type. But most of the code should use Fruit instead of String.
static void grow(String type) {
Fruit f = map.get(type);
if (f == null) {
OTHER.growFruit();
}
else {
f.growFruit();
}
}
当然,您真的需要这里的字符串吗?你不应该总是使用枚举对象吗?
I think you want a Map<String, Fruit> (or <String, FruitGrower>).
This map could be filled automatically by the enum's constructors, or by a static initializer. (It could even map multiple names on the same enum constant, if some fruits have alias names.)
Your grow method then looks like this:
static void grow(String type) {
Fruit f = map.get(type);
if (f == null) {
OTHER.growFruit();
}
else {
f.growFruit();
}
}
Of course, do you really need the string here? Shouldn't you always use the enum object?
// 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...
}
}
I'm not sure I'd use Enums here. I may be missing something here (?), but I think my solution would look something like this, with separate classes for each type of fruit, all based on one genera fruit-class:
// 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...
}
}
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,但如果您认为有必要,就使用它。
I think you're on the right track. I'd go and throw in some extra bytes for a HashMap to get rid of the string switching block. This gives you both, cleaner looks, less code and most likely a little extra performance.
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();
}
And that's how you use it:
Fruit.getByCode("wtrmln").grow();
Simple, no need for a FruitGrower, but go for it if you think it's necessary.
public static String grow(Fruits f) {
return f.gimmeFruit();
}
I second Sean Patrick Floyd on that enums are the way to go, but would like to add that you can shorten your code event more dramatically by using a scheme like this:
I often implement a method in enums parsing a given String and gives back the corresponding enum constant. I always name this method parse(String).
Sometimes I overload this method in order to parse enum constant by another given input type, too.
It's implementation is always the same: Iterate over all enum values() and return when you hit one. Finally do a return as fallthrough - often a specific enum constant or null. In most cases I prefer 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();
}
}
If you do not really need this Fruit.OTHER then delete it. Or how a "Other-fruit" grows? oO Return null then in parse(String) method as fallthrough value and do null-check before calling grow() in growAFruit(String).
It is a good idea to add @CheckForNull annotation to the parse(String) method then.
公共 API 完全相同。你仍然有相同的 if-else 块,它现在只是在枚举中。所以我认为这也好不到哪儿去。如果说有什么不同的话,那就是由于复杂性的增加,情况变得更糟。
“种植水果”是什么意思?你是在谈论水果种植者所做的事情(耕种土壤、植物种子等),还是水果本身所做的事情(发芽、发芽、开花等)?在原始示例中,操作由 FruitGrower 定义,但在您的修改中,它们由 Fruit 定义。当您考虑子类化时,这会产生很大的差异。例如,我可能想定义一个 MasterFruitGrower,他使用不同的流程来更好地种植水果。在 Fruit 中定义 grow() 操作使得这更难以推理。
The public API is exactly the same. You still have the same if-else block, it's just in the enum now. So I think it's no better. If anything it's worse, due to added complexity.
What does it mean to 'do fruit growing stuff'? Are you talking about stuff the fruit grower does (till the soil, plant seeds, etc), or stuff the fruit itself does (germinate, sprout, blossom, etc)? In the original example, the actions are defined by the FruitGrower, but in your modifications they are defined by the Fruit. This makes a big difference when you consider subclassing. For example I might want to define a MasterFruitGrower who uses different processes to grow fruit better. Having the grow() operation defined in Fruit makes this harder to reason about.
How complex are the fruit growing operations? If you're concerned about the line length of the if-else block, I think a better idea is to define separate fruit growing methods (growWatermelon(), growOrange(), ...) or define a FruitGrowingProcedure interface, implementing subclasses for each fruit type, and store them in a map or set under FruitGrower.
The answer to your question is using enums, or better yet, factories and polymorphism, as mentioned above. However, if you want to get rid of switches (which is what your if-else statement is really doing) completely , a good way, if not the best, to do it is using inversion of control. Thus, I suggest using spring, as follows:
public interface Fruit {
public void grow();
}
public class Watermelon implements Fruit {
@Override
public void grow()
{
//Add Business Logic Here
}
}
Now, create the fruit locator interface, as follows:
public interface FruitLocator {
Fruit getFruit(String type);
}
Let main class have a reference to a FruitLocator object, a setter for it, and just call the getFruit command:
private FruitLocator fruitLocator;
public void setFruitLocator (FruitLocator fruitLocator)
{
this.fruitLocator = fruitLocator;
}
public void growAFruit(String type) {
fruitLocator.getFruit(type).grow();
}
Now comes the tricky part. Define your FruitGrower class as a spring bean, as well as your FruitLocator and Fruits:
Only thing left to do is create a fruits.properties file in your classpath and add the type-bean mapping, as follows:
wtrmln=waterMelon
Now, you can add as many fruits as you wish, you only need to create a new fruit class, define it as a bean and add a mapping to your properties file. Much much more scalable than hunting for if-else logic in the code.
I know this seems complex at first, but I think the topic would be incomplete without mentioning Inversion of Control.
// 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();
}
}
To answer your question, I would say neither if-else or enums are preferable to your specific problem. A good way to approach this problem would be to use encapsulation and abstraction and let inheritance handle the "type" for you.
The task of growing a fruit varies greatly between each fruit. It would be wise to make each fruit its own class allowing greater flexibility later on when more functionality may be needed.
Heres a good example:
// 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
}
}
...
Once the product classes have been defined we can create a class that grows fruit without any "ify" type checking.
public class FruitGrower {
public void growAFruit(Fruit fruit) {
fruit.grow();
}
}
Well, the enums code is surely longer. What I suggest:
Use Strings when the logic is simple, small, and will be used 1 time.
Use Enums when you have to use the constants many times, addition or modifications are possible, or when they are mixed in a complex code so it is better to clarify them with the Enum.
If different Fruits have different grow behaviors, I would not use an if-else or an enum, and instead use an object-oriented design. The problem with the if-else/enum style (I consider them equivalent in your example) is that they collect behaviors of different object types into one location. If you add a new fruit, you have to edit your if-else/enum each time. This violates the open-closed principle.
Consider if your 4 fruits had 3 behaviors (e.g. grow, ripen, rot). You would have an if-else/enum for each behavior, each containing 4 fruit references. Now consider adding a 5th fruit, you must edit 3 different if-else/enum blocks. The behavior of a watermelon has nothing to do with an apple, but they are in the same piece of code. Now consider adding a new fruit behavior (e.g. aroma) - you must create a new if-else/enum, which has the same issues.
I believe the right solution in most cases is to use classes (e.g. a Fruit interface/base class with one implementation class per fruit) and put the behavior in each class instead of collecting it all in one place. So when you add a new fruit, the only code being changed is that a new fruit class is written.
There is a well-known refactoring you can use to take your existing code and migrate it to what I am talking about. It's called Replace Conditional with Polymorphism.
发布评论
评论(14)
关于改进枚举的使用,您已经得到了很好的答案。至于为什么它们比字符串常量更好:
我认为最大的好处是编译时错误检查。如果我调用
growAFruit("watermelon")
,编译器不会知道有什么问题。由于我的拼写是正确的,因此当您查看代码时,它不会被认为是错误。但如果我要WATERMELEN.growAFruit()
,编译器会立即告诉您我拼写错误。您还可以将
growAFruit
定义为一堆简单、易于阅读的方法,而不是一大块if
-then
-其他
。当您有几十个水果时,或者当您开始添加harvestAFruit()
、packageAFruit()
、sellAFruit()
时,这种情况会变得更加明显等等。如果没有枚举,您将复制大的 if-else 块,如果您忘记添加一个案例,它将落入默认案例或不执行任何操作,而使用枚举,编译器可以告诉您该方法尚未实施。更多编译器检查优点:如果您还有
growVegetable
方法和相关字符串常量,则没有什么可以阻止您调用growVegetable("pineapple")
或种植水果(“豌豆”)
。如果您有一个“tomato”常量,那么了解您是否将其视为水果或蔬菜的唯一方法是阅读相关方法的源代码。再一次,使用枚举,如果您做错了什么,编译器可以立即告诉您。另一个好处是它将相关常量组合在一起并给它们一个合适的家。另一种选择是在某个恰好使用它们的类中抛出一堆
public static final
字段,或者卡住常量接口。充满常量的接口甚至没有意义,因为如果这就是您所需要的,那么定义枚举就比编写接口容易得多。此外,在类或接口中,有可能意外地对多个常量使用相同的值。它们也是可迭代的。要获取枚举的所有值,您只需调用
Fruit.values()
即可,而对于常量,您必须创建并填充自己的数组。或者,如果仅像示例中那样使用文字,则没有有效值的权威列表。奖励回合:IDE 支持
不使用枚举的主要原因是:您在编译时不知道所有可能的值(即您需要在程序运行时添加更多值)。在这种情况下,您可能希望将它们定义为类层次结构。另外,不要将一堆不相关的常量放入枚举中然后就到此为止。应该有某种共同的线索连接这些值。如果更合适的话,您始终可以创建多个枚举。
You've already got good answers about improving your use of Enums. As for why they're better than string constants:
I think the biggest benefit is compile-time error checking. If I were to call
growAFruit("watermelon")
, the compiler would have no idea anything was wrong. And since I've spelled it correctly, it's not going to stand out as a mistake when you're viewing the code. But if I were toWATERMELEN.growAFruit()
, the compiler can tell you immediately that I've misspelled it.You also get to define
growAFruit
as a bunch of simple, easy-to-read methods, instead of a big block ofif
-then
-else
s. This becomes even more apparent when you have a few dozen fruits, or when you start addingharvestAFruit()
,packageAFruit()
,sellAFruit()
, etc. Without enums you'd be copying your big if-else block all over, and if you forgot to add a case it would fall through to the default case or do nothing, while with enums the compiler can tell you that the method hasn't been implemented.Even more compiler-checking goodness: If you also have a
growVegetable
method and the related string constants, there's nothing stopping you from callinggrowVegetable("pineapple")
orgrowFruit("peas")
. If you've got a"tomato"
constant, the only way to know if you consider it a fruit or a vegetable is to read the source code of the relevant methods. Once again, with enums the compiler can tell you right away if you've done something wrong.Another benefit is that it groups related constants together and gives them a proper home. The alternative being a bunch of
public static final
fields thrown in some class that happens to use them, or stuck a interface of constants. The interface full of constants doesn't even make sense because if that's all you need defining the enum is much easier than writing out the interface. Also, in classes or interfaces there is the possibility to accidentally use the same value for more than one constant.They're also iterable. To get all the values of an enum you can just call
Fruit.values()
, whereas with constants you'd have to create and populate your own array. Or if just using literals as in your example, there is no authoritive list of valid values.Bonus Round: IDE Support
The main reason not to use an enum would be if you don't know all the possible values at compile time (i.e. you need to add more values while the program is running). In that case you might want to define them as a class heirarchy. Also, don't throw a bunch of unrelated constants into an enum and call it a day. There should be some sort of common thread connecting the values. You can always make multiple enums if that's more appropriate.
枚举是可行的方法,但是您可以像这样显着改进代码:
哦,您需要一个默认情况,这使得它有点困难。当然你可以这样做:
但这非常难看。我想我会这样做:
另外,如果所有枚举方法所做的都是返回一个值,那么您应该重构您的设计,以便在构造函数中初始化值并在 Enum 类中定义的方法中返回它们,而不是个别项目:
Enums are the way to go, but you can dramatically improve your code like this:
Oh, you need a default case, that makes it a bit tougher. Of course you can do this:
But that's pretty ugly. I guess I'd to something like this:
Also, if all your enum methods do is return a value, you should refactor your design so that you initialize the values in a constructor and return them in a method defined in the Enum class, not the individual items:
为了更干净,您只做了一半的更改。生长方法应该像这样改变:
并且
Fruits
应该重命名为Fruit
:苹果是一个水果,而不是一个水果。如果您确实需要保留字符串类型,请定义一个方法(例如,在枚举类本身中)返回与每种类型关联的 Fruit。但大多数代码应该使用 Fruit 而不是 String。
You have only made half of the changes to be cleaner. The grow method should be changed like this:
And
Fruits
should be renamed toFruit
: an apple is a fruit, not a fruits.If you really need to keep your string types, then define a method (in the enum class itself, for example) returning the Fruit associated to each type. But most of the code should use Fruit instead of String.
我认为您需要一个
Map
(或
)。该映射可以由枚举的构造函数或静态初始值设定项自动填充。 (如果某些水果有别名,它甚至可以将多个名称映射到同一个枚举常量上。)
您的
grow
方法如下所示:当然,您真的需要这里的字符串吗?你不应该总是使用枚举对象吗?
I think you want a
Map<String, Fruit>
(or<String, FruitGrower>
).This map could be filled automatically by the enum's constructors, or by a static initializer. (It could even map multiple names on the same enum constant, if some fruits have alias names.)
Your
grow
method then looks like this:Of course, do you really need the string here? Shouldn't you always use the enum object?
我不确定我会在这里使用枚举。我可能在这里遗漏了一些东西(?),但我认为我的解决方案看起来像这样,每种水果都有单独的类,所有这些都基于一个属水果类:
I'm not sure I'd use Enums here. I may be missing something here (?), but I think my solution would look something like this, with separate classes for each type of fruit, all based on one genera fruit-class:
我认为你走在正确的道路上。我会为 HashMap 添加一些额外的字节,以摆脱字符串切换块。这为您提供了更清晰的外观、更少的代码,并且很可能还获得了一些额外的性能。
这就是您使用它的方式:
简单,不需要 FruitGrower,但如果您认为有必要,就使用它。
I think you're on the right track. I'd go and throw in some extra bytes for a HashMap to get rid of the string switching block. This gives you both, cleaner looks, less code and most likely a little extra performance.
And that's how you use it:
Simple, no need for a FruitGrower, but go for it if you think it's necessary.
我赞同肖恩·帕特里克·弗洛伊德(Sean Patrick Floyd)的说法,枚举是可行的方法,但想补充一点,您可以通过使用这样的方案来更显着地缩短代码事件:
此外,“增长”方法是可疑的。难道不应该是这样的吗
I second Sean Patrick Floyd on that enums are the way to go, but would like to add that you can shorten your code event more dramatically by using a scheme like this:
Also, the "grow" method is suspicious. Shouldn't it be something like
您还可以通过使用变量来存储 gimmeFruit 值并使用构造函数进行初始化来改进它。
(我实际上还没有编译这个,所以可能存在一些语法错误)
编辑:
如果 Growth 方法的类型不是相同的字符串,则使用 Map 定义类型与 Enum 的匹配,并从映射返回查找。
You can also improve it by using a variable to store the gimmeFruit value and inititialize with the constructor.
(I haven't actually compiled this so there may be some syntax errors)
EDIT:
If the type for the grow method is not the same string, then use a Map to define the matches of type to Enum and return the lookup from the map.
我经常在枚举中实现一个方法来解析给定的字符串并返回相应的枚举常量。我总是将此方法命名为
parse(String)
。有时我也会重载此方法,以便通过另一个给定的输入类型解析枚举常量。
它的实现总是相同的:
迭代所有枚举值()并在遇到一个时返回。最后返回作为失败 - 通常是特定的枚举常量或
null
。大多数情况下我更喜欢 null。如果您确实不需要此
Fruit.OTHER
,请将其删除。或者“其他水果”如何生长? oO然后在
parse(String)
方法中返回 null 作为fallthrough 值,并在调用growAFruit(String) 中的
。grow()
之前进行 null 检查)最好在
parse(String)
方法中添加@CheckForNull
注释。I often implement a method in enums parsing a given String and gives back the corresponding enum constant. I always name this method
parse(String)
.Sometimes I overload this method in order to parse enum constant by another given input type, too.
It's implementation is always the same:
Iterate over all enum values() and return when you hit one. Finally do a return as fallthrough - often a specific enum constant or
null
. In most cases I prefer null.If you do not really need this
Fruit.OTHER
then delete it. Or how a "Other-fruit" grows? oOReturn
null
then inparse(String)
method as fallthrough value and do null-check before callinggrow()
ingrowAFruit(String)
.It is a good idea to add
@CheckForNull
annotation to theparse(String)
method then.公共 API 完全相同。你仍然有相同的 if-else 块,它现在只是在枚举中。所以我认为这也好不到哪儿去。如果说有什么不同的话,那就是由于复杂性的增加,情况变得更糟。
“种植水果”是什么意思?你是在谈论水果种植者所做的事情(耕种土壤、植物种子等),还是水果本身所做的事情(发芽、发芽、开花等)?在原始示例中,操作由
FruitGrower
定义,但在您的修改中,它们由Fruit
定义。当您考虑子类化时,这会产生很大的差异。例如,我可能想定义一个MasterFruitGrower
,他使用不同的流程来更好地种植水果。在Fruit
中定义grow()
操作使得这更难以推理。水果种植作业有多复杂?如果您担心 if-else 块的行长度,我认为更好的主意是定义单独的水果种植方法(
growWatermelon()
、growOrange()
,...)或定义一个FruitGrowingProcedure
接口,为每种水果类型实现子类,并将它们存储在FruitGrower
下的映射或集合中。The public API is exactly the same. You still have the same if-else block, it's just in the enum now. So I think it's no better. If anything it's worse, due to added complexity.
What does it mean to 'do fruit growing stuff'? Are you talking about stuff the fruit grower does (till the soil, plant seeds, etc), or stuff the fruit itself does (germinate, sprout, blossom, etc)? In the original example, the actions are defined by the
FruitGrower
, but in your modifications they are defined by theFruit
. This makes a big difference when you consider subclassing. For example I might want to define aMasterFruitGrower
who uses different processes to grow fruit better. Having thegrow()
operation defined inFruit
makes this harder to reason about.How complex are the fruit growing operations? If you're concerned about the line length of the if-else block, I think a better idea is to define separate fruit growing methods (
growWatermelon()
,growOrange()
, ...) or define aFruitGrowingProcedure
interface, implementing subclasses for each fruit type, and store them in a map or set underFruitGrower
.您问题的答案是使用枚举,或者更好的是使用工厂和多态性,如上所述。但是,如果您想完全摆脱开关(这就是 if-else 语句真正要做的事情),一个好的方法(如果不是最好的方法)就是使用控制反转。因此,我建议使用 spring,如下所示:
现在,创建水果定位器接口,如下所示:
让主类拥有对 FruitLocator 对象的引用和它的 setter,然后调用 getFruit 命令:
现在到了棘手的部分。将 FruitGrower 类定义为 spring bean,以及 FruitLocator 和 Fruits:
剩下要做的就是在类路径中创建一个fruits.properties 文件并添加类型-bean 映射,如下所示:
现在,您可以添加任意数量的水果如你所愿,你只需要创建一个新的水果类,将其定义为一个bean并将映射添加到你的属性文件中。比在代码中寻找 if-else 逻辑更具可扩展性。
我知道这乍一看似乎很复杂,但我认为如果不提及控制反转,这个主题就不完整。
The answer to your question is using enums, or better yet, factories and polymorphism, as mentioned above. However, if you want to get rid of switches (which is what your if-else statement is really doing) completely , a good way, if not the best, to do it is using inversion of control. Thus, I suggest using spring, as follows:
Now, create the fruit locator interface, as follows:
Let main class have a reference to a FruitLocator object, a setter for it, and just call the getFruit command:
Now comes the tricky part. Define your FruitGrower class as a spring bean, as well as your FruitLocator and Fruits:
Only thing left to do is create a fruits.properties file in your classpath and add the type-bean mapping, as follows:
Now, you can add as many fruits as you wish, you only need to create a new fruit class, define it as a bean and add a mapping to your properties file. Much much more scalable than hunting for if-else logic in the code.
I know this seems complex at first, but I think the topic would be incomplete without mentioning Inversion of Control.
为了回答你的问题,我想说 if-else 或枚举都不适合你的具体问题。解决这个问题的一个好方法是使用封装和抽象,并让继承为您处理“类型”。
每种水果的种植任务差异很大。明智的做法是让每种水果都有自己的类,以便以后可能需要更多功能时具有更大的灵活性。
这是一个很好的例子:
一旦定义了产品类,我们就可以创建一个种植水果的类,而无需任何“ify”类型检查。
To answer your question, I would say neither if-else or enums are preferable to your specific problem. A good way to approach this problem would be to use encapsulation and abstraction and let inheritance handle the "type" for you.
The task of growing a fruit varies greatly between each fruit. It would be wise to make each fruit its own class allowing greater flexibility later on when more functionality may be needed.
Heres a good example:
Once the product classes have been defined we can create a class that grows fruit without any "ify" type checking.
嗯,枚举代码肯定更长。我的建议是:
Well, the enums code is surely longer. What I suggest:
如果不同的水果有不同的生长行为,我不会使用 if-else 或枚举,而是使用面向对象的设计。 if-else/enum 风格的问题(我认为它们在您的示例中是等效的)是它们将不同对象类型的行为收集到一个位置。如果添加新水果,则每次都必须编辑 if-else/enum。这违反了开闭原则。
考虑一下您的 4 个水果是否有 3 种行为(例如生长、成熟、腐烂)。每个行为都有一个 if-else/枚举,每个行为包含 4 个水果引用。现在考虑添加第五个水果,您必须编辑 3 个不同的 if-else/enum 块。西瓜的行为与苹果无关,但它们在同一段代码中。现在考虑添加新的水果行为(例如香气) - 您必须创建一个新的 if-else/enum,它具有相同的问题。
我相信在大多数情况下正确的解决方案是使用类(例如,每个水果有一个实现类的 Fruit 接口/基类)并将行为放入每个类中,而不是将其全部收集到一个地方。因此,当您添加新水果时,唯一更改的代码就是编写新的水果类。
您可以使用一种众所周知的重构来获取现有代码并将其迁移到我正在讨论的内容。它称为用多态性替换条件。
If different Fruits have different grow behaviors, I would not use an if-else or an enum, and instead use an object-oriented design. The problem with the if-else/enum style (I consider them equivalent in your example) is that they collect behaviors of different object types into one location. If you add a new fruit, you have to edit your if-else/enum each time. This violates the open-closed principle.
Consider if your 4 fruits had 3 behaviors (e.g. grow, ripen, rot). You would have an if-else/enum for each behavior, each containing 4 fruit references. Now consider adding a 5th fruit, you must edit 3 different if-else/enum blocks. The behavior of a watermelon has nothing to do with an apple, but they are in the same piece of code. Now consider adding a new fruit behavior (e.g. aroma) - you must create a new if-else/enum, which has the same issues.
I believe the right solution in most cases is to use classes (e.g. a Fruit interface/base class with one implementation class per fruit) and put the behavior in each class instead of collecting it all in one place. So when you add a new fruit, the only code being changed is that a new fruit class is written.
There is a well-known refactoring you can use to take your existing code and migrate it to what I am talking about. It's called Replace Conditional with Polymorphism.