Let's say I have a switch case that looks something like this: (just an example)
switch(Type) {
case MSSQL: /
Connector = new MSSQLCOnnector(
args);
break;
case MYSQL:
Connector = new MYSQLConnector(
different_args);
break;
case ORACLE:
databaseConnector = new OracleConnector(
again_different_args);
break;
default:
break;
}
Since Switch-case violates OCP, is there a way to eliminate it, and use something else in this code?
Thank you.
You are using the switch-statement to create an instance based on a type argument. In this case or basically when using conditional operations with data types, each time you want to extend your application you would have to touch this statements by adding and modifying conditions. Easy to imagine how this can make your condition checks explode. Note that your example is hiding dependencies by introducing a factory or a factory methods to create the concrete type instead of creating it directly in the class where the dependency actually exists.
You should try to use a factory only when instantiating a type that is complex to construct (e.g. you have to instantiate additional types in order to satisfy all dependenciesr) or needs additional configuration. In this situation a switch could be used to check a parameter to determine the configuration to apply on the created instance. The Builder pattern can be also useful in this situation. By encapsulating complex construction or configuration, you can eliminate duplicated code as well, since all is in one place now.
To eliminate this switch I would extract each type construction into (if needed) a separate factory that knows how to construct this type only. So instead of having a method ConnectorFactory.CreateDbConnector(type) you should have a MySqlFactory.CreateConnector(args) and a OracleDbFactory.CreateConnector() method. When the type switch is eliminated this way (a factory each type), introducing a new type only requires to create a new factory which is dedicated to this very type only. No modifications on existing code.
Given that you can modify the connector types from your example, it would be an even better solution to leave the bad factories alone by moving each (factory) method to the object they actually create: MySqlFactory.CreateConnector(args) becomes MySqlConnector.CreateInstance(args). And here as well a method encapsulates each condition and in case of a factory (method) also the applied instance modifications. This means if we had a case where we created a read-only connector, we would now add an additional method to the connector called MySqlConnector.CreateReadOnlyInstance(args) (same applies if we would have stayed with the factories). If you like to expose a singleton you would introduce a MySqlConnector.CreateSharedInstance(args). Since each type features now its own factory method, adding a new type to the application won't break anything existing.
In case the factory is not required I would always instantiate the type directly in place.
The "Builder" pattern as a solution also offers flexible control over instantiation and encapsulates the procedure, all without the need of a switch statement. But the builder again should be associated with one single type only.
If you need to be extensible e.g. you want substitute a type (or implementation) I would refactor it the same way but then you should implement all factories according to the "Abstract Factory Pattern" and use dependency inversion all over. All type instantiation should then occur in factories or factory methods only. The "new" keyword is not allowed anywhere else except for build-in types. This reduces the effort needed to modify existing code by making the factories the only place where the concrete type has to be changed. Once you have done so, you are also well prepared to use dependency injection if desired.
Long story short, generally you can extract every condition of a switch into a separate method with a descriptive name that describes the case. This time the caller is forced to choose the required method (representing a case). The caller of course already knows everything about himself (also his own type like in your example). He will also know the actions he likes to perform. No switching in order to choose the right action is necessary. And considering the "Tell-Don't-Ask" principle, no switch should be placed inside a second type to decide which action to perform on the first type. The first type must call the appropriate method to perform an intended action on his data or on his behalf.
And not every switch statement is a violation and violating a principle is fine if you now the consequences. E.g. it would be fine to use a switch to check a private flag. A switch-statement is equivalent to a nested if-then-else statement. The type switching should be avoided e.g. by using inheritance and defining good interfaces for boundaries. When you use e.g. polymorphism and dependency inversion you will most likely have no need for type checks.