2015-05-25 14 views
20

Tôi đang triển khai một công cụ quy tắc trong Java. Công cụ quy tắc của tôi xác định trước danh sách các quy tắc và quy tắc độc lập. Một quy tắc ở đây chỉ đơn giản là một phần của logic. Và một bộ quy tắc kết hợp các quy tắc đơn giản này thành một tập hợp có thứ tự.Mẫu/kiểu thiết kế hiệu quả cho việc thiết kế một công cụ quy tắc trong Java là gì?

Tôi là một nhà phát triển java phong nha nhưng không phải là một Guru. Đồng nghiệp của tôi đề nghị tôi hai thiết kế cho mục đích này. Tôi không hài lòng với cả hai thiết kế, do đó câu hỏi này.

Ví dụ về một quy tắc trong dự án của tôi: Giả sử đầu vào là địa điểm tại Mỹ cho ví dụ, Santa Barbara, CA, USA hoặc OH, mà Mỹ thường là trong một số định dạng được xác định rõ với các thành phố, tiểu bang và đất nước lĩnh vực. Sau đó, tôi có thể có một số quy tắc như sau:

Quy tắc 1: Thành phố không phải là null
Quy tắc 2: Nhà nước không phải là null
Quy tắc 3: Country là Mỹ hoặc Mỹ
Quy tắc 4: chiều dài Nhà nước bằng 2

Ví dụ về một ruleset trong dự án của tôi:

RULESET: Vị trí hợp lệ Quy tắc này là tập hợp theo thứ tự các quy tắc được xác định ở trên.

Hai mẫu thiết kế tôi đã thực hiện như sau:

Thiết kế 1: Sử dụng Enum với các lớp Anonymous Nội

Rule.java

public interface Rule { 
    public Object apply(Object object); 
} 

NlpRule.java

public enum NlpRule { 
    CITY_NOT_NULL(new Rule() { 

     @Override 
     public Object apply(Object object) { 
      String location = (String) object; 
      String city = location.split(",")[0]; 
      if (city != null) { 
       return true; 
      } 
      return false; 
     } 

    }), 

    STATE_NOT_NULL(new Rule() { 

     @Override 
     public Object apply(Object object) { 
      String location = (String) object; 
      String state = location.split(",")[1]; 
      if (state != null) { 
       return true; 
      } 
      return false; 
     } 

    }), 

    COUNTRY_US(new Rule() { 

     @Override 
     public Object apply(Object object) { 
      String location = (String) object; 
      String country = location.split(",")[2]; 
      if (country.equals("US") || country.equals("USA")) { 
       return true; 
      } 
      return false; 
     } 

    }), 

    STATE_ABBREVIATED(new Rule() { 

     @Override 
     public Object apply(Object object) { 
      String location = (String) object; 
      String state = location.split(",")[1]; 
      if (state.length() == 2) { 
       return true; 
      } 
      return false; 
     } 

    }); 

    private Rule rule; 

    NlpRule(Rule rule) { 
     this.rule = rule; 
    } 

    public Object apply(Object object) { 
     return rule.apply(object); 
    } 
} 

RuleSet.java

public class RuleSet { 
    private List<NlpRule> rules; 

    public RuleSet() { 
     rules = new ArrayList<NlpRule>(); 
    } 

    public RuleSet(List<NlpRule> rules) { 
     this.rules = rules; 
    } 

    public void add(NlpRule rule) { 
     rules.add(rule); 
    } 

    public boolean apply(Object object) throws Exception { 
     boolean state = false; 
     for (NlpRule rule : rules) { 
      state = (boolean) rule.apply(object); 
     } 
     return state; 
    } 
} 

RuleSets.java

public class RuleSets { 
    private RuleSets() { 

    } 

    public static RuleSet isValidLocation() { 
     RuleSet ruleSet = new RuleSet(); 
     ruleSet.add(NlpRule.CITY_NOT_NULL); 
     ruleSet.add(NlpRule.STATE_NOT_NULL); 
     ruleSet.add(NlpRule.COUNTRY_US); 
     ruleSet.add(NlpRule.STATE_ABBREVIATED); 
     return ruleSet; 
    } 
} 

