对于行为相同但 class 常量不同的两个 classes,推荐的模式是什么?
What is the recommended pattern for two classes with identical behaviours but different class constants?
我有两个具有相同行为的 classes,除了 class SnakeCaseyMapper
使用 snake_case
常量字段和 class CamelCaseyMapper
使用camelCase
常量字段。
在要求其中两个 classes 之前,我的逻辑大致如下:
public class Mapper {
public static final String FIELD = "snake_casey_field";
// Lots of other constant fields ...
public Foo map(Bar bar) {
// Some logic that makes use of the constant FIELDs
}
}
public class ClassThatsDoingLogic {
var mapper = new Mapper();
var result = mapper.map(bar);
}
现在我需要同样的方法,map(Bar bar)
但使用 camelCase
常量,以及使用 snake_case
.
的原始实现
我的想法是利用抽象 classes:
public abstract class Mapper {
public String field; // Not instantiated here
// Lots of other member variable fields ...
public Foo map(Bar bar) {
// Some logic that makes use of the constant FIELDs
}
}
public class SnakeCaseyMapper extends Mapper {
public SnakeCaseyMapper() {
field = "snake_casey_field";
// Lots of other fields instantiated
}
}
public class CamelCaseyMapper extends Mapper {
public CamelCaseyMapper() {
field = "camelCaseyField";
// Lots of other fields instantiated
}
}
public class ClassThatsDoingLogic {
var snakeCaseyMapper = new SnakeCaseyMapper();
var result = snakeCaseyMapper.map(snakeCaseyBar);
var camelCaseyMapper = new CamelCaseyMapper();
var result = camelCaseyMapper.map(camelCaseyBar);
}
这样,两个 class 都在 map()
中使用相同的方法逻辑,而无需重复代码。但是,我想我失去了我原来拥有的常量字段的最终性。有没有解决的办法?有没有办法处理我所缺少的这个问题?
正如@Kayaman 所建议的那样,应避免继承,在您的情况下,一切都与参数化有关。如果你能通过配置加载来做到这一点,那就太好了。
中间的一个解决方案,可能是用所有需要的参数实例化一个私有构造函数,然后提供一个 public 构造函数来调用私有构造函数,根据条件设置所需的参数。 (注意: 下面示例中的代码未经测试)
public class Mapper {
enum MapperType {
CamelCase,
SnakeCase
}
// Never define a public property. Use setters
// and getters to modify them outside the class,
// preserving the encapsulation principle.
private MapperType mType;
private int mProperty1;
public Mapper(MapperType type) {
this(type, type == MapperType.CamelCase ? 100 : 200);
}
private Mapper(MapperType type, int mProperty1) {
this.mType = type;
this.mProperty1 = property1;
// More properties here
}
}
与此不同的是,也可以使用 Factory-ish 模式(注意: 对定义持保留态度,通常情况下,工厂可以用于为了生成共享相同基础 class) 的不同派生 classes 的实例。
public class Mapper {
enum MapperType {
CamelCase,
SnakeCase
}
private MapperType mType;
private int mProperty1;
public Mapper(MapperType type, int mProperty1) {
this.mType = type;
this.mProperty1 = property1;
// More properties here
}
}
然后,你可以创建一个Factory"Wrapper"class来初始化:
public static class MapperFactory {
public static Mapper instantiate(Mapper.MapperType type) {
// Dummy example. Notice that we change all parameters.
// a dispatch table can also be considered to avoid switching.
switch(type) {
case Mapper.MapperType.CamelCase:
return new Mapper(Mapper.MapperType.CamelCase, 100);
case Mapper.MapperType.SnakeCase:
return new Mapper(Mapper.MapperType.SnakeCase, 200);
}
}
}
然后,您可以:
Mapper m = MapperFactory.instantiate(Mapper.MapperType.CamelCase);
考虑一下,如果你只是添加这么几个参数,这样的实现是过度工程,只是给你举个例子。 仅 如果您的对象有很多参数并且您想要 ti,请使用它。在简单的场景中,只需调用 Mapper
class 并带上适当的参数,或者在初始化时进行简单的条件检查。
此外,关于 snake_case
和 camelCase
字段之间的区别,您可以使用正则表达式来区分并根据条件正确初始化,但我的感觉是您主要要求正确的代码分割,而不是基于编写风格的字段区分。
添加到我的评论中。因为当有不同的行为时可以使用继承,所以这绝对不是合适的地方。
下面是 "least effort" 的 3 个示例,尽管它们仍然至少需要您在映射器中拥有字段的行数。
public class Mapper {
private final String FIELD;
private String FIELD2 = "defaultCamelCase";
private final String FIELD3;
public Mapper(boolean snakeCase) {
// This would work for final instance fields
FIELD = snakeCase ? "snakey_case_field" : "camelCaseField";
// or fields having default values
if(snakeCase) {
FIELD2 = toSnakeCase(FIELD2);
// or some kind of similar mechanism
}
// or final instance fields with a private constructor helper
// that returns either the parameter as-is, or converts it
FIELD3 = initField("fieldName", snakeCase);
}
private String initField(String field, boolean snakeCase) {
if(!snakeCase)
return field;
return Arrays.stream(field.split("(?=[A-Z])")).map(String::toLowerCase).collect(Collectors.joining("_"));
}
}
我有两个具有相同行为的 classes,除了 class SnakeCaseyMapper
使用 snake_case
常量字段和 class CamelCaseyMapper
使用camelCase
常量字段。
在要求其中两个 classes 之前,我的逻辑大致如下:
public class Mapper {
public static final String FIELD = "snake_casey_field";
// Lots of other constant fields ...
public Foo map(Bar bar) {
// Some logic that makes use of the constant FIELDs
}
}
public class ClassThatsDoingLogic {
var mapper = new Mapper();
var result = mapper.map(bar);
}
现在我需要同样的方法,map(Bar bar)
但使用 camelCase
常量,以及使用 snake_case
.
我的想法是利用抽象 classes:
public abstract class Mapper {
public String field; // Not instantiated here
// Lots of other member variable fields ...
public Foo map(Bar bar) {
// Some logic that makes use of the constant FIELDs
}
}
public class SnakeCaseyMapper extends Mapper {
public SnakeCaseyMapper() {
field = "snake_casey_field";
// Lots of other fields instantiated
}
}
public class CamelCaseyMapper extends Mapper {
public CamelCaseyMapper() {
field = "camelCaseyField";
// Lots of other fields instantiated
}
}
public class ClassThatsDoingLogic {
var snakeCaseyMapper = new SnakeCaseyMapper();
var result = snakeCaseyMapper.map(snakeCaseyBar);
var camelCaseyMapper = new CamelCaseyMapper();
var result = camelCaseyMapper.map(camelCaseyBar);
}
这样,两个 class 都在 map()
中使用相同的方法逻辑,而无需重复代码。但是,我想我失去了我原来拥有的常量字段的最终性。有没有解决的办法?有没有办法处理我所缺少的这个问题?
正如@Kayaman 所建议的那样,应避免继承,在您的情况下,一切都与参数化有关。如果你能通过配置加载来做到这一点,那就太好了。
中间的一个解决方案,可能是用所有需要的参数实例化一个私有构造函数,然后提供一个 public 构造函数来调用私有构造函数,根据条件设置所需的参数。 (注意: 下面示例中的代码未经测试)
public class Mapper {
enum MapperType {
CamelCase,
SnakeCase
}
// Never define a public property. Use setters
// and getters to modify them outside the class,
// preserving the encapsulation principle.
private MapperType mType;
private int mProperty1;
public Mapper(MapperType type) {
this(type, type == MapperType.CamelCase ? 100 : 200);
}
private Mapper(MapperType type, int mProperty1) {
this.mType = type;
this.mProperty1 = property1;
// More properties here
}
}
与此不同的是,也可以使用 Factory-ish 模式(注意: 对定义持保留态度,通常情况下,工厂可以用于为了生成共享相同基础 class) 的不同派生 classes 的实例。
public class Mapper {
enum MapperType {
CamelCase,
SnakeCase
}
private MapperType mType;
private int mProperty1;
public Mapper(MapperType type, int mProperty1) {
this.mType = type;
this.mProperty1 = property1;
// More properties here
}
}
然后,你可以创建一个Factory"Wrapper"class来初始化:
public static class MapperFactory {
public static Mapper instantiate(Mapper.MapperType type) {
// Dummy example. Notice that we change all parameters.
// a dispatch table can also be considered to avoid switching.
switch(type) {
case Mapper.MapperType.CamelCase:
return new Mapper(Mapper.MapperType.CamelCase, 100);
case Mapper.MapperType.SnakeCase:
return new Mapper(Mapper.MapperType.SnakeCase, 200);
}
}
}
然后,您可以:
Mapper m = MapperFactory.instantiate(Mapper.MapperType.CamelCase);
考虑一下,如果你只是添加这么几个参数,这样的实现是过度工程,只是给你举个例子。 仅 如果您的对象有很多参数并且您想要 ti,请使用它。在简单的场景中,只需调用 Mapper
class 并带上适当的参数,或者在初始化时进行简单的条件检查。
此外,关于 snake_case
和 camelCase
字段之间的区别,您可以使用正则表达式来区分并根据条件正确初始化,但我的感觉是您主要要求正确的代码分割,而不是基于编写风格的字段区分。
添加到我的评论中。因为当有不同的行为时可以使用继承,所以这绝对不是合适的地方。
下面是 "least effort" 的 3 个示例,尽管它们仍然至少需要您在映射器中拥有字段的行数。
public class Mapper {
private final String FIELD;
private String FIELD2 = "defaultCamelCase";
private final String FIELD3;
public Mapper(boolean snakeCase) {
// This would work for final instance fields
FIELD = snakeCase ? "snakey_case_field" : "camelCaseField";
// or fields having default values
if(snakeCase) {
FIELD2 = toSnakeCase(FIELD2);
// or some kind of similar mechanism
}
// or final instance fields with a private constructor helper
// that returns either the parameter as-is, or converts it
FIELD3 = initField("fieldName", snakeCase);
}
private String initField(String field, boolean snakeCase) {
if(!snakeCase)
return field;
return Arrays.stream(field.split("(?=[A-Z])")).map(String::toLowerCase).collect(Collectors.joining("_"));
}
}