【问题标题】:Java Simple Factory with constructors using different parameters带有使用不同参数的构造函数的 Java 简单工厂
【发布时间】:2018-10-01 07:01:18
【问题描述】:

我有两种方法可以在我的应用程序中保存数据:保存到数据库和保存到文件。因为我不希望客户端代码处理对象的构造,所以我创建了一个类(据我所知)是带有工厂方法的简单工厂。代码如下:

public static DataPersister createDataPersister(Boolean saveToDb, Session session, String filename) {
    if (saveToDb) {
        return new DatabaseDataPersister(session);
    } else {
        return new FileDataPersister(filename);
    }
}

使用此设置,客户端代码不必处理任何构造或决定是否保存到 DB 或文件 - 它可以像这样在工厂返回的对象上调用 save() 方法:

DataPersister dataPersister = DataPersisterSimpleFactory.createDataPersister(this.savetoDb, this.session, this.filename);
dataPersister.save(this.data);

我的问题是 - 这个解决方案是否违反了 SOLID 原则?为了创建例如DatabaseDataPersister 客户端代码需要传递 filename 参数,而 DataPersister 的这个实现将不会使用它。我觉得它不适合类似于接口隔离原则的东西,但不完全是这样。

如果解决方案确实是代码异味 - 我该如何清理它?

【问题讨论】:

    标签: java oop design-patterns factory solid-principles


    【解决方案1】:

    已经传递了一堆与各种坚持者无关的东西。

    就目前而言,您需要一个采用Session 的方法和一个采用String 的方法,然后您就完成了。不需要布尔值,不需要无用的参数。这可以轻松地处理您的决策。

    不管这是否是个好主意……我很矛盾。你没有节省多少;最好在每种类型中都有一个静态工厂,以便在代码中明确您正在创建的类型。

    考虑当你添加一个新的持久化器时会发生什么,比如一个 REST 端点,它需要一个 URL(可以是一个字符串,也可以是一个实际的 URL)。您现在甚至需要更多无用的参数等。或者您可以从一开始就传入一个 URI,例如 file://http:// 并解决这个问题。

    有很多方法可以做到这一点——我不相信有一个“明显正确”的答案,它可能归结为意见。

    【讨论】:

    • 布尔变量saveToDb 被工厂用来决定创建哪个具体实现。编写两个单独的方法来创建两个单独的实现,就像在@OldCurmudgeon 回答中一样,虽然有助于摆脱一个变量,但我的目标是客户端代码不需要决定要实例化哪个DataPersister 实现。
    • @naru 你是在交易变量的方法——没有逻辑上的区别;客户仍然决定。可能的?当然总是传递所有变量:数据库不需要文件名,文件不需要会话。另一个答案只需要一个根据参数类型决定的函数,这也是我所说的。
    【解决方案2】:

    这是使用工厂模式的绝佳机会

    interface DataPersister {
        void persist(String s);
    }
    
    private class DatabasePersister implements DataPersister {
        final Session session;
    
        public DatabasePersister(Session session) {
            this.session = session;
        }
    
        @Override
        public void persist(String s) {
            System.out.println("Persist to database: " + s);
        }
    }
    
    private class FilePersister implements DataPersister {
        final String filename;
    
        public FilePersister(String filename) {
            this.filename = filename;
        }
    
        @Override
        public void persist(String s) {
            System.out.println("Persist to file: " + s);
        }
    }
    
    class PersisterFactory {
        public DataPersister createDatabasePersister(Session session) {
            return new DatabasePersister(session);
        }
    
        public DataPersister createFilePersister(String filename) {
            return new FilePersister(filename);
        }
    }
    
    public void test(String[] args) {
        DataPersister databasePersister = new PersisterFactory().createDatabasePersister(new Session());
        databasePersister.persist("Hello");
        DataPersister filePersister = new PersisterFactory().createFilePersister("Hello");
        filePersister.persist("Hello");
    }
    

    【讨论】:

    • 您的代码基本上看起来像我的,除了您处理在客户端代码中创建 DataPersister 的具体实现的决定(在您的示例中,test() 方法)并且我希望将决策推送到某个地方否则(在我的示例中,工厂根据saveToDb boolean 决定创建什么 - 这是基于命令行参数设置的方式,因此客户端也不会决定)。你能想出一个不需要客户端代码的解决方案吗?有可能,甚至是合理的吗?
    【解决方案3】:

    我认为违反的 SOLID 原则是 DIP。

    由于必须直接依赖静态工厂,您的客户端类在编译时依赖于实际实现 DatabaseDataPersisterFileDataPersister,而不仅仅是抽象 DataPersister

    要解决此问题,请向客户提供您希望他们使用的DataPersister。构造函数通常是这样做的好地方:

    public class ExampleClient {
    
        private final DataPersister dataPersister;
    
        public ExampleClient(DataPersister dataPersister) {
            this.dataPersister = dataPersister;
        }
    
        public void methodThatUsesSave(){
            dataPersister.save(data);
        }
    }
    

    此代码在没有具体实现的情况下编译,即它不依赖于它们。客户端也不需要知道filenamesession,所以它也解决了代码异味。

    我们可以在构建时决定给它具体的实现,这里我使用你现有的方法:

    DataPersister dataPersister = DataPersisterSimpleFactory.createDataPersister(this.savetoDb, this.session, this.filename);
    ExampleClient example = new ExampleClient(dataPersister);
    

    【讨论】:

    • 我觉得您的评论最有帮助。我可以根据命令行参数轻松注入正确的DataPresister。谢谢!
    【解决方案4】:

    这里正确的解决方案是将 Weston 的依赖注入和 OldCurmudgeon 的工厂模式结合起来。

    public class ExampleClient {
    
        private final DataPersister dataPersister;
    
        public ExampleClient(DataPersister dataPersister) {
            this.dataPersister = dataPersister;
        }
    
        public void methodThatUsesSave(){
            dataPersister.save(data);
        }
    }
    
    class PersisterFactory {
        public DataPersister createDatabasePersister(Session session) {
            return new DatabasePersister(session);
        }
    
        public DataPersister createFilePersister(String filename) {
            return new FilePersister(filename);
        }
    }
    

    上层代码:

    PersisterFactory = new PersisterFactory();
    DataPersister dataPersister;
    if (saveToDb)
        dataPersister = PersisterFactory.createDatabasePersister(new Session());
    else
        dataPersister = PersisterFactory.createFilePersister("Hello");
    ExampleClient example = new ExampleClient(dataPersister);
    

    通常dataPersister 来自 DI 容器,saveToDb 来自配置,当然测试也可以例外。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-19
      • 2014-11-03
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 2015-06-04
      相关资源
      最近更新 更多