Advertisement
Guest User

Untitled

a guest
Oct 4th, 2017
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.65 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement