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



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

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的这种实现将没有任何用处。我觉得它与类似于接口隔离原则的东西不符,但并不完全如此。

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

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

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

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

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

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

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

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

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

正确的解决方案是将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来自配置,但当然测试也可能是一个例外。

相关内容

最新更新