SHARE
TWEET

Untitled

a guest Oct 4th, 2017 39 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Consider this example to illustrate the Open-Closed Principle (OCP):
  2. #
  3. # Let's say you a have a client SomeClient that uses
  4. # a class DatabaseConnection to connect to the database and do something.
  5. #
  6. class SomeClient
  7.   def do_something
  8.     params = [:mysql, 'host', 1234, 'dbname', 'user', 'pass']
  9.     connection = DatabaseConnection.new(*params).connect
  10.   end
  11. end
  12.  
  13. # Now, let's see different approaches to the implementation of the class DatabaseConnection...
  14.  
  15. #### A bad approach that violates the OCP:
  16.  
  17. #
  18. # This class does not comply with the Open-Closed principle
  19. # because we would have to change its implementation to add support
  20. # to another database (hence, it is not open for extension and neither closed for modification),
  21. # like SQLite for example (SQLite does not require a host, port, etc, because it is just a file).
  22. #
  23. class DatabaseConnection
  24.   def initialize(adapter, host, port, db_name, user, password)
  25.     @adapter = adapter
  26.     @host = host
  27.     @port = port
  28.     @db_name = db_name
  29.     @user = user
  30.     @password = password
  31.   end
  32.  
  33.   def connect
  34.     case @adapter
  35.     when :mysql
  36.       require 'mysql-driver-lib-name'
  37.       MySQLDriver.new_connection(@host, @port, @db_name, @user, @password)
  38.     when :postgres
  39.       require 'postgres-driver-lib-name'
  40.       # Let's say that postgres connection interface is different
  41.       PostgresDriver.new.connect_to(@host, @port, @db_name, @user, @password)
  42.     else
  43.       raise 'We cannot handle this database'
  44.     end
  45.   end
  46. end
  47.  
  48.  
  49. #### A better approach compliant with the OCP:
  50.  
  51. #
  52. # The key to be compliant with the OCP is abstraction.
  53. # The client need to have a established contract with
  54. # DatabaseConnection class, and we can achieve it
  55. # by making it rely on a Adapter common interface.
  56. #
  57. # Ruby is ducked-type, but let's say that we have this
  58. # interface as contract to define database adapters.
  59. #
  60. class AdapterInterface
  61.  
  62.   def initialize(**_params)
  63.     raise NotImplementedError
  64.   end
  65.  
  66.   def connect
  67.     raise NotImplementedError
  68.   end
  69. end
  70.  
  71. #
  72. # Now, this class is compliant with the OCP, because we
  73. # don't need to change its implementation anymore to support more databases,
  74. # it only need to create a new adapter that implements the AdapterInterface
  75. # and then add it to DatabaseConnection supported adapters list.
  76. #
  77. class DatabaseConnection
  78.   class << self
  79.     attr_reader :adapters
  80.   end
  81.  
  82.   def self.add_database_adapter(adapter_name, handler)
  83.     @adapters ||= {}
  84.     @adapters[adapter_name] = handler
  85.   end
  86.  
  87.   def initialize(**params)
  88.     @params = params
  89.   end
  90.  
  91.   def connect(adapter)
  92.     raise 'We cannot handle this database' unless self.class.adapters.key? adapter
  93.     handler = self.class.adapters[adapter]
  94.     handler.new(**@params).connect
  95.   end
  96. end
  97.  
  98. #
  99. # For instance, a MySQL adapter.
  100. #
  101. class MySQLAdapter < AdapterInterface
  102.  
  103.   # Let's say that mysql driver is installed and required
  104.   # require 'mysql-driver-lib-name'
  105.  
  106.   # Define here all the parameters that MySQL needs, as keyword arguments
  107.   def initialize(host: 'localhost', port: '3306', database:, user:, password:)
  108.     # save all parameter as intances variables
  109.   end
  110.  
  111.   def connect
  112.     # Do here MySQL specific needs to create a new connection
  113.     MySQLDriver.new_connection(@host, @port, @database, @user, @password)      
  114.   end
  115. end
  116.  
  117. #
  118. # Now the client could do something like: this:
  119. # (
  120. #     Assuming that the MySQLAdapter was registered in DatabaseConnection class somewhere before:
  121. #
  122. #           DatabaseConnection.add_database_adapter(:mysql, MySQLAdapter)
  123. # )
  124. #
  125. class SomeClient
  126.   def do_something
  127.     params = { host: 'host', port: 1234, database: 'dbname', user: 'user', password: 'pass' }
  128.     connection = DatabaseConnection.new(**params).connect(:mysql)
  129.   end
  130. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top