【问题标题】:ComperatorFactory using static methods or each in its own class?ComperatorFactory 使用静态方法还是在自己的类中使用?
【发布时间】:2016-01-15 10:13:52
【问题描述】:

我正在考虑实现多个 Comperators。现在我不确定如何以最有利的方式做到这一点,即仍然有能力进行依赖注入。

方法一:一类是一个比较器(见this answer):

// file LexicographicComparator.java
class LexicographicComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.name.compareToIgnoreCase(b.name);
    }
}

// file AgeComparator.java
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
    }
}

方法2:现在肯定可以这样做了:

class PersonComperatorFactory {
    public static Comparator<Person> getAgeComparator() {
        return new Comparator<Person>() {
            @Override
            public int compare(Person a, Person b) {
                return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
            }
        }
    }
    
    public static Comparator<Person> getLexicographicComparator() {
        return new Comparator<Person>() {
            @Override
            public int compare(Person a, Person b) {
                return a.name.compareToIgnoreCase(b.name);
            }
        }
    }
}

第二种方法肯定会减少你的包的混乱(把它放到 .domain 包或 .util 包中?或者创建一个新的 .domain.comperators 包?)。但这是个好主意吗?我的意思是,除了更少的文件之外,这还有什么好处吗?代码重用或 DependencyInjection 怎么样?它可能有我还看不到的缺点吗?

我希望这不是一个“选择你最喜欢的”问题,但可能有很多充分的理由选择其中一个。

非常感谢您。

PS:你会把这个放在哪个包里(这可能是个人选择)?但这只是一个适合发表评论的附带问题。

【问题讨论】:

  • 除非有充分的理由,否则我不会使用工厂类。你没有(文件少是一个荒谬的“原因”)。 KISS 原则也适用于此,从 Spring 的角度来看,如果您打算注入比较器,这也是有意义的。
  • 无论谁投了反对票,请善意地解释为什么您认为这是一个愚蠢的问题。谢谢。 BTW:的确,这不是一个真正的工厂,正如我之前所说,我自己质疑“不那么杂乱”的原因。

标签: java design-patterns compare


【解决方案1】:

在我看来,实现预期结果的最佳方法是拥有一个包含所有静态比较器的实用程序类(将其放入 .util 包中)。

例子:

public class Comparators {
    public static final Comparator<Person> PERSON_LEXICOGRAPHIC_COMPARATOR = new Comparator<Person>() {
        @Override
        public int compare(Person a, Person b) {
            return a.name.compareToIgnoreCase(b.name);
        }
    }

    public static final Comparator<Person> PERSON_AGE_COMPARATOR = new Comparator<Person>() {
        @Override
        public int compare(Person a, Person b) {
            return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
        }
    }
}

或者以 Java 8 方式:

public class Comparators {
    public static final Comparator<Person> PERSON_LEXICOGRAPHIC_COMPARATOR = (Person a, Person b) -> a.name.compareToIgnoreCase(b.name);
    public static final Comparator<Person> PERSON_AGE_COMPARATOR = (Person a, Person b) -> (a.age < b.age ? -1 : a.age == b.age ? 0 : 1);
}

为了不浪费内存和资源,我会直接使用静态实例,因为它们在比较方法中不使用任何状态属性。

【讨论】:

  • 这可能对注入不太友好,但到目前为止,对于 API 设计而言,这是最简单和/或最干净的解决方案。为 Java8 代码 +1。
  • @Ben 我认为出于依赖注入的目的,您的选项都适合,但我建议使用静态实例。 DI 的一个很好的解决方案是 Sergiy Medvynskyy 解决方案,它使用静态比较器,如果您需要非单例比较器,它也很好。如果您确定您的比较器是单例的,您可以决定使用您的单例非静态比较器实例。
  • 我将此作为公认的答案,因为我认为将比较器保留在自己的类中没有任何优势。尤其是当它们那么短时(Java 8 甚至更短)。不过,在自己的类中保留一些非常特殊的长比较器可能会很有趣。
【解决方案2】:

恕我直言,最好的方法是混合,因为您有一个入口点来获取所有比较器(您不需要知道每个比较器的名称:所有比较器都在同一个 java 类中)并且您可以实例化所有比较器作为一个豆子。

public class LexicographicComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.name.compareToIgnoreCase(b.name);
    }
}

// file AgeComparator.java
public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
    }
}

public class PersonComperatorFactory {

    private static final Comparator<Person> NAME_COMPARATOR = new LexicographicComparator();       
    private static final Comparator<Person> AGE_COMPARATOR = new AgeComparator();

    public static Comparator<Person> getLexicographicComparator() {
        return NAME_COMPARATOR;
    }

    public static Comparator<Person> getAgeComparator() {
        return AGE_COMPARATOR;
    }
}

