【问题标题】:using setter to inject service into Enum...Bad Practice?使用 setter 将服务注入 Enum...不好的做法?
【发布时间】:2014-01-27 02:23:46
【问题描述】:

这被认为是不好的做法吗?本质上,基于枚举,我想在接口中调用特定的方法。每个枚举都有自己的接口实现(WalkImpl、RunImpl、JogIMpl 等......),所有这些都基于 ActivityService 接口。我只是想知道这是将服务“注入”到枚举中的正确方法。我这样做是因为我无法自动连接服务。谢谢

@Component
public class HelloWorldImpl implements HelloWorld {

private enum MyEnum{

    WALK {
        @Override
         public void execute() {
            System.out.println("I am walking");
            activityService.doSomethingWithMe(this.name());
        }
    },
    RUN{
        @Override
        public void execute() {
            System.out.println("I am running");
        }
    },JOG{
        @Override
        public void execute() {
            System.out.println("I am jogging!");
        }
    }, SKIP{
        @Override
        public void execute() {
            System.out.println("I am skipping!");
        }
    };

    public abstract void execute();

    private static ActivityService activityService;

    public void setActivityService(ActivityService activityService) {
        this.activityService = activityService;
    }
}


@Autowired
ActivityService activityService;


 @Override
 public void executeMe(){
    MyEnum myEnum = MyEnum.WALK;
    myEnum.setActivityService(activityService);
    myEnum.execute();

  }
}

【问题讨论】:

  • 如果不了解您要完成的实际任务,很难准确地说出。我会说使用枚举来完成这类工作通常是使用了错误的工具,尤其是在使用 Spring 组件扫描时;我通常更喜欢将实现注入到持有者/调度程序中的命令对象模式。

标签: java spring enums


【解决方案1】:

不要使用这样的枚举。当我有更多时间时,我会解释,但大多数程序员都希望,甚至 Java 语言都希望枚举是幂等和不可变的。

枚举中的所有成员变量都应该是最终的,并且枚举不应产生副作用。这是因为枚举是函数式编程风格分派的一种形式(尽管很糟糕)。也就是说,枚举应该被视为符号而不是对象(即使它们是单例对象)。

除非你遵循上面的函数规则,否则不要使用枚举来解决单例模式。以下是我可能会纠正您给定代码的方法:

@Component
public class HelloWorldImpl implements HelloWorld {

    private enum MyEnum{
        //Notice the final here 
        private final String message;
        WALK ("I am walking"),
        RUN("I am running"),
        JOG("I am jogging!"),
        SKIP("I am skipping!");

        public MyEnum(String message) { this.message = message; }

        public String getMessage() { return this.message; }

    }        

    @Autowired
    ActivityService activityService;

    @Override
    public void executeMe() {
       MyEnum myEnum = MyEnum.WALK;
       _executeMe(myEnum);
    }

    void _executeMe(MyEnum m) {
        //switch or if on the enums you want to 
        //do stuff on with the activity service.
        System.out.println(m.getMessage());
        if (m == MyEnum.WALK)
           activityService.doSomethingWithMe(m.name());
    }
}

【讨论】:

  • 感谢您的快速解释
【解决方案2】:

枚举最适合用于在代码中必须区分的事物——业务逻辑。如果您将它们用于数据(例如您的示例),那对我来说没有任何意义。

另外,通过数据与代码,我不是在谈论简单地迭代它们,实际上你必须使用不同的枚举来拥有显着不同的代码,否则它们只是一个(坏的)数据初始化设备。

该类型数据的更好初始化可能是:

String[] init=new String[] {"WALK", "I am walking", "SKIP", "I am skipping", ...}
Map lookup=new HashMap();
for(int i=0;i+=2;i<init.length)
{
    lookup.put(init[i],init[i+1])
}

没有冗余,更简单,当该列表变得更复杂时,将其从代码中移至文本、属性、xml 或您喜欢的任何类型的数据是微不足道的。

如果这是你所追求的,你甚至可以将代码与这些相关联,方法是将“查找”和整个初始化包装到一个对象中(一个好主意)我会做一些看起来像这样的东西:

public class Motivate() 
{

    private static Map<String, Motivate> motivations;
    private String action;
    private String description;

    private Motivate(String action, String description)
    {
        this.action=action;
        this.description=description;
    }
    public void init()
    {
        if(motivations == null)
        {
            build motivations using all the stuff in the first example
        }
    }
}

如果您想要附加不同的代码(假设您的示例只是微不足道的,并且每个“模式”需要不同的代码),请添加一个包含“可运行”之类的接口的成员,并在构建它们时将其传递给构造函数。

那么你的代码不应该引用“RUN”或“WALK”,它只是绑定到例如用户击键或其他一些数据的数据。

【讨论】:

  • 我在 executeme 方法中使用了开关,但我读到你不应该在开关中使用枚举。这是我能想到的唯一选择。我认为在 executeme 方法中使用 switch 是一种更好的方法。这种模式正是我所追求的。根据模式不同,接口中会调用不同的方法。
  • 简单地使用一个开关(或一个像开关一样工作的 if/then/else 模式)是一种“不良代码气味”——更准确地说,它是一种 OO 反模式。解决方案通常涉及每个案例是不同的子类,或者只是使用将键(无论您在开关中比较什么)映射到某些代码(如 Runnable)的映射。虽然一开始这似乎并没有为您节省多少,但一旦您养成了不使用开关进行编码的习惯,许多其他的重构就成为可能,否则这些重构是不可能完成的。
猜你喜欢
  • 2023-01-04
  • 1970-01-01
  • 1970-01-01
  • 2019-03-24
  • 2011-12-30
  • 2011-05-18
  • 2017-06-27
  • 1970-01-01
  • 2018-07-01
相关资源
最近更新 更多