Main.java

public class Main { 
    public static void main(String... args) { 
     String location = "Santa Barbara,CA,USA"; 
     RuleSet ruleSet = RuleSets.isValidLocation(); 
     try { 
      boolean isValid = (boolean) ruleSet.apply(location); 
      System.out.println(isValid); 
     } catch (Exception e) { 
      e.getMessage(); 
     } 
    } 
} 

Thiết kế 2: Sử dụng Abstract class

NlpRule.java

public abstract class NlpRule { 

    public abstract Object apply(Object object); 

    public final static NlpRule CITY_NOT_NULL = new NlpRule() { 
     public Object apply(Object object) { 
      String location = (String) object; 
      String city = location.split(",")[0]; 
      if (city != null) { 
       return true; 
      } 
      return false; 

     } 

    }; 

    public final static NlpRule STATE_NOT_NULL = new NlpRule() { 
     public Object apply(Object object) { 
      String location = (String) object; 
      String city = location.split(",")[0]; 
      if (city != null) { 
       return true; 
      } 
      return false; 

     } 

    }; 

    public final static NlpRule COUNTRY_US = new NlpRule() { 
     public Object apply(Object object) { 
      String location = (String) object; 
      String country = location.split(",")[2]; 
      if (country.equals("US") || country.equals("USA")) { 
       return true; 
      } 
      return false; 

     } 

    }; 

    public final static NlpRule STATE_ABBREVIATED = new NlpRule() { 
     public Object apply(Object object) { 
      String location = (String) object; 
      String state = location.split(",")[1]; 
      if (state.length() == 2) { 
       return true; 
      } 
      return false; 
     } 

    }; 

} 

ruleset.java

public class RuleSet { 
    private List<NlpRule> rules; 

    public RuleSet() { 
     rules = new ArrayList<NlpRule>(); 
    } 

    public RuleSet(List<NlpRule> rules) { 
     this.rules = rules; 
    } 

    public void add(NlpRule rule) { 
     rules.add(rule); 
    } 

    public boolean apply(Object object) throws Exception { 
     boolean state = false; 
     for (NlpRule rule : rules) { 
      state = (boolean) rule.apply(object); 
     } 
     return state; 
    } 
} 

RuleSets.java

import com.hgdata.design.one.NlpRule; 
import com.hgdata.design.one.RuleSet; 

public class RuleSets { 
    private RuleSets() { 

    } 

    public static RuleSet isValidLocation() { 
     RuleSet ruleSet = new RuleSet(); 
     ruleSet.add(NlpRule.CITY_NOT_NULL); 
     ruleSet.add(NlpRule.STATE_NOT_NULL); 
     ruleSet.add(NlpRule.COUNTRY_US); 
     ruleSet.add(NlpRule.STATE_ABBREVIATED); 
     return ruleSet; 
    } 
} 

Main.java

public class Main { 
    public static void main(String... args) { 
     String location = "Santa Barbara,CA,USA"; 
     RuleSet ruleSet = RuleSets.isValidLocation(); 
     try { 
      boolean isValid = (boolean) ruleSet.apply(location); 
      System.out.println(isValid); 
     } catch (Exception e) { 
      e.getMessage(); 
     } 
    } 
} 

Better Thiết kế tiếp cận/Pattern? Như bạn có thể thấy, thiết kế 2 loại bỏ giao diện và enum. Thay vào đó, nó sử dụng một lớp trừu tượng. Tôi vẫn tự hỏi nếu có một mẫu thiết kế tốt hơn/cách tiếp cận để thực hiện như vậy.

õ sử dụng khối initializer:

Bây giờ trong trường hợp của cả hai thiết kế trên. Nói, nếu tôi cần phải nhanh chóng một lớp bên ngoài để sử dụng nó bên trong logic áp dụng của tôi, sau đó tôi buộc phải sử dụng các khối khởi tạo mà tôi không hoàn toàn nhận thức được liệu đó có phải là một thực hành tốt hay không. Xem ví dụ cho một kịch bản như vậy dưới đây:

Thiết kế 1:

... 
STATE_ABBREVIATED(new Rule() { 
     private CustomParser parser; 

     { 
      parser = new CustomParser(); 
     } 

     @Override 
     public Object apply(Object object) { 
      String location = (String) object; 
      location = parser.parse(location); 
      String state = location.split(",")[1]; 
      if (state.length() == 2) { 
       return true; 
      } 
      return false; 
     } 

    }); 
... 

Thiết kế 2:

... 
public final static NlpRule STATE_ABBREVIATED = new NlpRule() { 
     private CustomParser parser; 

     { 
      parser = new CustomParser(); 
     } 
     public Object apply(Object object) { 
      String location = (String) object; 
      location = parser.parse(location); 
      String state = location.split(",")[1]; 
      if (state.length() == 2) { 
       return true; 
      } 
      return false; 
     } 

    }; 
... 

chuyên gia Java xin đúc một số ánh sáng! Ngoài ra xin vui lòng xác định nếu bạn tìm thấy bất kỳ sai sót trong hai thiết kế trên. Tôi cần biết những ưu và khuyết điểm liên quan đến từng thiết kế để giúp tôi đưa ra quyết định đúng đắn. Tôi đang xem xét lambdas, predicates và một số mẫu khác theo gợi ý của một số người dùng trong các ý kiến.

+0

Không gây phiền toái, nhưng tại sao đây không phải là vấn đề về regex? Regex là một "ruleset" khá phát triển và được thử nghiệm chống lại các chuỗi. Thậm chí nếu bạn muốn chia các regex ra thành các đối tượng riêng của chúng, bạn có thể 1) sử dụng các đối tượng đó trong một mảng, 2) yêu cầu mỗi đối tượng cho regex 3) biên dịch thành regex lớn hơn (và đó có thể là ruleset của bạn) – LLFourn

+0

@LLFourm Các quy tắc tôi đã trình bày ở trên thực sự đơn giản. Tôi có hơn 100 quy tắc mà tôi sẽ cần phải thực hiện. Và hầu hết các quy tắc có logic phức tạp và phụ thuộc vào các lớp khác. Đây không thực sự là một vấn đề về regex. – Shankar

+0

Sử dụng JavaCompilerAPI: http://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html.Không thể nói nhiều hơn, tôi không biết nhiều về java. – Fendy

Trả lời

4

Có rất nhiều (mã nguồn mở) động cơ quy tắc Java ra đã có - xem http://java-source.net/open-source/rule-engines & http://drools.org/

Bạn có thể bắt đầu với việc sử dụng/kiểm tra các nguồn cho một trong những người (tham gia ghi lại nơi mà nó không đáp ứng yêu cầu của bạn) và đi từ đó.

+0

Nhìn vào các bản tóm tắt các triển khai hiện tại (đối với ứng dụng cụ thể của bạn) sẽ giúp bạn tinh chỉnh thiết kế của mình để nó giải quyết những sự kiện ngắn đó. – eddiewould

+1

Tôi đã nhìn vào những giọt nước hoa và cảm thấy nó quá nặng nề cho vấn đề của tôi. Tôi cần một thiết kế thực sự đơn giản và hiệu quả vì các quy tắc của tôi hoàn toàn độc lập với nhau khác và cũng là ý tưởng của việc có một ruleset làm cho nhóm và áp dụng các quy tắc thực sự thẳng về phía trước – Shankar

+0

@Shankar - Có một bó nhiều hơn trên trang đó (mỗi haps một trong đó là đủ nhẹ). Khá công bằng nếu không. – eddiewould

6

Đây là một câu hỏi thú vị với nhiều câu trả lời có thể có. Ở một mức độ nào đó, giải pháp sẽ phụ thuộc vào sở thích cá nhân. Tôi thường gặp các vấn đề tương tự và có các khuyến nghị sau đây. Lưu ý rằng những công việc này cho tôi nhưng có thể không phù hợp với nhu cầu của bạn.

  1. Sử dụng enum. Về lâu dài, tôi cảm thấy họ có nhiều lợi thế hơn private static thành viên về kiểm tra lỗi và các vùng chứa hữu ích (EnumSet, v.v.) có thể sử dụng chúng một cách hiệu quả.

  2. Sử dụng giao diện trên lớp trừu tượng. Trước Java 8 có những lý do hữu ích để sử dụng các lớp trừu tượng. Với default thành viên hiện tại không có lý do chính đáng (chỉ là ý kiến ​​của tôi - tôi chắc rằng những người khác sẽ không đồng ý). Một enum có thể thực hiện một giao diện.

  3. Trong Java 8, logic được liên kết với mỗi 'quy tắc' có thể được nhúng trong một biểu thức lambda làm cho mã khởi tạo cho enums của bạn rõ ràng hơn.

  4. Giữ lambdas rất ngắn - chỉ một hoặc hai lệnh nhiều nhất (và tốt nhất là một biểu thức không có khối). Điều này có nghĩa là tách bất kỳ logic phức tạp thành một phương pháp riêng biệt.

  5. Sử dụng các enums riêng để phân loại các quy tắc của bạn. Không có lý do chính đáng để đưa tất cả chúng vào một và bằng cách tách chúng ra, bạn có thể làm cho các nhà xây dựng đơn giản bằng cách chính xác các biểu thức lambda có liên quan đến miền của họ. Xem ví dụ của tôi dưới đây để xem những gì tôi có ý nghĩa.

  6. Nếu bạn có phân cấp quy tắc, hãy sử dụng mẫu thiết kế tổng hợp. Nó linh hoạt và mạnh mẽ.

Vì vậy, việc đưa những khuyến nghị cùng tôi xin đề nghị một cái gì đó như:

interface LocationRule{ 
    boolean isValid(Location location); 
} 

enum ValidValueRule implements LocationRule { 
    STATE_NOT_NULL(location -> location.getState() != null), 
    CITY_NOT_NULL(location -> location.getCity() != null); 

    private final Predicate<Location> locationPredicate; 
    ValidValueRule(Predicate<Location> locationPredicate) { 
     this.locationPredicate = locationPredicate; 
    } 

    public boolean isValid(Location location) { 
     return locationPredicate.test(location); 
    } 
} 

enum StateSizeRule implements LocationRule { 
    IS_BIG_STATE(size -> size > 1000000), 
    IS_SMALL_STATE(size -> size < 1000); 

    private final Predicate<Integer> sizePredicate; 
    StateSize(Predicate<Integer> sizePredicate) { 
     this.sizePredicate = sizePredicate; 
    } 
    public boolean isValid(Location location) { 
     return sizePredicate.test(location.getState().getSize()); 
    } 
} 

class AllPassRule implements LocationRule { 
    private final List<LocationRule > rules = new ArrayList<>(); 
    public void addRule(LocationRule rule) { 
     rules.add(rule); 
    } 
    public boolean isValid(Location location) { 
     return rules.stream().allMatch(rule -> rule.isValid(location)); 
    } 
} 

class AnyPassRule implements LocationRule { 
    private final List<LocationRule > rules = new ArrayList<>(); 
    public void addRule(LocationRule rule) { 
     rules.add(rule); 
    } 
    public boolean isValid(Location location) { 
     return rules.stream().anyMatch(rule -> rule.isValid(location)); 
    } 
} 

class NegateRule implements LocationRule { 
    private final Rule rule; 
    public NegateRule(Rule rule) { 
     this.rule = rule; 
    } 
    public boolean isValid(Location location) { 
     return !rule.isValid(location); 
    } 
} 

Đây có thể được sử dụng như:

AnyPassRule anyPass = new AnyPassRule(); 
anyPass.addRule(ValidValueRule.STATE_NOT_NULL); 
anyPass.addRule(new NegateRule(StateSize.IS_SMALL_STATE)); 
anyPass.isValid(location); 

Và vân vân.

3

Giao diện với các lĩnh vực tĩnh:

public interface NlpRule 
{ 
    Object apply(Object object); 

