Java中不使用生成器模式的不可变字段

Java中不使用生成器模式的不可变字段,java,immutability,Java,Immutability,我试图通过创建一个接口及其实现来创建不可变的配置,该接口及其实现是包私有的。只有接口向客户端公开;实现是隐藏的,不能在包外部调用接口具有访问器和变异器以及一个实例化其实现并返回自身的静态方法。每当调用mutator时,就会创建一个新实例,传递构造函数中的所有字段,以避免更改第一个对象的原始值 这是我的密码: package com.example.api; public interface Config { static Config newConfig() { ret

我试图通过创建一个接口及其实现来创建
不可变的
配置,该接口及其实现是
包私有的
。只有
接口
向客户端公开;实现是隐藏的,不能在包外部调用<代码>接口具有
访问器
变异器
以及一个实例化其实现并返回自身的静态方法。每当调用
mutator
时,就会创建一个新实例,传递构造函数中的所有字段,以避免更改第一个对象的原始值

这是我的密码:

package com.example.api;

public interface Config {
    static Config newConfig() {
        return new ConfigImpl();
    }

    String host();
    Config host(String host);
    int port();
    Config port(int port);
    String database();
    Config database(String database);
    String user();
    Config user(String user);
    String password();
    Config password(String password);
}

package com.example.api;

class ConfigImpl implements Config {
    private final String host;
    private final int port;
    private final String database;
    private final String user;
    private final String password;

    public ConfigImpl() {
        this(null, -1, null, null, null);
    }

    public ConfigImpl(String host, int port, String database, String user, String password) {
        this.host = host;
        this.port = port;
        this.database = database;
        this.user = user;
        this.password = password;
    }

    @Override
    public String host() {
        return host;
    }

    @Override
    public Config host(String host) {
        return new ConfigImpl(host, port, database, user, password);
    }

    @Override
    public int port() {
        return port;
    }

    @Override
    public Config port(int port) {
        return new ConfigImpl(host, port, database, user, password);
    }

    @Override
    public String database() {
        return database;
    }

    @Override
    public Config database(String database) {
        return new ConfigImpl(host, port, database, user, password);
    }

    @Override
    public String user() {
        return user;
    }

    @Override
    public Config user(String user) {
        return new ConfigImpl(host, port, database, user, password);
    }

    @Override
    public String password() {
        return password;
    }

    @Override
    public Config password(String password) {
        return new ConfigImpl(host, port, database, user, password);
    }

}
使用API的示例程序:

import com.example.api.Config;

public class App {
    public static void main(String[] args) {
        Config config = Config.newConfig()
            .host("localhost")
            .port(7000)
            .database("mydb")
            .user("admin")
            .password("pwd");

        config.database("mydb2"); // won't change 'mydb'
        Config config2 = config.database("mydb2"); // needs to be assigned to new instance

        System.out.println(config.host() + "|" + config.port() + "|" + config.database() + "|" + config.user() + "|" + config.password());
        System.out.println(config2.host() + "|" + config2.port() + "|" + config2.database() + "|" + config2.user() + "|" + config2.password());
    }
}
这是预期的工作:

localhost|7000|mydb|admin|pwd
localhost|7000|mydb2|admin|pwd
我关心的是,这是一个好的设计吗?因为每个mutator/setter都创建了一个news实例,所以它会影响内存和性能吗

如果我当前的设计没有问题,我更喜欢这个而不是构建器模式


谢谢。

生成器可能应该有一个单独的
build()
方法来执行最终的一组操作,例如,对多个字段进行验证,而这些字段不能仅用方法签名来处理。感觉更干净:

Config config = Config.newConfig()
            .host("localhost")
            .build();
关于您的设计,唯一值得关注的是:

  • 要使这项工作正常进行,必须手工编写的样板代码的数量。也许它最终是值得的,但是如果你打算为多个类型实现这一点,你应该考虑一些自动生成代码的方法,例如:

  • 由于您没有使用常规的
    getXXX()
    setXXX()
    方法,一些依赖属性方法表示法的框架将无法处理您的类。可能与你的情况完全无关


  • 构建器可能应该有一个单独的
    build()
    方法来执行最后一组操作,例如,对多个字段进行验证,而这些字段不能仅用方法签名来解决。感觉更干净:

    Config config = Config.newConfig()
                .host("localhost")
                .build();
    
    关于您的设计,唯一值得关注的是:

  • 要使这项工作正常进行,必须手工编写的样板代码的数量。也许它最终是值得的,但是如果你打算为多个类型实现这一点,你应该考虑一些自动生成代码的方法,例如:

  • 由于您没有使用常规的
    getXXX()
    setXXX()
    方法,一些依赖属性方法表示法的框架将无法处理您的类。可能与你的情况完全无关


  • 如果将其用于数据库配置,则性能实际上是无关紧要的:与打开数据库相比,创建这几个对象的成本很小。根据个人喜好,我会调用使用*返回新实例
    ,或其他方法,为了更清楚地说明返回了一个新实例。为什么在调用
    访问器时要创建一个新实例?当调用
    突变子时,这样做不是更合适吗?@Andreas My bad。它应该是
    mutator
    。我已经对它进行了更新。@AndyTurner最初我是对
    使用
    ,但后来删除了它。IDK但是当我看到Spark框架的
    request
    response
    方法时,它们看起来更简洁,更接近访问器,参数就是区别。无论如何,这只是偏好的问题。如果您将其用于数据库配置,那么性能实际上是无关紧要的:与打开数据库相比,创建这几个对象的成本很小。根据个人偏好,我会调用使用*
    返回新实例的方法,或者其他方法,为了更清楚地说明返回了一个新实例。为什么在调用
    访问器时要创建一个新实例?当调用
    突变子时,这样做不是更合适吗?@Andreas My bad。它应该是
    mutator
    。我已经对它进行了更新。@AndyTurner最初我是对
    使用
    ,但后来删除了它。IDK但是当我看到Spark框架的
    request
    response
    方法时,它们看起来更简洁,更接近访问器,参数就是区别。不管怎样,这只是偏好的问题。谢谢你的回复。我从来都不是像
    Lombok
    这样的自动生成库的粉丝。我更喜欢在
    Lombok
    特别适合的地方编写样板。我宁愿等待
    记录
    ,它们将进入
    Java14
    。是的。我知道如果不使用常规的
    getter
    setter
    方法,可能会导致其他框架无法工作,如果它们依赖于它的话。现在,是的,它们与我的情况完全无关。就验证而言,我将考虑
    builder模式中的
    build
    。感谢您的回复。我从来都不是像
    Lombok
    这样的自动生成库的粉丝。我更喜欢在
    Lombok
    特别适合的地方编写样板。我宁愿等待
    记录
    ,它们将进入
    Java14
    。是的。我知道如果不使用常规的
    getter
    setter
    方法,可能会导致其他框架无法工作,如果它们依赖于它的话。现在,是的,它们与我的情况完全无关。就验证而言,我将考虑
    builder模式中的
    build