如果在验证规则中使用 type-code ,如何进行重构以消除?

How to do refactoring to eliminate type-code if it is used in validation rules?

假设我们必须在 collection 中添加新元素之前检查一些规则集。元素是 objects 几种相似类型。所有特定于类型的功能都封装在抽象 class 的子 class 中。 Collection 包含此摘要 class 的 objects。这些规则适用于类型的条件以及其他约束。因此,项目的抽象 superclass 具有额外的类型代码。可以向 collection 添加新元素,但由于附加规则,可以删除或替换 collection 中的其他元素。

在需要重构的代码中,规则的验证被实现为一长串带有嵌套控制流语句的代码。类型代码的验证会破坏封装。不能将控制流语句的单独分支定义为 collection 元素的相应子 class 的方法,因为它们需要检查类型并更改 collection.

关于我的案例的类型代码的其他事实:

如何消除类型代码并将规则与类型分开?

这是此类问题的示例:

Item 的特定类型特征被封装在 AbstractItem subclasses 中。
ItemManager 的添加方法 class 打破了封装。
规则:Type2 的项目必须如果具有相同 SomeUsefull 属性 值的 Type1 的新项目被添加到 collection.

为简单起见,省略了 ICloneable 和 IComparable 接口的实现。在现实世界中,collection 中的项目是不可变且可克隆的,规则系统非常复杂。

abstract class AbstractItem {

    private int Type; // this would like to eliminate
    private int SomeUseful;

    protected AbstractItem(int Type, int Value) {
        this.Type = Type;
        this.SomeUseful = Value;
    }

    public int getType() { return this.Type; }
    public int getSomeUseful() { return this.SomeUseful; }

    @Override
    public String toString() {
        return String.format("Item{Type=%d, Value=%d}", Type, SomeUseful);
    }
}

class ItemType1 extends AbstractItem {
    ItemType1(int Value) { super(1, Value); }
}

class ItemType2 extends AbstractItem {
    ItemType2(int Value) { super(2, Value); }
}

class ItemManager {

    private java.util.ArrayList<AbstractItem> ListOfItems;

    public ItemManager(){
        this.ListOfItems = new java.util.ArrayList<AbstractItem>();
    }

    public void add(final AbstractItem newItem) {
        // this code breaks encapsulation
        switch (newItem.getType()) {
            case 1:
                // do some type dependent operations
                for(AbstractItem i: this.ListOfItems) {
                    if (i.getType()==2 && i.getSomeUseful()==newItem.getSomeUseful()) {
                        this.ListOfItems.remove(i);
                        break;
                    }
                }
                break;
            case 2:
                // do some other type dependent operations
                break;
            default:
                // throw error
        }
        this.ListOfItems.add(newItem);
    }

    @Override
    public String toString() {
        String str = String.format("ItemsManager content");
        for(AbstractItem i: this.ListOfItems) {
            str += String.format("\n\tType = %d, Value = %d", i.getType(), i.getSomeUseful());
        }
        return str;
    }
}

public class Example1 {
    public static void main(String[] arg) {
        System.out.println("Example 1");
        ItemManager im = new ItemManager();
        im.add(new ItemType1(1));
        im.add(new ItemType2(2));
        im.add(new ItemType2(3));
        im.add(new ItemType1(3));
        System.out.println(im.toString());
    }
}

/*
Example 1
ItemsManager content
    Type = 1, Value = 1
    Type = 2, Value = 2
    Type = 1, Value = 3
*/

这并不理想,但它是朝着获得一些封装和取消 switch 语句迈出的一步...

向基础 class 添加一个将列表作为参数的 onAdd 方法。

 public java.util.ArrayList<AbstractItem> onAdd(java.util.ArrayList<AbstractItem> list) { return list; }

然后在子 classes 中覆盖它,例如...

@Override
     public java.util.ArrayList<AbstractItem> onAdd(java.util.ArrayList<AbstractItem> list) {
         for(AbstractItem i: this.ListOfItems) {
                    if (i.getType()==2 && i.getSomeUseful()==this.getSomeUseful()) {
                        list.remove(i);
                        break;
                    }
                }
        return list;
    }

然后重写 ItemManager add 方法以仅调用子 classes 的 onAdd 方法...

public void add(final AbstractItem newItem) {
        this.ListOfItems = newItem.onAdd(this.ListOfItems);
        this.ListOfItems.add(newItem);
    }

从@dbugger 的回答开始,您可以进一步推动它。 您可以使用 Double Dispatch 来隐藏类型代码。仍然不是一个完美的解决方案,因为父级对其子级了解太多,但类型代码现在已经消失了。

很难说您提供的示例代码可能是更好的解决方案,因为当您简化时,您删除了有关所涉及项目的所有信息。那里可能有一些东西可以以其他方式用于歧视,让你摆脱 shoudBeRemovedBecuseType1 的双重分派。

这是类型 1 的修改后的 onAdd 方法

@Override
public List<AbstractItem> onAdd(List<AbstractItem> list)    {
    for (AbstractItem item : list)      {
        if (item.shoudBeRemovedBecauseType1(this))          {
            list.remove(item);
            break;
        }
    }
    return list;
}

基础中的新方法class

    public boolean shoudBeRemovedBecauseType1(ItemType1 itemType1)
    {
        return false;
    }

在类型 2 子中被覆盖class

    @Override
    public boolean shoudBeRemovedBecauseType1(ItemType1 itemType1)
    {
        return getSomeUseful() == itemType1.getSomeUseful();
    }