Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Consider this example to illustrate the Open-Closed Principle (OCP):
- #
- # Let's say you a have a client SomeClient that uses
- # a class DatabaseConnection to connect to the database and do something.
- #
- class SomeClient
- def do_something
- params = [:mysql, 'host', 1234, 'dbname', 'user', 'pass']
- connection = DatabaseConnection.new(*params).connect
- end
- end
- # Now, let's see different approaches to the implementation of the class DatabaseConnection...
- #### A bad approach that violates the OCP:
- #
- # This class does not comply with the Open-Closed principle
- # because we would have to change its implementation to add support
- # to another database (hence, it is not open for extension and neither closed for modification),
- # like SQLite for example (SQLite does not require a host, port, etc, because it is just a file).
- #
- class DatabaseConnection
- def initialize(adapter, host, port, db_name, user, password)
- @adapter = adapter
- @host = host
- @port = port
- @db_name = db_name
- @user = user
- @password = password
- end
- def connect
- case @adapter
- when :mysql
- require 'mysql-driver-lib-name'
- MySQLDriver.new_connection(@host, @port, @db_name, @user, @password)
- when :postgres
- require 'postgres-driver-lib-name'
- # Let's say that postgres connection interface is different
- PostgresDriver.new.connect_to(@host, @port, @db_name, @user, @password)
- else
- raise 'We cannot handle this database'
- end
- end
- end
- #### A better approach compliant with the OCP:
- #
- # The key to be compliant with the OCP is abstraction.
- # The client need to have a established contract with
- # DatabaseConnection class, and we can achieve it
- # by making it rely on a Adapter common interface.
- #
- # Ruby is ducked-type, but let's say that we have this
- # interface as contract to define database adapters.
- #
- class AdapterInterface
- def initialize(**_params)
- raise NotImplementedError
- end
- def connect
- raise NotImplementedError
- end
- end
- #
- # Now, this class is compliant with the OCP, because we
- # don't need to change its implementation anymore to support more databases,
- # it only need to create a new adapter that implements the AdapterInterface
- # and then add it to DatabaseConnection supported adapters list.
- #
- class DatabaseConnection
- class << self
- attr_reader :adapters
- end
- def self.add_database_adapter(adapter_name, handler)
- @adapters ||= {}
- @adapters[adapter_name] = handler
- end
- def initialize(**params)
- @params = params
- end
- def connect(adapter)
- raise 'We cannot handle this database' unless self.class.adapters.key? adapter
- handler = self.class.adapters[adapter]
- handler.new(**@params).connect
- end
- end
- #
- # For instance, a MySQL adapter.
- #
- class MySQLAdapter < AdapterInterface
- # Let's say that mysql driver is installed and required
- # require 'mysql-driver-lib-name'
- # Define here all the parameters that MySQL needs, as keyword arguments
- def initialize(host: 'localhost', port: '3306', database:, user:, password:)
- # save all parameter as intances variables
- end
- def connect
- # Do here MySQL specific needs to create a new connection
- MySQLDriver.new_connection(@host, @port, @database, @user, @password)
- end
- end
- #
- # Now the client could do something like: this:
- # (
- # Assuming that the MySQLAdapter was registered in DatabaseConnection class somewhere before:
- #
- # DatabaseConnection.add_database_adapter(:mysql, MySQLAdapter)
- # )
- #
- class SomeClient
- def do_something
- params = { host: 'host', port: 1234, database: 'dbname', user: 'user', password: 'pass' }
- connection = DatabaseConnection.new(**params).connect(:mysql)
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement