简体   繁体   中英

Performance cost of refactoring similar methods into Strategy pattern?

I find myself facing a lot of similar methods with some repeating code in my projects. The general pattern seems to look like (apologies for vague code, licensing won't let me provide a concrete example):

 public void modifyType1Person() {
        Map<String, ?> parameters = new HashMap<>();
        parameters.put("type", "type1");
        parameters.put("stringArgument", "some name");
        editPersonBasedOnType(parameters);
    }

    public void modifyType2Person() {
        Map<String, ?> parameters = new HashMap<>();
        parameters.put("type", "type2");
        editPersonBasedOnType(parameters);
    }

    public void modifyDefaultTypePerson() {
        Map<String, ?> parameters = new HashMap<>();
        parameters.put("type", "otherType");
        parameters.put("booleanArgument", true);
        editPersonBasedOnType(parameters);
    }

    public void editPersonBasedOnType(Map<String, ?> parameters) {
        // assume some stuff is done
        switch (parameters.get("type")) {
            case "type1":
                editType1Person(parameters.get("stringArgument"));
            case "type2":
                editType2Person();
            default:
                editOtherType(parameters.get("type"), parameters.get("booleanArgument"));
        }
        // assume more stuff is done
    }

    private void editType1Person(String stringArg) {
        Person person = PersonService.getPerson(stringArg);
        person.edit();
        doMoreThingsForType1Person(person);
    }

    private void editType2Person() {
        Person person = PersonService.getPerson(HARDCODED_NAME);
        person.edit();
        doMoreThingsForType2Person(person);
    }

    private void editOtherType( String type, boolean boolArg ) {
        Person person = PersonService.getPerson(HARDCODED_NAME);
        person.edit()
        doMoreThingsForDefaultTypePerson(person)
    }

The "doMoreThingsForTypeXPerson" methods can be either similar or not similar, depending on context.

Personally, I feel that the Strategy pattern or dependency injection could be used to get rid of similar code and eliminate the need to write multiple methods per type, so it would look like:

public void modifyType1Person() {
    editPersonBasedOnType(new Type1Strategy("some name"));
}

public void modifyType2Person() {
    editPersonBasedOnType(new Type2Strategy());
}

public void modifyDefaultTypePerson() {
    editPersonBasedOnType(new DefaultTypeStrategy("other type", true));
}

public void editPersonBasedOnType(TypeStrategy typeStrategy) {
    // assume some stuff is done
    typeStrategy.doProcedure();
    // assume more stuff is done
}

public interface TypeStrategy {
    public void doProcedure()
}

public class Type1Strategy {
    private String stringArgument;

    public Type1Strategy(String stringArgument) {
        this.stringArgument = stringArgument;
    }
    public void doProcedure() {
        // editType1Person method
    }
    private void doMoreThingsForType1Person() {//implementation}
}

public class Type2Strategy {
    public void doProcedure() {
        // editType2Person method
    }
    private void doMoreThingsForType2Person() {//implementation}
}

public class DefaultTypeStrategy {
    private String type;
    private boolean boolArg;

    public DefaultTypeStrategy(String type, boolean boolArg) {
        this.type = type;
        this.boolArg = boolArg;
    }
    public void doProcedure() {
        // editOtherType code
    }
    private void doMoreThingsForDefaultTypePerson() {//implementation}
}

Would you use the first approach or the refactored approach? Or is there a better approach for this general situation? Is there a need to be concerned about the cost of instantiating the strategy object?

Whenever you exchange concrete-type branching code (a bunch of if/else statements or a switch) for something abstract and polymorphic, you're typically paying a somewhat higher cost with only the rarest of exceptions (ex: when icache hits become important), perhaps considerably higher if you involve some lookup in exchange like a Map .

And you do so typically with extensibility in mind so that you can extend what the original code supports without constantly modifying it (ex: to avoid constantly fiddling with editPersonBasedOnType ) or modifying it as much. Sometimes it becomes a necessity if it's impossible to continue editing such functions until the end of time, like if the behavior should be extensible by third parties adding plugins that extend the range of behaviors.

So whether you favor extensibility here or performance is up to you, but it's worth noting that there's typically some performance overhead of the micro-efficiency sort here. It's also worth noting that productivity often trumps micro-level performance except in the tightest, loopiest parts of your codebase that show up in your profiler.

So I'd go with whatever design fits your needs. In your example code, I'm assuming it's been simplified over the original, and there I wouldn't favor going such a route with only one place doing a switch on a possibility of 3 types. Smarter solutions are only smart when they're actually reducing the burden. But perhaps your original code is a lot more extensive and becoming a maintenance burden with broad plans to continue extending and extending it, and if so, it might be worth refactoring to a polymorphic design.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM