Java 具有使用不同参数的构造函数的简单工厂

Java Simple Factory with constructors using different parameters

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

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

有了这个设置,客户端代码就不必处理构建任何东西或决定是保存到数据库还是文件——它可以只对工厂返回的对象调用 save() 方法,如下所示:

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

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

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

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

就目前而言,您需要一种采用 Session 的方法和一种采用 String 的方法,您就完成了。不需要布尔值,不需要无用的参数。处理您的决策时没有任何麻烦。

这是否是个好主意……我很矛盾。你存的钱不多;还不如在每种类型中都有一个静态工厂,以便在代码中明确说明您正在创建什么类型。

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

有很多方法可以做到这一点——我不相信有一个 "clearly correct" 答案,它可能归结为意见。

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

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");
}

我认为违反的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);

这里正确的解决方案是结合 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 来自配置,当然测试可以例外。