【讨论】:

    【解决方案3】:

    实现不可变比较器的最佳方法是将其实现为单例。根据 Effective Java,第 3 项,首选方法是使用 Enum 单例:

    这种方法在功能上等同于公共字段方法,只是它更简洁,免费提供序列化机制,并提供针对多次实例化的铁定保证,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式

    至于比较器的位置,应考虑 API 的可发现性。如果您在单独的类甚至单独的包中实现比较器,您的软件用户(即其他程序员)可能会简单地忽略它们并试图重新实现它们(导致重复的代码和工作)。因此,为了帮助可发现性,创建在 Person 类本身中返回比较器的静态方法,或者采用 convention similar to Guava 并将它们放在同一个包中以对象复数形式命名的实用程序类中(例如 Persons)。使用此约定,其他程序员只需在他们最喜欢的 IDE 中键入 Person(s).,自动完成功能将显示所有可用的实用程序方法。

    完整的代码如下所示:

    public final class Persons {
      private Persons() {}
    
      public static Comparator<Person> getLexicographicComparator() {
        return Comparators.LEXICOGRAPHIC;
      }
    
      public static Comparator<Person> getAgeComparator() {
        return Comparators.AGE;
      }
    
      enum Comparators implements Comparator<Person> {
        LEXICOGRAPHIC {
          @Override
          public int compare(Person a, Person b) {
            return a.name.compareToIgnoreCase(b.name);
          }
    
          @Override
          public String toString() {
            return Persons.class.getName() + ".getLexicographicComparator()";
          }
        },
    
        AGE {
          @Override
          public int compare(Person a, Person b) {
            return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
          }
    
          @Override
          public String toString() {
            return Persons.class.getName() + ".getAgeComparator()";
          }
        }
      }
    }
    

    请注意,由于实例化实用程序类没有意义,因此构造函数是私有的(根据 Effective Java,第 4 条)。此外,为了隐藏比较器实际上是枚举的事实,枚举具有包私有(默认)可访问性,并且 toString() 方法被覆盖以指向其可公开访问的 getter 方法。

    【讨论】:

      【解决方案4】:

      我可能会将比较器实现为enum,并且我会将enum 放在与Person 相同的包中,或者将其嵌套在Person 类中,以提供对字段的直接访问进行比较。

      public enum PersonComparator implements Comparator<Person> {
          AGE {
              @Override
              public int compare(Person a, Person b) {
                  return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
              }
          },
      
          NAME {
              @Override
              public int compare(Person a, Person b) {
                  return a.name.compareToIgnoreCase(b.name);
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        Factory 版本更通用。

        调用类不必知道真正的实现类。

        此外,您可以添加一个入口点,该入口点将根据类型(此处基于公共 Enumeration)决定返回哪个实现:

        class PersonComperatorFactory {
        
            public static enum Type {
        
                AGE, LEXIC
            }
        
            public static Comparator<Person> getComparator(final Type comparatorType) {
        
                switch (comparatorType) {
        
                case AGE:
                    return getAgeComparator();
                case LEXIC:
                    return getLexicographicComparator();
                default:
                    return getLexicographicComparator();
        
                }
        
            }
        
            private static Comparator<Person> getAgeComparator() {
                return new Comparator<Person>() {
                    @Override
                    public int compare(final Person a, final Person b) {
                        return a.age < b.age ? -1 : a.age == b.age ? 0 : 1;
                    }
                };
            }
        
            private static Comparator<Person> getLexicographicComparator() {
                return new Comparator<Person>() {
                    @Override
                    public int compare(final Person a, final Person b) {
                        return a.name.compareToIgnoreCase(b.name);
                    }
                };
            }
        }
        

        【讨论】:

          【解决方案6】:

          组合很可能会产生最佳结果。 选项 1 很好地封装了逻辑,但是直接访问这些类会产生很多混乱。您的“工厂”确实解决了这个问题,但我不同意在此处实施逻辑的决定。

          如果比较器的逻辑应该改变,这需要你改变工厂,这不是它的责任。

          我建议保留您的初始类,然后从工厂返回这些类的实例。即使您有 3 个类,您也只会使用其中的 1 个,因此混乱应该是可控的。

          class PersonComperatorFactory {
              public static Comparator<Person> getAgeComparator() {
                  return new LexicographicComparator();
              }
          
              public static Comparator<Person> getLexicographicComparator() {
                  return new AgeComparator();
              }
          }
          

          我还提出了第三种选择,它提供零封装和不重用,但具有最大的灵活性。这可能不是您想要的,只是想确保它得到考虑。

          public class Demo {
          
              public static void main(String[] args) {
                  List<Person> persons = Arrays.asList(new Person(), new Person(), new Person(), new Person());
                  SortPersons(persons, (p1, p2) -> p1.age < p2.age ? -1 : p1.age == p2.age ? 0 : 1);
              }
          
              private static void SortPersons(List<Person> persons, Comparator<Person> comparator){
                  //Or what ever logic you need the comparator for
                  persons.sort(comparator);
              }
          
          }
          

          最后,如果您的 .util 包以任何方式(或能够)单独部署,我不会将 Person 的依赖项放入其中。在这种情况下,我会把它放在 .default 中。

          【讨论】:

            猜你喜欢
            • 2011-02-01
            • 2013-07-10
            • 2012-06-25
            • 2017-07-30
            • 2012-06-17
            • 1970-01-01
            • 1970-01-01
            • 2013-04-22
            • 2014-05-24
            相关资源
            最近更新 更多