简体   繁体   English

Java类:将实例变量限制为几个可能的值之一,具体取决于其他实例变量

[英]Java class: limit instance variable to one of several possible values, depending on other instance variables

I am sorry for the vague question. 对于这个模糊的问题,我感到抱歉。 I am not sure what I'm looking for here. 我不确定在这里找什么。

I have a Java class, let's call it Bar . 我有一个Java类,我们称之为Bar In that class is an instance variable, let's call it foo . 在该类中是一个实例变量,我们将其称为foo foo is a String. foo是一个字符串。

foo cannot just have any value. foo不能仅仅具有任何价值。 There is a long list of strings, and foo must be one of them. 字符串很长,并且foo必须是其中之一。

Then, for each of those strings in the list I would like the possibility to set some extra conditions as to whether that specific foo can belong in that specific type of Bar (depending on other instance variables in that same Bar ). 然后,对于列表中的每个字符串,我希望可以设置一些额外的条件,以确定特定的foo是否可以属于该特定的Bar类型(取决于同一Bar其他实例变量)。

What approach should I take here? 我应该在这里采取什么方法? Obviously, I could put the list of strings in a static class somewhere and upon calling setFoo(String s) check whether s is in that list. 显然,我可以将字符串列表放在某个静态类中的某个位置,并在调用setFoo(String s)检查s是否在该列表中。 But that would not allow me to check for extra conditions - or I would need to put all that logic for every value of foo in the same method, which would get ugly quickly. 但这不允许我检查额外的条件-否则我需要将foo每个值的所有逻辑都放在同一方法中,这很快就会变得难看。

Is the solution to make several hundred classes for every possible value of foo and insert in each the respective (often trivial) logic to determine what types of Bar it fits? 解决方案是否为每个可能的foo值创建数百个类,并在每个逻辑中分别插入(通常是琐碎的)逻辑以确定适合的Bar类型? That doesn't sound right either. 这听起来也不对。

What approach should I take here? 我应该在这里采取什么方法?

Here's a more concrete example, to make it more clear what I am looking for. 这是一个更具体的示例,以使其更清楚我在寻找什么。 Say there is a Furniture class, with a variable material , which can be lots of things, anything from mahogany to plywood. 假设有一个Furniture类,其material可变,可以包含很多东西,从红木到胶合板。 But there is another variable, upholstery , and you can make furniture containing cotton of plywood but not oak; 但是还有另一个变数,就是upholstery ,您可以制作包含夹板棉但不包含橡木的家具。 satin furniture of oak but not walnut; 橡木但不是胡桃木的缎面家具; other types of fabric go well with any material; 其他类型的织物可以与任何材料配合使用; et cetera. 等等。

I wouldn't suggest creating multiple classes/templates for such a big use case. 我不建议为如此大的用例创建多个类/模板。 This is very opinion based but I'll take a shot at answering as best as I can. 这是基于意见的,但我会尽全力回答。

In such a case where your options can be numerous and you want to keep a maintainable code base, the best solution is to separate the values and the logic. 在这种情况下,您的选择可能很多,并且您想要保持可维护的代码库,最好的解决方案是将值和逻辑分开。 I recommend that you store your foo values in a database. 我建议您将foo值存储在数据库中。 At the same time, keep your client code as clean and small as possible. 同时,保持您的客户端代码尽可能干净和小巧。 So that it doesn't need to filter through the data to figure out which data is valid. 这样就不需要筛选数据来确定哪些数据有效。 You want to minimize dependency to data in your code . 您希望最小化对代码中数据的依赖性 Think of it this way: tomorrow you might need to add a new material to your material list. 这样考虑:明天您可能需要在材料清单中添加新材料。 Do you want to modify all your code for that? 您是否要为此修改所有代码? Or do you want to just add it to your database and everything magically works? 还是只想将其添加到数据库中就可以神奇地完成所有工作? Obviously the latter is a better option. 显然,后者是一个更好的选择。 Here is an example on how to design such a system. 这是有关如何设计这样的系统的示例。 Of course, this can vary based on your use case or variables but it is a good guideline. 当然,这可能会因您的用例或变量而异,但这是一个很好的指导原则。 The basic rule of thumb is: your code should have as little dependency to data as possible. 基本的经验法则是:您的代码应尽可能少地依赖数据。

Let's say you want to create a Bar which has to have a certain foo . 假设您要创建一个必须具有特定fooBar In this case, I would create a database for BARS which contains all the possible Bar s. 在这种情况下,我将为BARS创建一个数据库,其中包含所有可能的Bar Example: 例:

ID NAME FOO 1 Door 1,4,10 ID NAME FOO 1门1,4,10

I will also create a database FOOS which contains the details of each foo . 我还将创建一个数据库FOOS,其中包含每个foo的详细信息。 For example: 例如:

ID NAME PROPERTY1 PROPERTY2 ... 1 Oak Brown Soft 编号名称PROPERTY1 PROPERTY2 ... 1橡木棕色软

When you create a Bar: 创建条形图时:

Bar door = new Bar(Bar.DOOR);

in the constructor you would go to the BARS table and query the foo s. 在构造函数中,您将转到BARS表并查询foo Then you would query the FOOS table and load all the material and assign them to the field inside your new object. 然后,您将查询FOOS表并加载所有材料,并将它们分配给新对象内的字段。

This way whenever you create a Bar the material can be changed and loaded from DB without changing any code. 这样,无论何时创建Bar ,都可以在不更改任何代码的情况下从DB更改和加载物料。 You can add as many types of Bar as you can and change material properties as you goo. 您可以添加多种类型的Bar ,你可以和改变材料性能,你咕咕。 Your client code however doesn't change much. 但是,您的客户端代码变化不大。

You might ask why do we create a database for FOOS and refer to it's ids in the BARS table? 您可能会问为什么我们要为FOOS创建数据库并在BARS表中引用它的ID? This way, you can modify the properties of each foo as much as you want. 这样,您可以根据需要修改每个foo的属性。 Also you can share foo s between Bar s and vice versa but you only need to change the db once. 同样,您可以在Bar之间共享foo ,反之亦然,但是您只需更改一次数据库。 cross referencing becomes a breeze. 交叉引用变得轻而易举。 I hope this example explains the idea clearly. 我希望这个例子可以清楚地解释这个想法。

You say: 你说:

Is the solution to make several hundred classes for every possible value of foo and insert in each the respective (often trivial) logic to determine what types of Bar it fits? 解决方案是否为每个可能的foo值创建数百个类,并在每个逻辑中分别插入(通常是琐碎的)逻辑以确定适合的Bar类型? That doesn't sound right either. 这听起来也不对。

Why not have separate classes for each type of Foo? 为什么不为每种类型的Foo提供单独的类? Unless you need to define new types of Foo without changing the code you can model them as plain Java classes. 除非您需要定义新的Foo类型而不更改代码,否则可以将它们建模为简单的Java类。 You can go with enums as well but it does not really give you any advantage since you still need to update the enum when adding a new type of Foo. 您也可以使用枚举,但是它并没有真正的优势,因为添加新类型的Foo时仍然需要更新枚举。

In any case here is type safe approach that guarantees compile time checking of your rules: 无论如何,这里都是类型安全的方法,可确保对规则进行编译时检查:

public static interface Material{}

public static interface Upholstery{}

public static class Oak implements Material{}

public static class Plywood implements Material{}

public static class Cotton implements Upholstery{}

public static class Satin implements Upholstery{}


public static class Furniture<M extends Material, U extends Upholstery>{

    private M matrerial = null;
    private U upholstery = null;

    public Furniture(M matrerial, U upholstery){
        this.matrerial = matrerial;
        this.upholstery = upholstery;
    }

    public M getMatrerial() {
        return matrerial;
    }

    public U getUpholstery() {
        return upholstery;
    }  
}


public static Furniture<Plywood, Cotton> cottonFurnitureWithPlywood(Plywood plywood, Cotton cotton){
    return new Furniture<>(plywood, cotton);
}

public static Furniture<Oak, Satin> satinFurnitureWithOak(Oak oak, Satin satin){
    return new Furniture<>(oak, satin);
} 

It depends on what you really want to achieve. 这取决于您真正想要实现的目标。 Creating objects and passing them around will not magically solve your domain-specific problems. 创建对象并传递它们不会神奇地解决您的特定于域的问题。

If you cannot think of any real behavior to add to your objects (except the validation), then it might make more sense to just store your data and read them into memory whenever you want. 如果您无法想到要添加到对象中的任何实际行为(除了验证),那么仅存储数据并在需要时将其读入内存可能更有意义。 Even treat rules as data. 甚至将规则视为数据。

Here is an example: 这是一个例子:

public class Furniture {
     String name;
     Material material;
     Upholstery upholstery;

     //getters, setters, other behavior

     public Furniture(String name, Material m, Upholstery u) {
         //Read rule files from memory or disk and do all the checks
         //Do not instantiate if validation does not pass
         this.name = name;
         material = m;
         upholstery = u;
     }
}

To specify rules, you will then create three plain text files (eg using csv format). 为了指定规则,您将创建三个纯文本文件(例如,使用csv格式)。 File 1 will contain valid values for material, file 2 will contain valid values for upholstery, and file 3 will have a matrix format like the following: 文件1将包含材料的有效值,文件2将包含内饰的有效值,文件3将具有如下矩阵格式:

upholstery\material  plywood mahogany oak
cotton                  1       0      1
satin                   0       1      0

to check if a material goes with an upholstery or not, just check the corresponding row and column. 检查的材料与内饰件或没有,只是检查相应的行和列。

Alternatively, if you have lots of data, you can opt for a database system along with an ORM. 另外,如果您有大量数据,则可以选择一个数据库系统以及一个ORM。 Rule tables then can be join tables and come with extra nice features a DBMS may provide (like easy checking for duplicate values). 然后,规则表可以是联接表,并具有DBMS可能提供的其他出色功能(例如,轻松检查重复值)。 The validation table could look something like: 验证表可能类似于:

MaterialID UpholsteryID Compatability_Score
plywood    cotton          1
oak        satin           0

The advantage of using this approach is that you quickly get a working application and you can decide what to do as you add new behavior to your application. 使用这种方法的优点是,您可以快速获得一个可运行的应用程序,并且可以在向应用程序中添加新行为时决定要做什么。 And even if it gets way more complex in the future (new rules, new data types, etc) you can use something like the repository pattern to keep your data and business logic decoupled. 并且即使将来变得越来越复杂(新规则,新数据类型等),您也可以使用诸如存储库模式之类的方法来保持数据与业务逻辑的分离。

Notes about Enums: 关于枚举的注意事项:

Although the solution suggested by @Igwe Kalu solves the specific case described in the question, it is not scalable. 尽管@Igwe Kalu建议的解决方案解决了问题中描述的特定情况,但它不可扩展。 What if you want to find what material goes with a given upholstery (the reverse case)? 如果要查找给定装饰的材料是什么(相反的情况),该怎么办? You will need to create another enum which does not add anything meaningful to the program, or add complex logic to your application. 您将需要创建另一个枚举,该枚举不会对程序添加任何有意义的内容,也不会在应用程序中添加复杂的逻辑。

This is a more detailed description of the idea I threw out there in the comment: 这是我在评论中提出的想法的更详细描述:

Keep Furniture a POJO, ie, just hold the data, no behavior or rules implemented in it. 保持Furniture为POJO,即仅保存数据,不执行任何行为或规则。

Implement the rules in separate classes, something along the lines of: 在单独的类中实施规则,大致类似于:

interface FurnitureRule {
    void validate(Furniture furniture) throws FurnitureRuleException;
}

class ValidMaterialRule implements FurnitureRule {
    // this you can load in whatever way suitable in your architecture -
    // from enums, DB, an XML file, a JSON file, or inject via Spring, etc.
    private Set<String> validMaterialNames;

    @Overload
    void validate(Furniture furniture) throws FurnitureRuleException {
        if (!validMaterialNames.contains(furniture.getMaterial()))
            throws new FurnitureRuleException("Invalid material " + furniture.getMaterial());
    }
}

class UpholsteryRule implements FurnitureRule {
    // Again however suitable to implement/config this
    private Map<String, Set<String>> validMaterialsPerUpholstery;

    @Overload
    void validate(Furniture furniture) throws FurnitureRuleException {
        Set<String> validMaterialNames = validMaterialsPerUpholstery.get(furniture.getUpholstery();
        if (validMaterialNames != null && !validMaterialNames.contains(furniture.getMaterial()))
            throws new FurnitureRuleException("Invalid material " + furniture.getMaterial() + " for upholstery " + furniture.getUpholstery());
    }
}

// and more complex rules if you need to

Then have some service along the lines of FurnitureManager . 然后按照FurnitureManager进行一些服务。 It's the "gatekeeper" for all Furniture creation/updates: 它是所有Furniture创建/更新的“关守”:

class FurnitureManager {
    // configure these via e.g. Spring.
    private List<FurnitureRule> rules;

    public void updateFurniture(Furniture furniture) throws FurnitureRuleException {
        rules.forEach(rule -> rule.validate(furniture))
        // proceed to persist `furniture` in the database or whatever else you do with a valid piece of furniture.
    }
}

material should be of type Enum. material应为Enum类型。

public enum Material {
   MAHOGANY,
   TEAK,
   OAK,
   ...

}

Furthermore you can have a validator for Furniture that contains the logic which types of Furniture make sense, and then call that validator in every method that can change the material or upholstery variable (typically only your setters). 此外,您可以为“ Furniture提供一个验证器,其中包含“ Furniture类型有意义的逻辑,然后在可以更改materialupholstery变量的每种方法中调用该验证器(通常仅使用设置器)。

public class Furniture {

  private Material material;
  private Upholstery upholstery; //Could also be String depending on your needs of course

  public void setMaterial(Material material) {
     if (FurnitureValidator.isValidCombination(material, this.upholstery)) {
        this.material = material;
     }
  }

  ...

  private static class FurnitureValidator {
     private static boolean isValidCombination(Material material, Upholstery upholstery) {
        switch(material) {
           case MAHOGANY: return upholstery != Upholstery.COTTON;
                break;
           //and so on
        }

     }   
  }
}

Probably the approach I'd use (because it involves the least amount of code and it's reasonably fast) is to "flatten" the hierarchical logic into a one-dimensional Set of allowed value combinations. 我可能使用的方法(因为它涉及最少的代码量并且相当快)是将层次逻辑“展平”为一维允许值组合集。 Then when setting one of the fields, validate that the proposed new combination is valid. 然后,在设置其中一个字段时,请验证建议的新组合是否有效。 I'd probably just use a Set of concatenated Strings for simplicity. 为了简单起见,我可能只使用一组串联的字符串。 For the example you give above, something like this: 对于上面给出的示例,如下所示:

class Furniture {
  private String wood;
  private String upholstery;

  /**
   * Set of all acceptable values, with each combination as a String.
   * Example value: "plywood:cotton"
   */
  private static final Set<String> allowed = new HashSet<>();

  /**
   * Load allowed values in initializer.
   *
   * TODO: load allowed values from DB or config file
   * instead of hard-wiring.
   */
  static {
    allowed.add("plywood:cotton");
    ...
  }

  public void setWood(String wood) {
    if (!allowed.contains(wood + ":" + this.upholstery)) {
      throw new IllegalArgumentException("bad combination of materials!");
    }
    this.wood = wood;
  }

  public void setUpholstery(String upholstery) {
    if (!allowed.contains(this.wood + ":" + upholstery)) {
      throw new IllegalArgumentException("bad combination of materials!");
    }
    this.upholstery = upholstery;
  }

