抽象工厂模式和建造者模式都属于创建型模式。两者都能创建对应的对象,而创建者模式更侧重于创建复杂对象,将对象的创建过程封装起来,让客户端不需要知道对象的内部细节。
1 抽象工厂模式
需求:
- 在使用工厂方法模式时,当增加新的产品类型时,需要创建对应的Factory类,这导致类的数量增加,让系统变得臃肿。
- 需要保证同风格下生成一致的组件。比如在写页面时,有dark及light模式,我们希望button、text等组件能在不同模式下保持一致的风格。抽象工厂模式介绍
1.1 抽象工厂模式介绍
为创建一组对象提供一组解决方案。与工厂模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,而是创建一族产品。
图 抽象工厂模式UML
public class Button {
private final String color;
public Button(String color) {
this.color = color;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"color='" + color + '\'' +
'}';
}
}
public class Text {
private final String color;
public Text(String color) {
this.color = color;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"color='" + color + '\'' +
'}';
}
}
public class DarkButton extends Button{
public DarkButton(String color) {
super(color);
}
}
public class DarkText extends Text{
public DarkText(String color) {
super(color);
}
}
public class LightButton extends Button{
public LightButton(String color) {
super(color);
}
}
public class LightText extends Text{
public LightText(String color) {
super(color);
}
}
public interface ComponentsFactory {
Button buildButton();
Text buildText();
}
public class DarkComponentFactory implements ComponentsFactory{
private final String color = "dark";
@Override
public Button buildButton() {
return new DarkButton(color);
}
@Override
public Text buildText() {
return new DarkText(color);
}
}
public class LightComponentFactory implements ComponentsFactory{
private final String color = "light";
@Override
public Button buildButton() {
return new LightButton(color);
}
@Override
public Text buildText() {
return new LightText(color);
}
}
public class HtmlWeb {
public static void main(String[] args) {
ComponentsFactory componentsFactory = new DarkComponentFactory();
System.out.println("-----------dark模式-----------");
System.out.println(componentsFactory.buildButton());
System.out.println(componentsFactory.buildText());
System.out.println("-----------light模式-----------");
componentsFactory = new LightComponentFactory();
System.out.println(componentsFactory.buildButton());
System.out.println(componentsFactory.buildText());
}
}
1.1.1 优缺点
优点:
- 隔离类的实例化过程,使得客户端并不需要知道如何创建对象。
- 当不同产品族多个对象被设计成一起工作时,能保证客户端始终只使用同一个产品族的对象。
- 减少了工厂类的数量。
- 增加新的产品族方便,符合开闭原则。
缺点:
- 不符合单一职责原则,一个类创建了多个对象。
- 族群增加新的种类时,不符合开闭原则,需要修改全部的工厂类。
2 建造者模式
需求:
- 需要创建一个复杂的对象,隔离类的实例化过程,使得客户端不需要知道对象的内部细节。
- 创建对象时,对执行顺序有要求。
2.1 建造者模式介绍
将一个复杂对象的构建与它的表示分离,使得同样的构建过程,不同的构建顺序可以构建不同的表示。
图 构建模式UML
Director 为指挥者,用来控制Buider 的执行顺序,在实际开发中,这个角色经常会被省略,它的功能集成到Builder上;而Builder中的builderPart方法的返回值为它本身,这样就可以链式调用了。
public class JdbcConnector {
private JdbcConnection connection;
public void initConnection(URI uri) {
connection = new JdbcConnection(uri);
}
public JdbcPreparedStatement getPreparedStatement(String query) {
if (connection == null) throw new RuntimeException("连接器还未初始化");
return connection.getPreparedStatement(query);
}
public static class JdbcConnection {
public JdbcConnection(URI uri){
System.out.println(uri);
}
public JdbcPreparedStatement getPreparedStatement(String query) {
return new JdbcPreparedStatement(query);
}
}
public static class JdbcPreparedStatement {
String query;
public JdbcPreparedStatement(String query) {
this.query = query;
}
public String getResult() {
return this.query;
}
}
}
public class JdbcConnectorBuilder {
private String scheme;
private String host;
private String port;
private String database;
private String username;
private String password;
public JdbcConnectorBuilder setScheme(String scheme) {
this.scheme = scheme;
return this;
}
public JdbcConnectorBuilder setHost(String host) {
this.host = host;
return this;
}
public JdbcConnectorBuilder setPort(String port) {
this.port = port;
return this;
}
public JdbcConnectorBuilder setDatabase(String database) {
this.database = database;
return this;
}
public JdbcConnectorBuilder setUsername(String username) {
this.username = username;
return this;
}
public JdbcConnectorBuilder setPassword(String password) {
this.password = password;
return this;
}
/**
* 产品
*/
private JdbcConnector jdbcConnector = null;
public JdbcConnectorBuilder() {
jdbcConnector = new JdbcConnector();
}
public JdbcConnector build() {
StringBuilder sb = new StringBuilder("jdbc:");
sb.append(scheme == null ? "mysql" : scheme).append("://");
if (host == null) throw new RuntimeException("host不能为空");
sb.append(host);
sb.append(":").append(port == null ? "3306" : port);
if (database == null) throw new RuntimeException("数据库不能为空");
sb.append("/").append(database);
try {
URI uri = new URI(sb.toString());
if (username == null || password == null) throw new RuntimeException("账号或密码错误");
jdbcConnector.initConnection(uri);
} catch (URISyntaxException e) {
throw new RuntimeException("连接失败,连接信息有误");
}
return jdbcConnector;
}
}
public class JdbcConnectionTest {
public static void main(String[] args) {
JdbcConnectorBuilder builder = new JdbcConnectorBuilder();
builder.setScheme("mysql")
.setHost("localhost")
.setPort("3307")
.setDatabase("my-database")
.setUsername("root")
.setPassword("123456");
JdbcConnector jdbcConnector = builder.build();
JdbcConnector.JdbcPreparedStatement statement = jdbcConnector.getPreparedStatement("select * from student");
System.out.println(statement.getResult());
}
}
JDK 中的StringBuilder及Apache httpclient 的 URIBuilder 应用了建造者模式。
自定义StringBuilder
public class CustomStringBuilder {
public static void main(String[] args) {
CustomStringBuilder customStringBuilder = new CustomStringBuilder("hello customStringBuilder");
for (int i =0; i < 100; i++) {
customStringBuilder.append("---建造者模式---").append(i + "");
}
System.out.println(customStringBuilder);
}
char[] value;
int count = 0;
public CustomStringBuilder(int capacity) {
value = new char[capacity];
}
public CustomStringBuilder(String str) {
this(str.length() + 16);
append(str);
}
CustomStringBuilder append(String str) {
if (str == null) {
throw new RuntimeException("字符串不能为空");
}
ensureCapacity(count + str.length());
str.getChars(0,str.length(),value,count);
count += str.length();
return this;
}
private void ensureCapacity(int minCapacity) {
if (minCapacity >= value.length) {
value = Arrays.copyOf(value,value.length << 1);
}
}
@Override
public String toString() {
return new String(value,0,count);
}
}
自定义URIBuilder
public class CustomURIBuilder {
public static void main(String[] args) throws URISyntaxException {
CustomURIBuilder customURIBuilder = new CustomURIBuilder();
customURIBuilder.setHost("localhost").setPort("8080").setEncodedPath("/userinfo");
customURIBuilder.appendQueryParam("username","hmf");
customURIBuilder.appendQueryParam("status","1");
customURIBuilder.appendQueryParam("createDate","2024/04/20");
URI uri = customURIBuilder.build();
System.out.println(uri);
}
private String scheme;
private String host;
private String port;
private String encodedPath;
private List<NameValuePair> queryParams;
public CustomURIBuilder setScheme(String scheme) {
this.scheme = scheme;
return this;
}
public CustomURIBuilder setHost(String host) {
this.host = host;
return this;
}
public CustomURIBuilder setPort(String port) {
this.port = port;
return this;
}
public CustomURIBuilder setEncodedPath(String encodedPath) {
this.encodedPath = encodedPath;
return this;
}
public CustomURIBuilder appendQueryParam(String name,String value) {
if (queryParams == null) queryParams = new ArrayList<>();
queryParams.add(new NameValuePair(name,value));
return this;
}
public URI build() throws URISyntaxException {
return new URI(buildStr());
}
public String buildStr() {
StringBuilder sb = new StringBuilder();
if (this.scheme != null) sb.append(this.scheme).append("://");
if (this.host == null) throw new RuntimeException("host不能为空");
sb.append(this.host);
if (this.port != null) sb.append(":").append(this.port);
if (this.encodedPath != null) sb.append(this.encodedPath);
if (this.queryParams != null) {
for (int i = 0; i < this.queryParams.size(); i++) {
sb.append(i == 0 ? "?" : "&").append(this.queryParams.get(i));
}
}
return sb.toString();
}
private static class NameValuePair {
String name;
String value;
public NameValuePair(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public String toString() {
if (this.value == null) return name;
return name + "=" + value;
}
}
}
2.1.1 优缺点
优点:
- 封装性好,将产品的内部表示与产品的生成过程分割开。客户端只需知道所需的产品类型,而不需要知道产品内部的具体结构和实现细节。
- 扩展性好,各个具体建造者相互独立,如果需要添加新的产品,只需创建对应的建造者即可,符合开闭原则。
- 便于构建发展对象,可以简化对象的创建过程,提高代码的可读性和可维护性。
缺点:
- 可维护性差,如果产品的内部发生变化,则对应的创建者可能需要修改的地方会比较多。
- 类的数量增加。