Domanda

I'm having major troubles trying to get rid of this switch statement. To put a little of context first, I'm working with asynchronous batch operations. These operations can be applied to any entity in the system. You just have to send a different namespace.

Example:

"jobs": [
{
    namespace: "client",
    request: {"id": 10, "name": "john"}
}, 
{
    namespace: "invoice",
    request: {"id": 99, "number": "F20170101"}
}]

After I send those jobs, the service returns data similar, but with a response key like the following:

results: [
{
    namespace: "client",
    request: {id: 10, name: "john"},
    response: {success: false}
}, 
{
    namespace: "invoice",
    request: {id: 99, number: "F20170101"},
    response: {success: true, s_id: 3310}
}]

Now here comes the funny part

I have to process the response, and based on the namespace, I have to select the appropriate repository to update our database records. So far I have this smelly thing:

if (namespace == 'client') {
    customerRepository.doSomething()
} elseif (namespace == 'invoice') {
    invoiceRepository.doSomethingTotallyDifferent(withDiffParams)
}

Now this clearly breaks Open/Close principle, and I can't figure out how to solve this by removing the switch (actually a if/elseif spaghetti thingy, but it's the same). Thanks in advance.

PS: this is not a specific language question, so please refrain to provide answers that applies only to a specific language.

È stato utile?

Soluzione

Your repositories should be derived from a common base class, lets call it baseRepository. This class needs to provide two virtual methods

  • correspondingNamespace()

  • processReponse(responseKey)

Overide the methods in the derived classes, so correspondingNamespace should return the correct namespace for each repo, for example "client" for customerRepository. processReponse should delegate to the individual business logic like calling doSomething or calling doSomethingTotallyDifferent with different parameters.

Now you can easily initialize a dictionary with the available repositories beforehand, in pseudo code:

for each repo in [customerRepository, invoiceRepository]:
     repoDict[repo.correspondingNamespace()]=repo

And the response processing will look (roughly) like:

  repoDict[responseKey.namespace].processReponse(responseKey)

To your comment: if you do not want to add these methods to the repos directly, you can use a separate hierarchy of strategy classes and place the two methods there. Derive customerResponseStrategy and invoiceResponseStrategy from responseStrategy, and let each responseStrategy hold a reference to the corresponding repo. That will lead to something like

 for each respStrat in 
        [new customerResponseStrategy(customerRepository),
         new invoiceResponseStrategy(invoiceRepository)]:
         responseStrategyDict[respStrat .correspondingNamespace()]=respStrat 

and

  responseStrategyDict[responseKey.namespace].processReponse(responseKey)

Altri suggerimenti

What I've done plenty of times in projects, and that helped a LOT with new feature requests was:

interface JobResponseWorker {
    bool canProcess(namespace);
    void process(response);
}

class ClientResponseWorker implements JobResponseWorker {

    bool canProcess(namespace) {
        return namespace == "client"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

class InvoiceResponseWorker implements JobResponseWorker {
    bool canProcess(namespace) {
        return namespace == "invoice"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

workers = [new ClientResponseWorker(), new InvoiceResponseWorker()]

//your main logic
void processResponse(namespace, request, response) {

    for (JobResponseWorker worker in workers) {
        if (worker.canProcess(namespace)){
            worker.process(response)
        }
    }
}

Like this, most of your main code is closed for modifications. For new workers, you Add new code (open for extension), and then only adds another object into an array (no additional logic in old code).

I think what you are looking for here is the kind of approach described by the Strategy pattern. In a nutshell you need to translate your namespaces to classes/objects. In your example you would have a Client class and a Invoice class. Each would have a doTheThing() method.

What remains is that you need to have some sort of switch or map that determines which type you should be using. This might seem somewhat pointless in a simple example like this but as the number of operations and classes increases, it will greatly simplify both the code and make it much more understandable. You will also be more able to add new types without touching any of the existing logic for other types.

As long as you have only one place in your code where you have to do this discrimination you should stay with it. But if you have more places where you need to apply different behavior upon the namespace value you should go for the "polymorphic" approach.

The problem is that with a "polymorphic" approach you have to do the discrimination too because you have to select the implementation that belongs to your namespace value at some point.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top