# 如何封装client/tool

## 单例

工具类，模板类，可以确定只用生成一个实例的，考虑这种设计模式，善用例模式。

在类中构造方法善用重载

### Demo

```
public class ZooKeeperTemplate implements AutoCloseable {

    private static final Logger logger = LoggerFactory.getLogger(ZooKeeperTemplate.class);

    /** zookeeper session timeout: 30 seconds */
    private static final int DEFAULT_SESSION_TIMEOUT_MILLISECONDS = 30_000;
    /** zookeeper connect timeout: 60 seconds */
    private static final int DEFAULT_CONNECT_TIMEOUT_MILLISECONDS = 60_000;
    /** znode path separator */
    private static final String PATH_SEPARATOR = "/";

    public static ZooKeeperTemplate getInstance(String zooKeeperHost) {
        return getInstance(zooKeeperHost, null, null);
    }

    public static ZooKeeperTemplate getInstance(String zooKeeperHost, Integer sessionTimeoutMilliseconds, Integer connectTimeoutMilliseconds) {
        return new ZooKeeperTemplate(zooKeeperHost, sessionTimeoutMilliseconds, connectTimeoutMilliseconds);
    }

    private final String zooKeeperHost;
    private final Integer sessionTimeoutMilliseconds;
    private final Integer connectTimeoutMilliseconds;

    private final ZooKeeper zoo;

    private ZooKeeperTemplate(String zooKeeperHost, Integer sessionTimeoutMilliseconds, Integer connectTimeoutMilliseconds) {
        this.zooKeeperHost = zooKeeperHost;
        this.sessionTimeoutMilliseconds = defaultValue(sessionTimeoutMilliseconds, DEFAULT_SESSION_TIMEOUT_MILLISECONDS);
        this.connectTimeoutMilliseconds = defaultValue(connectTimeoutMilliseconds, DEFAULT_CONNECT_TIMEOUT_MILLISECONDS);
        this.zoo = connect();
    }
    
    //TODO 省略
  }  
```

## Builder构建器

这个设计模式也是数据创建型的，如果参与大于4个，推荐使用这种模式。

### Demo

```
public class RedisTemplate implements Closeable {

    private JedisCluster jedisCluster;
    private String keyPrefix;

    private static String clientName = "xaecbd-sns";

    private RedisTemplate(RedisBuilder builder) {
        final String[] hosts = builder.hosts.split(RedisConstants.HOST_SPLIT);
        keyPrefix = builder.prefix;
        Set<HostAndPort> nodes = new HashSet<HostAndPort>() {
            private static final long serialVersionUID = 5341345879054512402L;

            {
                for (String hostAndPort : hosts) {
                    String[] array = hostAndPort.split(":");
                    if (array.length > 1) {
                        add(new HostAndPort(array[0], Integer.parseInt(array[1])));
                    } else {
                        add(new HostAndPort(array[0], RedisConstants.DEFAULT_PORT));
                    }
                }
            }
        };
        this.jedisCluster = new JedisCluster(nodes, builder.timeout, builder.timeout, builder.retry, builder.password, clientName,new GenericObjectPoolConfig<Jedis>());
    }

    //TODO 省略其他逻辑代码  

    public static class Builder {
        private String hosts;
        private String prefix;
        private int timeout = BinaryJedisCluster.DEFAULT_TIMEOUT;
        private int retry = BinaryJedisCluster.DEFAULT_MAX_ATTEMPTS;
        private String password = null;

        public RedisBuilder(String hosts) {
            this.hosts = hosts;
        }

        public RedisBuilder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public RedisBuilder retry(int retry) {
            this.retry = retry;
            return this;
        }

        public RedisBuilder password(String password) {
            this.password = password;
            return this;
        }

        public RedisBuilder keyPrefix(String prefix) {
            this.prefix = prefix;
            return this;
        }

        public RedisTemplate builder() {
            return new RedisTemplate(this);
        }
    }
}
```

## 封装技巧

### 返回值

使用Optinal包装返回值，避免返回值为null

### 统一校验

在类中的多个方法都需要相同的参数校验的，抽象出统一校验，jdk源码中就存在大量这样的例子。

下面举个例子：

```
private void assertConnected() {
        if(zoo == null) {
            throw new SnsException("ZooKeeper is not connected");
        }
    }

    private void assertPathValid(String path) {
        PathUtils.validatePath(path);
}

public void delete(String path) {
        assertConnected();
        assertPathValid(path);
       //省略
    }
    
public void create(String path) {
        assertConnected();
        assertPathValid(path);
       //省略
    }    
```

### AutoCloseable/Closeable

需要关闭资源的，记得实现AutoCloseable/Closeable


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://trumandu.gitbook.io/java-architect-tutorial/xiang-mu-kai-fa/ru-he-feng-zhuang-clienttool.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
