java 如何避免Factory中的条件语句?

5jvtdoz2  于 2023-06-28  发布在  Java
关注(0)|答案(1)|浏览(124)

假设我们有interface Button{ void Click(); }及其实现:AndroidButtonIOSButton。有没有可能在不违反sOlid的情况下,基于string os = "ios"之类的输入创建适当的按钮?就像当一个新的‘os’类型出现时,我们不必改变现有的代码

mspsb9vt

mspsb9vt1#

打开/关闭原则并不意味着您不能更改工厂代码。它要求您只扩展,而不修改工厂的接口和行为。
然而,像这样实现Abstract Factory Pattern将最接近您的愿望:

public interface ButtonFactory {
    Button createButton();
}

public class IosButtonFactory implements ButtonFactory {
    public Button createButton() {
        return new IosButton();
    }
}

public class AndroidButtonFactory implements ButtonFactory {
    public Button createButton() {
        return new AndroidButton();
    }
}

现在。如果你必须提供一个新的按钮类型,你只需要像这样添加一个新的实现:

public class WindowsButtonFactory implements ButtonFactory {
    public Button createButton() {
        return new WindowsButton();
    }
}

这样,你的工厂就没有条件语句了。
考虑到必须有一个位置,其中一个条件决定使用与所提供的string os相关的具体实现。这可以在你的 Bootstrap 中,在那里你配置你的IoC容器,或者在你的main方法中,如这个非常简洁的sample所示。
这样,你的工厂就没有条件语句了,就像你最初的问题所要求的那样。

    • EDIT1**关于@LightSoul下面的第一条评论

工厂模式还有其他一些设计,但是,它不再是抽象工厂,而是简单工厂。
在这里,我们再次从接口开始:

public interface ButtonFactory {
    Button createButton(String token);
}

实现可能看起来像这样:

public class ButtonFactoryImpl implements ButtonFactory {
    private final Map<String, Supplier<Button>> factoryMethods = new HashMap<>();

    public ButtonFactoryImpl() {
        factoryMethods.put("ios", this::createIosButton);
        factoryMethods.put("android", this::createAndroidButton);
    }

    @Override
    public Button createButton(String token) {
        if (factoryMethods.containsKey(token)) {
            return factoryMethods.get(token).get();
        }

        throw new IllegalArgumentException("Token %s not configured".formatted(token);
    }

    private Button createIosButton() {
        return new IosButton();
    }

    private Button createAndroidButton() {
        return new AndroidButton();
    }
}

阅读this article以了解为什么token必须是原始数据类型。
这种设计符合OpenClosed原则,因为一旦引入了上面提到的WindowsButton,您只需扩展而无需修改代码。
如果你喜欢,并且不需要考虑速度和其他缺点,你可能想使用反射:
首先,创建一个新的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Token {
    public String value();
}

我们的工厂可能看起来像这样:

public class ButtonFactoryImpl implements ButtonFactory {

    @Override
    public Button createButton(String token) {
        for(Method method : getType().getDeclaredMethods()) {
            Annotation annotation = method.getDeclaredAnnotation(Token.class);
            if (annotation instanceof Token t && t.value().equals(token)) {
               method.setAccessible(true);
               Button button = (Button) method.invoke(this, null);
               method.setAccessible(false)
               return button;
            }
        }

        throw new IllegalArgumentException("Token %s not configured".formatted(token);
    }
    

    @Token("ios")
    private Button createIosButton() {
        return new IosButton();
    }

    @Token("android")
    private Button createAndroidButton() {
        return new AndroidButton();
    }
}

现在,如果您必须为@Token("windows")扩展一个新的私有方法,则不再需要调整任何分派代码,如switch/if或Map初始化。但是,如上所述,反射有一些严重的影响需要考虑。保重,祝你好运。
(免责声明:上面的代码未经测试,可能需要一些小的更改才能运行。为了便于阅读和理解,它不包含错误处理。)

相关问题