  public void setMaterials(String wood, String upholstery) {
    if (!allowed.contains(wood + ":" + upholstery)) {
      throw new IllegalArgumentException("bad combination of materials!");
    }
    this.wood = wood;
    this.upholstery = upholstery;
  }

  // getters
  ...
}

The disadvantage of this approach compared to other answers is that there is no compile-time type checking. 与其他答案相比,此方法的缺点是没有编译时类型检查。 For example, if you try to set the wood to plywoo instead of plywood you won't know about your error until runtime. 例如,如果您尝试将木材设置为plywoo而不是plywood ,则直到运行时您才知道错误。 In practice this disadvantage is negligible since presumably the options will be chosen by a user through a UI (or through some other means), so you won't know what they are until runtime anyway. 在实践中,此缺点可以忽略不计,因为这些选项可能是由用户通过UI(或通过其他方式)选择的,因此无论如何您都不会知道它们是什么。 Plus the big advantage is that the code will never have to be changed so long as you're willing to maintain a list of allowed combinations externally. 加上最大的好处是,只要您愿意在外部维护允许的组合列表,就永远不必更改代码。 As someone with 30 years of development experience, take my word for it that this approach is far more maintainable. 作为拥有30年开发经验的人,请相信这个方法更容易维护。

With the above code, you'll need to use setMaterials before using setWood or setUpholstery , since the other field will still be null and therefore not an allowed combination. 使用上面的代码,您需要在使用setWoodsetMaterials之前使用setUpholstery ,因为另一个字段仍然为null ,因此是不允许的组合。 You can initialize the class's fields with default materials to avoid this if you want. 您可以使用默认材料初始化班级的字段,以避免这种情况。

We often are oblivious of the power inherent in enum types. 我们经常忽略enum类型固有的功能。 The Java™ Tutorials clearly states "you should use enum types any time you need to represent a fixed set of constants." Java™教程明确指出: “只要需要表示固定的一组常量,就应使用枚举类型。”

How do you simply make the best of enum in resolving the challenge you presented? 您如何简单地利用enum来解决您提出的挑战? - Here goes: - 开始:

public enum Material {
    MAHOGANY( "satin", "velvet" ),
    PLYWOOD( "leather" ),
    // possibly many other materials and their matching fabrics...
    OAK( "some other fabric - 0" ),
    WALNUT( "some other fabric - 0", "some other fabric - 1" );

    private final String[] listOfSuitingFabrics;

    Material( String... fabrics ) {
        this.listOfSuitingFabrics = fabrics;
    }

    String[] getListOfSuitingFabrics() {
        return Arrays.copyOf( listOfSuitingFabrics );
    }

    public String toString() {
        return name().substring( 0, 1 ) + name().substring( 1 );
    }
}

Let's test it: 让我们测试一下:

public class TestMaterial {

    for ( Material material : Material.values() ) {
        System.out.println( material.toString() + " go well with " + material.getListOfSuitingFabrics() );
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java变量范围 - 实例和类变量 - Java Variable Scope - Instance and class variables 在Eclipse中检查Java类实例变量值 - Inspecting java class instance variable values in Eclipse 创建一个实例变量,该变量表示具有相同名称但在不同类中具有唯一值的其他变量(Java) - Creating an instance variable that represents other variables with same names but unique values in different classes (Java) 如何在其他 class 中使用来自一个 Class 的实例变量? - how to use a instance variable from one Class in other class? (Java)更改类实例中的变量 - (Java) Changing variables in a instance of a class Java 构造函数调用和更改父 class 实例变量的值 - Java constructor calling and changing values of parent class instance variables java存储实例变量并使用键值之一进行访问 - java storing instance variables and access using one of the key values 将一个类的实例变量设置为另一类的实例变量,然后 - Setting the instance variables of one class to the instance variables of another class and 一个实例变量用于多个值 - One instance variable for multiple values 一个实例如何更改同一类的另一个实例的某些变量 - How can one instance change some variable of the other instance of the same class
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM