Save Calculation in Code or Database?

后端 未结 4 713
广开言路
广开言路 2021-01-14 09:27

I\'m setting up a system which will carry out various calculations on primitive data, and the provide an output based on the calculation.

My main question,

4条回答
  •  醉酒成梦
    2021-01-14 10:04

    Eval is Evil

    First of all: don't use eval() unless there is a good reason. And there is never a good reason.

    in the worst case eval() makes your application vulnerable to injection attacks and also it's very slow. A bit of research reveals plenty of reasons why eval is a big no-no.

    Don't save your calculation code into the database

    If you do so and you would like to switch from PHP to another language you would still have PHP code in your database. It makes it really hard to migrate languages. You should always strive to make as many parts of your application as independent as possible.

    In this case you would tight-couple the language you use, to the database. That's a bad practice.

    Also the only possibilities to run your calculations from the database would be to eval them (which is bad, see above) or to disassemble the string with string operations or regex which causes unnecessary effort.

    It's all about Strategy

    In order to solve your problem you must execute code dependent of which calculation you need. That could be either done with switch-case-statements or if-statements. But that's also not a very elegant solution. Imagine you would need to execute other operations before calculating in the future, or extend functionality. You would need to update all your cases or if-statements.

    There is a nice design-pattern which is called Strategy Pattern. The strategy pattern solves problems when one use-case can be handled differently which is probably what you want.

    You want to calculate something (use-case) and there are different calculation types for it (different strategies)

    How it works

    To implement the Strategy pattern you basically need three things.

    • A class where you inject your strategies. It's basically a wrapper for your strategy tasks.
    • An interface which will be implemented by your strategies
    • Your strategies

    Your interface could look like this:

    The interface will make sure that all your strategies provide a method to actually run the calculation. Nothing special.

    Next you may want to have a base class that takes your calculation operators as constructor arguments and stores them into properties.

    valueA = $valueA;
            $this->valueB = $valueB;
        }
    
    }
    

    Now it's getting serious. We are implementing our strategies.

    valueB != 0) ? $this->valueA / $this->valueB : 'NA';
        }
    
    }
    
    class Percentage extends Calculatable implements CalculatableInterface {
    
        public function calculate()
        {
            return ($this->valueB != 0) ? (100 / $this->valueB) * $this->valueA : 'NA';
        }
    
    }
    

    Of course you could clean this one up a bit, but what I want to point out here is the class declaration.

    We are extending our Calculatable class so that we can pass the arithmetic operations via constructor and we are implementing the CalculatableInterface which tells our class: "Hey! You must provide a calculate method, I don't care whether you want or not.

    We'll see later why this is an integral part of the pattern.

    So we have two concrete classes that contain the actual code for the actual arithmetic operation. If you would ever need to, you could change it easily as you see. To add more operations just add another class.

    Now we will create a class where our strategies can be injected. Later you will instantiate an object of this class and work with it.

    Here is how it looks like:

    calculatable = $calculatable;
        }
    
        public function calculate()
        {
            return $this->calculatable->calculate();
        }
    
    }
    

    The most important part here is the constructor. See how we type-hint our interface here. By doing that we make sure that only an object can be injected (Dependency Injection) whose class implements the interface. We do not need to demand a concrete class here. That's the crucial point here.

    Also there's a calculate method in there. It's just a wrapper for our strategy to execute it's calculate method.

    Wrapping it up

    So now we just need to create an object of our Calculator class and pass an object of one of our strategy classes (that contain the code for the arithmetic operations).

    calculate();
    

    Try replacing the string stored in $calculatable to Percentage and you see that the operation for calculating the percentage will be executed.

    Conclusion

    The strategy pattern allowed you to create a clean interface for working with dynamic tasks that are only made concrete during runtime. Neither your database needs to know how we calculate things, nor your actual calculator does. The only thing we need to make sure is to code against an interface that provides a method to let us calculate things.

提交回复
热议问题