什么是在具有相同行为但不同类常量的两个类之间推荐的模式?

huangapple go评论73阅读模式
英文:

What is the recommended pattern for two classes with identical behaviours but different class constants?

问题

以下是翻译好的部分:

我有两个类,它们具有相同的行为,只是类 SnakeCaseyMapper 使用 snake_case 常量字段,而类 CamelCaseyMapper 使用 camelCase 常量字段。

在需要这两个类之前,我的逻辑大致如下:

public class Mapper {
    public static final String FIELD = "snake_casey_field";
    // 许多其他常量字段...

    public Foo map(Bar bar) {
        // 使用常量字段的一些逻辑
    }
}

public class ClassThatsDoingLogic {
    var mapper = new Mapper();
    var result = mapper.map(bar);
}

现在我需要相同的 map(Bar bar) 方法,但一个使用 camelCase 常量,另一个保留原来的 snake_case 实现。

我的想法是利用抽象类:

public abstract class Mapper {
    public String field; // 这里不进行实例化
    // 许多其他成员变量字段...

    public Foo map(Bar bar) {
        // 使用常量字段的一些逻辑
    }
}

public class SnakeCaseyMapper extends Mapper {
    public SnakeCaseyMapper() {
        field = "snake_casey_field";
        // 实例化许多其他字段
    }
}

public class CamelCaseyMapper extends Mapper {
    public CamelCaseyMapper() {
        field = "camelCaseyField";
        // 实例化许多其他字段
    }
}

public class ClassThatsDoingLogic {
    var snakeCaseyMapper = new SnakeCaseyMapper();
    var result = snakeCaseyMapper.map(snakeCaseyBar);
    var camelCaseyMapper = new CamelCaseyMapper();
    var result = camelCaseyMapper.map(camelCaseyBar);
}

这样两个类都在 map() 方法中使用相同的逻辑,避免了代码的重复。然而,我认为我失去了最初的常量字段的不变性。有没有解决这个问题的方法?我是否忽略了处理这个问题的某种方式?

英文:

I have two classes which have identical behaviour, except class SnakeCaseyMapper uses snake_case constant fields and class CamelCaseyMapper uses camelCase constant fields.

Before requiring two of these classes, my logic looked roughly like:

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);
}

Now I require this same method, map(Bar bar) but with camelCase constants, as well as the original implementation with snake_case.

My idea was to make use of abstract 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);
}

This way both classes use the same method logic in map() without duplicating the code. However, I think I lose the finality of the constant fields I had originally. Is there a way around this? Is there a way of handling this problem I'm missing?

答案1

得分: 2

正如@Kayaman建议的那样,应避免使用继承,在您的情况下,关键在于参数化。如果您可以通过配置加载来实现,那将是很好的。

一个中间的解决方案可能是通过实例化一个带有所有必需参数的私有构造函数,然后提供一个公共构造函数,该构造函数将调用私有构造函数,在特定条件下设置所需参数。(注意:下面示例中的代码未经测试)

public class Mapper {

    enum MapperType {
        CamelCase,
        SnakeCase
    }

    // 不要定义公共属性。使用setter和getter在类外部修改它们,
    // 以保留封装原则。
    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;
        // 更多属性在这里
    }

}

对此的偏离也可以使用类似工厂的模式。(注意:将定义带一些保留的含义,因为通常情况下,工厂可以用于生成共享相同基类的不同派生类的实例)。

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;
        // 更多属性在这里
    }

}

然后,您可以创建一个工厂“Wrapper”类来进行初始化:

public static class MapperFactory {
    public static Mapper instantiate(Mapper.MapperType type) {

        // 示例。请注意,我们更改了所有参数。
        // 还可以考虑使用调度表来避免使用switch。
        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);

但请考虑,如果您只是添加了少量参数,这样的实现可能过于复杂,这只是为了给您展示一个示例。只有在对象有大量参数并且需要时才使用这种方式。在简单情况下,只需调用具有适当参数的Mapper类,或在初始化时进行简单的条件检查。

此外,关于snake_casecamelCase字段之间的区别,您可以使用正则表达式来区分并根据条件进行适当的初始化,但我感觉您主要是在询问适当的代码分割,而不是基于字段风格的区分。

英文:

As @Kayaman suggested, inheritance should be avoided, and in your case, it is all about parameterisation. If you can do it via configuration loading it would be great.

A solution in the middle, could be possibly to instantiate a private constructor with all the arguments needed, and then provide one public constructor that would call the private one, setting the arguments needed under condition. (Note: untested code in examples below)

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
    }

}

A deviation to this, would also be to use Factory-ish pattern (Note: take the definition with a grain of salt, as normally, a factory can be used in order to generate instances of different derived classes sharing the same base class).

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
    }

}

Then, you can create a Factory "Wrapper" class for the initialization:

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);
        } 
    }
}

and then, you can do:

Mapper m = MapperFactory.instantiate(Mapper.MapperType.CamelCase);

Consider though that, if you are just adding such a few parameters, such implementation is overengineering, just to show you an example. Use it only if you have LOTS of parameters for your objects and you want ti. In simple scenarios, just call the Mapper class with the appropriate parameters, or make a simple conditional check upon initialization.

Also, regarding the difference between snake_case and camelCase fields, you can use regex in order to distinguish and properly initialize upon condition, but my sense is that you are asking mainly for the proper code segmentation, rather than fields distinction based on the style they are written.

答案2

得分: 1

以下是翻译好的部分:

要补充我的评论。由于继承可以用于存在不同“行为”的情况,所以这绝对不是适合它的正确位置。

以下是3个示例,具有“最小的努力”,尽管它们仍然需要至少与您在映射器中的字段数量相同的行数。

public class Mapper {
    private final String FIELD;
    private String FIELD2 = "defaultCamelCase";
    private final String FIELD3;

    public Mapper(boolean snakeCase) {
        // 这对于最终实例字段有效
        FIELD = snakeCase ? "snakey_case_field" : "camelCaseField";
        // 或者具有默认值的字段
        if (snakeCase) {
            FIELD2 = toSnakeCase(FIELD2);
            // 或某种类似的机制
        }
        // 或者具有私有构造函数助手的最终实例字段
        // 它返回参数本身或者进行转换
        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("_"));
    }
}

请注意,代码段已经按照您提供的原始格式进行了翻译。如果您有任何其他问题或需要进一步的帮助,请随时提问。

英文:

To add to my comments. Since inheritance can be used when there's different behaviour, this is definitely not the right place for it.

Below are 3 examples with "least effort", although they still require at least the amount of lines that you have fields in the mapper.

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("_"));
    }
}

huangapple
  • 本文由 发表于 2020年4月10日 18:28:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/61138372.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定