Correct approach(design pattern) for update fields in java object

匆匆过客 提交于 2019-12-13 03:49:25

问题


I have java class Person:

public class Person {
    private String name;
    private int age;
    private String marriedStatus;
    private Date dob;
    //getters and setters
}

When I get new values for some fields of this object I can update it. But new fields values income in this format: Map<String, String> newValues where key - field number and value - the value of the field. I create this service:

public class UpdateService {
    public Person updateFields(Person targetPerson, Map<String, String> newValues){
        return null;
    }
}

I create a unit test and ask you to help implement this.

public class UpdateServiceTest {

    /*associations between field number and field name
    12 - name (length min: 2, max: 20. First letter must uppercase )
    18 - marriedStatus (only married, divorced, single)
    21  - age (only between 18 and 120)
    14  - dob (some format)
     */

    private Date dob;

    @Before
    public void setUp() {
        dob = new GregorianCalendar(2000, Calendar.NOVEMBER, 20).getTime();
    }

    @Test
    public void returnPersonWithUpdatedFields() {
        UpdateService updateService = new UpdateService();

        Person targetPerson = new Person();
        targetPerson.setName("Name");
        targetPerson.setMarriedStatus("MarriedStatus");
        targetPerson.setAge(20);
        targetPerson.setDob(dob);

        Map<String, String> newValues = new HashMap<String, String>();
        newValues.put("12", "Bill");
        newValues.put("18", "married ");
        newValues.put("21", "25");

        Person person = updateService.updateFields(targetPerson, newValues);

        assertEquals("Bill", person.getName());
        assertEquals("married", person.getMarriedStatus());
        assertEquals(25, person.getAge());
        assertEquals(dob, person.getDob());
    }
}

I need to get the person and update only fields which income in Map<String, String> newValues. And validate it.


回答1:


Here's a suggestion how you could do this.

Model each field as a separate class, implementing a common Field interface.

Inject all known fields via the constructor and when a map is received, for each entry look up the matching field class to handle validation and updating.

With this approach you can test each field's validation and updating logic separately. Adding person fields will not make your service class grow. Testing the PersonUpdateService will only need to use one or two mocked Fields to validate the lookup and execution logic. A nice separation of concerns I'd say.

import java.util.*;

@Component
public class PersonUpdateService {

    private final List<Field> fields;

    @Autowired
    public PersonUpdateService(final List<Field> fields) {
        this.fields = fields;
    }

    public void updatePerson(final Person person, final Map<String, String> update) {
        final boolean updated = false;
        update.forEach((key, value) -> this.findField(key).update(person, value));
    }

    private Field findField(final String index) {
        return this.fields.stream().filter(f -> f.index().equals(index)).findAny().orElseThrow(
                () -> new IllegalArgumentException("Field not found: " + index));
    }

}

The field interface:

public interface Field {
    String index();

    void update(Person person, String newValue);
}

An example field implementation:

import java.util.regex.Pattern;

@Component
public class NameField implements Field {

    private static final String INDEX = "12";
    private static final String REGEX = "/^[A-Z][a-z0-9_-]{1,19}$/";
    private static final String CONSTRAINTS = "length min: 2, max: 20. First letter must uppercase";

    @Override
    public String index() {
        return INDEX;
    }

    @Override
    public void update(final Person person, final String newValue) {
        if (!Pattern.matches(REGEX, newValue)) {
            throw new ValidationException(CONSTRAINTS);
        }
        person.setName(newValue);
    }

}

EDIT: added @Component and @Autowired annotations to indicate how dependency injection could be used in Spring. The available components implementing the Field interface will automatically be collected by Spring and injected via the constructor. In your unit test for the service you can inject one or two mock Fields. Don't test the actual validation/updating logic of the field implementations in the service test, but create separate unit tests for each field class.

EDIT 2: the above advice about writing unit tests is from my (mockist) perspective. Classical unit testers would probably write a single test to cover the complete specs (like the test you provided in the post). The reason I don't prefer that is that edge cases get lost easier in such an integration-style unit test and the chance increases that you are making false assumptions about how the code works or that you have to repeatedly exercise the same code in your tests. However this is a long-standing debate and there are multiple points of view which all have their merits.



来源:https://stackoverflow.com/questions/56595354/correct-approachdesign-pattern-for-update-fields-in-java-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!