    NlpRule CITY_NOT_NULL = object -> 
    { 
     String location = (String) object; 
     String city = location.split(",")[0]; 
     return ...true/false; 
    }; 

    // etc. 

Một số có thể thích phương pháp trên đối tượng chức năng

public interface NlpRule 
{ 
    Object apply(Object object); 

    static boolean cityNotNull(Object object) // java8: static method in interface 
    { 
     String location = (String) object; 
     String city = location.split(",")[0]; 
     return ...true/false; 
    }; 

    // etc. 

} 

// use method reference as functional object 

NlpRule rule = NlpRule::cityNotNull; 

ruleset.add(NlpRule::cityNotNull); 

Hoặc bạn có thể có cả hai phương pháp và lĩnh vực

public interface NlpRule 
{ 
    Object apply(Object object); 

    NlpRule CITY_NOT_NULL = NlpRule::cityNotNull; 
    static boolean cityNotNull(Object object) 
    { 
     ... 
    }; 

Ví dụ rul es là tất cả String->boolean, không chắc chắn tại sao NlpRule là Object->Object. Nếu các quy tắc thực sự có thể chấp nhận/trả lại các loại khác nhau, bạn có thể nên mở rộng NlpRule<T,R>.


Các CustomParser thể được lưu trữ trong một lớp helper gói tin

class NlpRuleHelper 
{ 
    static final CustomParser parser = new CustomParser(); 
} 

-- 

public interface NlpRule 
... 
    NlpRule STATE_ABBREVIATED = object -> 
    { 
     ... 
     location = NlpRuleHelper.parser.parse(location); 
3

Một câu trả lời có thể là sử dụng một phân tích cú pháp DSL để hợp lệ quy tắc của bạn, trong ngôn ngữ lập trình hàm, có là điều được gọi là phân tích cú pháp tổ hợp có thể xây dựng một trình phân tích cú pháp lớn hơn (bộ quy tắc) từ trình phân tích cú pháp cơ bản (quy tắc) khác nhau. Điểm tốt của cách này là tính linh hoạt, nhược điểm là mỗi khi bạn muốn thay đổi bộ quy tắc của mình, bạn phải nhập lại mã.

1

Tôi nghĩ bạn đã thêm ít nhất một lớp mã không cần thiết. Hãy nhớ rằng enum cũng có thể triển khai các giao diện và thậm chí có thể có các phương thức trừu tượng.

/** 
* I don't like `Object` so I will adjust. 
*/ 
public interface Rule { 

    public boolean pass(String s); 
} 

/** 
* All pass country codes. 
*/ 
public enum ValidCountry { 

    US, USA; 
    public static Set<String> all = new HashSet<>(); 

    static { 
     for (ValidCountry c : ValidCountry.values()) { 
      all.add(c.name()); 
     } 
    } 
} 

public enum NlpRule implements Rule { 

    CITY_NOT_NULL { 

       @Override 
       public boolean pass(String location) { 
        return location.split(",")[0] != null; 
       } 

      }, 
    STATE_NOT_NULL { 

       @Override 
       public boolean pass(String location) { 
        return location.split(",")[1] != null; 
       } 

      }, 
    COUNTRY_US { 
       @Override 
       public boolean pass(String location) { 
        return ValidCountry.all.contains(location.split(",")[2]); 
       } 

      }, 
    STATE_ABBREVIATED { 
       @Override 
       public boolean pass(String location) { 
        return location.split(",")[1].length() == 2; 
       } 

      }; 
    /** 
    * You can even make Sets of them. 
    */ 
    static Set<NlpRule> isValidLocation = EnumSet.of(CITY_NOT_NULL, STATE_NOT_NULL, COUNTRY_US, STATE_ABBREVIATED); 
} 

public void test() { 
    String test = "Santa Barbara,CA,USA"; 
    for (Rule r : NlpRule.isValidLocation) { 
     boolean pass = r.pass(test); 
     System.out.println(r + "(\"" + test + "\") - " + (pass ? "passes" : "FAILS")); 
    } 
} 
Các vấn đề liên quan