mercredi 28 janvier 2015

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

Let we have to check some set of rules before to add new element in collection. Elements are objects of a few similar types. All type specific features are encapsulated in subclasses of abstract class. Collection contains objects of this abstract class. The rules apply conditions for types along with other constraints. For that reason the abstract superclass of items has additional type code. New element can be added to collection but due to additional rules other elements in collection can be removed or replaced.


In code that need to be refactored validation of the rules is implemented as one long block of code with nested control flow statements. Validation of the type code breaks encapsulation. Separate branches of the control flow statements cannot be defined as method of corresponding subclasses of collection elements because them need to check type and make changes to collection.


additional facts regarding type code in my case:



  • type code does not affect the behaviour of class

  • type code is immutable

  • type code is used by ItemsManager to resolve some rules before to add new element to collection.


How to eliminate type code and separate rules from types?


Here is example of such problem:


Type specific features of Items are encpsulated in AbstractItem subclasses.

add method of ItemManager class breaks encapsulation.

Rule: item of Type2 must be removed if new item of Type1 with the same value of SomeUsefull property is adding to collection.


For simplicity implementation of ICloneable and IComparable interfaces is omitted. In real world items in collection are immutable and cloneable and the system of rules is quite tangled.



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
*/

Aucun commentaire:

Enregistrer un commentaire