【问题标题】:Appropriate Java design pattern to avoid method duplication适当的 Java 设计模式以避免方法重复
【发布时间】:2016-10-25 11:04:54
【问题描述】:

我有这种情况。我开始使用一个“处理”文档的系统。问题是,它似乎是典型的场景,它开始时很小,然后越来越大,一次构建一个块,现在需要重构。

每个文档类型都有一个标识符 (docID),它们都共享相同的底层结果结构。

有一个庞大的大师班可以完成所有的工作,但是在这个班里面有几种方法(每个站点几乎都有一个)具有自己的逻辑。它们几乎都做同样的事情,只是稍作更改(即在将字符串设置为结果结构中的字段之前格式化字符串或进行一些计算,然后在结果结构中设置字段)。

例如:

private Result processDocGeneric(Result result){
            result.setField1("value1");
            result.setField2("value2");
            result.setField3("value3");
            return result;
        }

private Result processDoc1(Result result){
            result.setField1("VALUE1");
            return result;
        }

private Result processDoc2(Result result){
            result.setField2("V-A-L-U-E-2");
            return result;
        }

private void processDocs(){
            Result result = new Result();
            result = processDocGeneric(result);
            if(docID == 1){
                result = processDoc1(result);
            }
            else if(docID == 2){
                result = processDoc2(result);
            }
            ...
        }

好的,所以我打算重构它,并且我正在考虑一些我知道的设计模式,但我不希望有一种我正在用火箭筒杀死蟑螂的感觉。

命令模式可能是我首先想到的,也是策略模式。我对这些的主要担心是,我必须为每个文档类型创建一个类,这些文档类型都有自己的 processDoc 方法实现(目前大约有 15 个)。我的意思是,如果这是要走的路,那就是它,但如果有一种我不知道的更简单的方法,那就更好了(因为改变是在一个方法中)。

我可以做的另一件事是将所有这些方法移动到“方法”类,并将 if-else 块移动到带有 docID 参数(process(int docID) 的单个方法,然后从主类调用它。但这只是分裂了庞大的班级。它会“更干净”,但不是最佳的。

清理和拆分这个庞大的类并使其可扩展的最佳方法是什么(因为将来会添加新的文档类型)?

【问题讨论】:

  • 那(写 15 个类)肯定是要走的路。面向对象意味着每个实体都有自己的类。不要创建"God" classes
  • 我可能想说 Result 的返回是非常不必要的,因为您仍在处理您确实传递给方法的 Result 的同一实例
  • 策略模式应该是这里的方法,IMO。是的,它确实需要您为每个不同的实现创建一个类,但同时它以一种在需要新实现时不会破坏现有代码并且也很容易插入到应用程序。
  • 策略模式看起来不错,但确实会增加类数。此外,如果Result 类不能轻松地进行子分类,并且如果此代码已本地化,那么Visitor 模式将是很好的选择。您介意分享调用这些private 方法的代码吗?也许是为您提供Resultinstances 的代码。然后我会写一个anwser。
  • 你有单元测试吗?

标签: java design-patterns


【解决方案1】:

您可以使用工厂或抽象工厂设计模式,在这种模式中,您可以获得所需的对象,而无需指定将要创建的对象的确切类。

【讨论】:

    【解决方案2】:

    我提出了一个基于Visitable / Visitor Pattern 的解决方案。这个解决方案只需要Result 类进行非常小的改动,同时为新的访问对象打开了大门,使其成为一个易于扩展的框架。我正在大量使用 Java8 的默认接口方法。

    Visitor / Visitable 接口:

    public interface DocVisitor<T extends VisitableDoc> {
        default void visit(T document){
            switch(document.getDocId()){
                case 1:
                    processDoc1(document);
                    break;
                case 2:
                    processDoc2(document);
                    break;
                // ... other cases...
                default:
                    processDocGeneric(document);
                    break;
            }
        }
        void processDocGeneric(VisitableDoc document);
        void processDoc1(VisitableDoc document);
        void processDoc2(VisitableDoc document);
    }
    
    public interface VisitableDoc {
        int getDocId();
        default void visit(DocVisitor visitor){
            visitor.visit(this);
        }
    }
    

    Result 类的轻微修改:

    public class Result implements VisitableDoc { // New interface declared
        int getDocId(){
            return docId; // This might already exist
        }
        // Rest is unchanged, the default implementation will suffice
    }
    

    访客实现:

    public class DocProcessor implements DocVisitor<Result> {
        @Override
        private Result processDocGeneric(Result result){
            result.setField1("value1");
            result.setField2("value2");
            result.setField3("value3");
            return result;
        }
        @Override
        private Result processDoc1(Result result){
            result.setField1("VALUE1");
            return result;
        }
        @Override
        private Result processDoc2(Result result){
            result.setField2("V-A-L-U-E-2");
            return result;
        }
    }
    

    用法:

    public static final main(String[] args){
         List<Result> results = // Obtain results somehow
         DocProcessor processor = new DocProcessor();
         for(Result result: results){
             processor.visit(result);
         }
    }
    

    [如何]拆分这个庞大的类并使其可扩展(因为将来会添加新的文档类型

    我所做的只是将 Result 类上的文档数据/DocProcessor 类上的文档处理拆分。如果你有其他处理不同类型,并且可以提取到外部类(不需要私有字段处理,私有方法调用等),这个框架操作系统完全适用。

    如果不是,您应该真的考虑重构它以使用多态性!使每个 Document 类型成为自己的对象。使用强大的抽象类将它们全部链接起来,如果您有许多方法在多个但不是所有类型之间共享,则相应地创建子类型 - 或使用默认方法! Java8 FTW

    【讨论】:

    • 感谢您的完整回答。我会在几天后尝试,所以我还不能将其标记为正确。但它确实很有用。
    【解决方案3】:

    适用于这种情况builder pattern

    /**
    * 
    * Hero, the class with many parameters.
    * 
    */
    public final class Hero {
    
    private final Profession profession;
    private final String name;
    private final HairType hairType;
    private final HairColor hairColor;
    private final Armor armor;
    private final Weapon weapon;
    
    private Hero(Builder builder) {
      this.profession = builder.profession;
      this.name = builder.name;
      this.hairColor = builder.hairColor;
      this.hairType = builder.hairType;
      this.weapon = builder.weapon;
      this.armor = builder.armor;
    }
    
    public Profession getProfession() {
      return profession;
    }
    
    public String getName() {
      return name;
    }
    
    public HairType getHairType() {
      return hairType;
    }
    
    public HairColor getHairColor() {
      return hairColor;
    }
    
    public Armor getArmor() {
      return armor;
    }
    
    public Weapon getWeapon() {
      return weapon;
    }
    
    @Override
    public String toString() {
    
      StringBuilder sb = new StringBuilder();
      sb.append("This is a ")
              .append(profession)
              .append(" named ")
              .append(name);
      if (hairColor != null || hairType != null) {
        sb.append(" with ");
        if (hairColor != null) {
          sb.append(hairColor).append(' ');
        }
        if (hairType != null) {
          sb.append(hairType).append(' ');
        }
        sb.append(hairType != HairType.BALD ? "hair" : "head");
      }
      if (armor != null) {
        sb.append(" wearing ").append(armor);
      }
      if (weapon != null) {
        sb.append(" and wielding a ").append(weapon);
      }
      sb.append('.');
      return sb.toString();
    }
    
    /**
     * 
     * The builder class.
     * 
     */
    public static class Builder {
    
      private final Profession profession;
      private final String name;
      private HairType hairType;
      private HairColor hairColor;
      private Armor armor;
      private Weapon weapon;
    
      /**
       * Constructor
       */
      public Builder(Profession profession, String name) {
        if (profession == null || name == null) {
          throw new IllegalArgumentException("profession and name can not be null");
        }
        this.profession = profession;
        this.name = name;
      }
    
      public Builder withHairType(HairType hairType) {
        this.hairType = hairType;
        return this;
      }
    
      public Builder withHairColor(HairColor hairColor) {
        this.hairColor = hairColor;
        return this;
      }
    
      public Builder withArmor(Armor armor) {
        this.armor = armor;
        return this;
      }
    
      public Builder withWeapon(Weapon weapon) {
        this.weapon = weapon;
        return this;
      }
    
      public Hero build() {
        return new Hero(this);
      }
    }
    }
    

    【讨论】:

    • 当有一个对象正在构建中时使用构建器模式,而问题是关于一种具有多个实现的方法。看看这个...stackoverflow.com/questions/328496/…
    • 嗯,我不太确定 Builder Pattern 如何适合我的场景,但感谢您的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 2010-10-01
    • 2017-09-13
    • 1970-01-01
    相关资源
    最近更新 更多