Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d8b6886

Browse files
Better extension pattern
1 parent b76c667 commit d8b6886

File tree

2 files changed

+58
-51
lines changed

2 files changed

+58
-51
lines changed

‎src/main/java/org/mybatis/dynamic/sql/SqlColumn.java‎

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,30 @@
3030
*
3131
* <p>The class contains many attributes that are helpful for use in MyBatis and Spring runtime
3232
* environments, but the only required attributes are the name of the column and a reference to
33-
* the SqlTable the column is a part of.
33+
* the {@link SqlTable} the column is a part of.
3434
*
3535
* <p>The class can be extended if you wish to associate additional attributes with a column for your
36-
* own purposes. Extending the class involves the following activities:
36+
* own purposes. Extending the class is a bit more challenging than you might expect because you will need to
37+
* handle the covariant types for many methods in {@code SqlColumn}. Additionally, many methods in {@code SqlColumn}
38+
* create new instances of the class in keeping with the library's primary strategy of immutability. You will also
39+
* need to ensure that these methods create instances of your extended class, rather than the base {@code SqlColumn}
40+
* class. We have worked to keep this process as simple as possible.
41+
*
42+
* <p>Extending the class involves the following activities:
3743
* <ol>
3844
* <li>Create a class that extends {@link SqlColumn}</li>
3945
* <li>In your extended class, create a static builder class that extends {@link SqlColumn.AbstractBuilder}</li>
4046
* <li>Add your desired attributes to the class and the builder</li>
47+
* <li>In your extended class, override the {@link SqlColumn#copyBuilder()} method and return a new instance of
48+
* your builder with all attributes set. You should call the
49+
* {@link SqlColumn#populateBaseBuilder(AbstractBuilder)} method
50+
* to set the attributes from {@code SqlColumn}, then populate your extended attributes.
51+
* </li>
4152
* <li>You MUST override the following methods. These methods are used with regular operations in the library.
4253
* If you do not override these methods, it is likely that your extended attributes will be lost during
43-
* regular usage. For example, if a user calls the {@code as} method to apply an alias, the base
44-
* {@code SqlColumn} class will create a new instance of {@code SqlColumn}, NOT your extended class.
54+
* regular usage. For example, if you do not override the {@code as} method and a user calls the method to
55+
* apply an alias, then the base {@code SqlColumn} class would create a new instance of {@code SqlColumn}, NOT
56+
* your extended class.
4557
* <ul>
4658
* <li>{@link SqlColumn#as(String)}</li>
4759
* <li>{@link SqlColumn#asCamelCase()}</li>
@@ -63,10 +75,21 @@
6375
* </li>
6476
* </ol>
6577
*
66-
* <p>The test code for this library contains an example of a proper extension of this class. In most cases, the code
67-
* for the overriding methods can be simply copied from this class and then the return types can be changed to the
68-
* subclass's covariant type (this pre-supposes that you create a {@code copyBuilder} method that returns a new builder
69-
* populated with all current class attributes).
78+
* <p>For all overridden methods except {@code copyBuilder()}, the process is to call the superclass
79+
* method and cast the result properly. We provide a {@link SqlColumn#cast(SqlColumn)} method to aid with this
80+
* process. For example, overriding the {@code descending} method could look like this:
81+
*
82+
* <p>
83+
* <pre>
84+
* {@code
85+
* @Override
86+
* public MyExtendedColumn<T> descending() {
87+
* return cast(super.descending());
88+
* }
89+
* }
90+
* </pre>
91+
*
92+
* <p>The test code for this library contains an example of a proper extension of this class.
7093
*
7194
* @param <T> the Java type associated with the column
7295
*/
@@ -84,7 +107,7 @@ public class SqlColumn<T> implements BindableColumn<T>, SortSpecification {
84107
protected final @Nullable Class<T> javaType;
85108
protected final @Nullable String javaProperty;
86109

87-
protected SqlColumn(AbstractBuilder<T, ?> builder) {
110+
protected SqlColumn(AbstractBuilder<T, ?, ?> builder) {
88111
name = Objects.requireNonNull(builder.name);
89112
table = Objects.requireNonNull(builder.table);
90113
jdbcType = builder.jdbcType;
@@ -142,11 +165,7 @@ public Optional<String> javaProperty() {
142165
*/
143166
@Override
144167
public SqlColumn<T> descending() {
145-
return setDescending(copyBuilder()).build();
146-
}
147-
148-
protected <B extends AbstractBuilder<T, ?>> B setDescending(B builder) {
149-
return cast(builder.withDescendingPhrase(" DESC")); //$NON-NLS-1$
168+
return cast(copyBuilder().withDescendingPhrase(" DESC").build()); //$NON-NLS-1$
150169
}
151170

152171
/**
@@ -159,11 +178,7 @@ public SqlColumn<T> descending() {
159178
*/
160179
@Override
161180
public SqlColumn<T> as(String alias) {
162-
return setAlias(copyBuilder(), alias).build();
163-
}
164-
165-
protected <B extends AbstractBuilder<T, ?>> B setAlias(B builder, String alias) {
166-
return cast(builder.withAlias(alias));
181+
return cast(copyBuilder().withAlias(alias).build());
167182
}
168183

169184
/**
@@ -174,11 +189,7 @@ public SqlColumn<T> as(String alias) {
174189
* @return a new column that will be rendered with the specified table qualifier
175190
*/
176191
public SqlColumn<T> qualifiedWith(String tableQualifier) {
177-
return setTableQualifier(copyBuilder(), tableQualifier).build();
178-
}
179-
180-
protected <B extends AbstractBuilder<T, ?>> B setTableQualifier(B builder, String tableQualifier) {
181-
return cast(builder.withTableQualifier(tableQualifier));
192+
return cast(copyBuilder().withTableQualifier(tableQualifier).build());
182193
}
183194

184195
/**
@@ -193,11 +204,9 @@ public SqlColumn<T> qualifiedWith(String tableQualifier) {
193204
* @return a new column aliased with a camel case version of the column name
194205
*/
195206
public SqlColumn<T> asCamelCase() {
196-
return setCamelCaseAlias(copyBuilder()).build();
197-
}
198-
199-
protected <B extends AbstractBuilder<T, ?>> B setCamelCaseAlias(B builder) {
200-
return cast(builder.withAlias("\"" + StringUtilities.toCamelCase(name) + "\"")); //$NON-NLS-1$ //$NON-NLS-2$
207+
return cast(copyBuilder()
208+
.withAlias("\"" + StringUtilities.toCamelCase(name) + "\"") //$NON-NLS-1$ //$NON-NLS-2$
209+
.build());
201210
}
202211

203212
@Override
@@ -310,7 +319,7 @@ public <S> SqlColumn<S> withJavaProperty(String javaProperty) {
310319
return cast(copyBuilder().withJavaProperty(javaProperty).build());
311320
}
312321

313-
privateBuilder<T> copyBuilder() {
322+
protectedAbstractBuilder<T, ?, ?> copyBuilder() {
314323
return populateBaseBuilder(new Builder<>());
315324
}
316325

@@ -319,11 +328,6 @@ protected <S extends SqlColumn<?>> S cast(SqlColumn<?> column) {
319328
return (S) column;
320329
}
321330

322-
@SuppressWarnings("unchecked")
323-
protected <B extends AbstractBuilder<?, ?>> B cast(AbstractBuilder<?, ?> builder) {
324-
return (B) builder;
325-
}
326-
327331
/**
328332
* This method will add all current attributes to the specified builder. It is useful when creating
329333
* new class instances that only change one attribute - we set all current attributes, then
@@ -334,7 +338,7 @@ protected <S extends SqlColumn<?>> S cast(SqlColumn<?> column) {
334338
* @return the populated builder
335339
*/
336340
@SuppressWarnings("unchecked")
337-
protected <B extends AbstractBuilder<T, ?>> B populateBaseBuilder(B builder) {
341+
protected <B extends AbstractBuilder<T, ?, ?>> B populateBaseBuilder(B builder) {
338342
return (B) builder
339343
.withName(this.name)
340344
.withTable(this.table)
@@ -362,7 +366,7 @@ public static <T> SqlColumn<T> of(String name, SqlTable table, JDBCType jdbcType
362366
.build();
363367
}
364368

365-
public static abstract class AbstractBuilder<T, B extends AbstractBuilder<T, B>> {
369+
public static abstract class AbstractBuilder<T, CextendsSqlColumn<T>, B extends AbstractBuilder<T, C, B>> {
366370
protected @Nullable String name;
367371
protected @Nullable SqlTable table;
368372
protected @Nullable JDBCType jdbcType;
@@ -431,9 +435,12 @@ public B withJavaProperty(@Nullable String javaProperty) {
431435
}
432436

433437
protected abstract B getThis();
438+
439+
public abstract C build();
434440
}
435441

436-
public static class Builder<T> extends AbstractBuilder<T, Builder<T>> {
442+
public static class Builder<T> extends AbstractBuilder<T, SqlColumn<T>, Builder<T>> {
443+
@Override
437444
public SqlColumn<T> build() {
438445
return new SqlColumn<>(this);
439446
}

‎src/test/java/examples/simple/PrimaryKeyColumn.java‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,63 +33,63 @@ public boolean isPrimaryKeyColumn() {
3333

3434
@Override
3535
public PrimaryKeyColumn<T> descending() {
36-
return setDescending(copyBuilder()).build();
36+
return cast(super.descending());
3737
}
3838

3939
@Override
4040
public PrimaryKeyColumn<T> as(String alias) {
41-
return setAlias(copyBuilder(), alias).build();
41+
return cast(super.as(alias));
4242
}
4343

4444
@Override
4545
public PrimaryKeyColumn<T> qualifiedWith(String tableQualifier) {
46-
return setTableQualifier(copyBuilder(), tableQualifier).build();
46+
return cast(super.qualifiedWith(tableQualifier));
4747
}
4848

4949
@Override
5050
public PrimaryKeyColumn<T> asCamelCase() {
51-
return setCamelCaseAlias(copyBuilder()).build();
51+
return cast(super.asCamelCase());
5252
}
5353

5454
@Override
5555
public <S> PrimaryKeyColumn<S> withTypeHandler(String typeHandler) {
56-
return cast(copyBuilder().withTypeHandler(typeHandler).build());
56+
return cast(super.withTypeHandler(typeHandler));
5757
}
5858

5959
@Override
6060
public <S> PrimaryKeyColumn<S> withRenderingStrategy(RenderingStrategy renderingStrategy) {
61-
return cast(copyBuilder().withRenderingStrategy(renderingStrategy).build());
61+
return cast(super.withRenderingStrategy(renderingStrategy));
6262
}
6363

6464
@Override
65-
@SuppressWarnings("unchecked")
6665
public <S> PrimaryKeyColumn<S> withParameterTypeConverter(ParameterTypeConverter<S, ?> parameterTypeConverter) {
67-
return cast(copyBuilder().withParameterTypeConverter((ParameterTypeConverter<T, ?>) parameterTypeConverter).build());
66+
return cast(super.withParameterTypeConverter(parameterTypeConverter));
6867
}
6968

7069
@Override
71-
@SuppressWarnings("unchecked")
7270
public <S> PrimaryKeyColumn<S> withJavaType(Class<S> javaType) {
73-
return cast(copyBuilder().withJavaType((Class<T>) javaType).build());
71+
return cast(super.withJavaType(javaType));
7472
}
7573

7674
@Override
7775
public <S> PrimaryKeyColumn<S> withJavaProperty(String javaProperty) {
78-
return cast(copyBuilder().withJavaProperty(javaProperty).build());
76+
return cast(super.withJavaProperty(javaProperty));
7977
}
8078

81-
private Builder<T> copyBuilder() {
79+
@Override
80+
protected Builder<T> copyBuilder() {
8281
return populateBaseBuilder(new Builder<>()).isPrimaryKeyColumn(isPrimaryKeyColumn);
8382
}
8483

85-
public static class Builder<T> extends AbstractBuilder<T, Builder<T>> {
84+
public static class Builder<T> extends AbstractBuilder<T, PrimaryKeyColumn<T>, Builder<T>> {
8685
private boolean isPrimaryKeyColumn;
8786

8887
public Builder<T> isPrimaryKeyColumn(boolean isPrimaryKeyColumn) {
8988
this.isPrimaryKeyColumn = isPrimaryKeyColumn;
9089
return this;
9190
}
9291

92+
@Override
9393
public PrimaryKeyColumn<T> build() {
9494
return new PrimaryKeyColumn<>(this);
9595
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /