如果在验证规则中使用 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.
关于我的案例的类型代码的其他事实:
- 类型代码不影响class
的行为
- 类型代码是不可变的
- ItemsManager 使用类型代码在添加之前解析一些规则
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();
}
假设我们必须在 collection 中添加新元素之前检查一些规则集。元素是 objects 几种相似类型。所有特定于类型的功能都封装在抽象 class 的子 class 中。 Collection 包含此摘要 class 的 objects。这些规则适用于类型的条件以及其他约束。因此,项目的抽象 superclass 具有额外的类型代码。可以向 collection 添加新元素,但由于附加规则,可以删除或替换 collection 中的其他元素。
在需要重构的代码中,规则的验证被实现为一长串带有嵌套控制流语句的代码。类型代码的验证会破坏封装。不能将控制流语句的单独分支定义为 collection 元素的相应子 class 的方法,因为它们需要检查类型并更改 collection.
关于我的案例的类型代码的其他事实:
- 类型代码不影响class 的行为
- 类型代码是不可变的
- ItemsManager 使用类型代码在添加之前解析一些规则 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();
}