我正在编写Ruby Gem,我有配置Connection
模块Faraday
module Example module Connection private def connection Faraday.new(url: 'http://localhost:3000/api') do |conn| conn.request :url_encoded # form-encode POST params conn.response :logger # log requests to STDOUT conn.adapter Faraday.default_adapter # make requests with Net::HTTP conn.use Faraday::Response::ParseJson conn.use FaradayMiddleware::RaiseHttpException end end end end
发出API请求的第二个模块如下所示:
module Example module Request include Connection def get(uri) connection.get(uri).body end def post(url, attributes) response = connection.post(url) do |request| request.body = attributes.to_json end end def self.extended(base) base.include(InstanceMethods) end module InstanceMethods include Connection def put(url, attributes) response = connection.put(url) do |request| request.body = attributes.to_json end end end end end
Cusomer
我使用的类Request
看起来像这样:
module Example class Customer extend Request attr_accessor :id, :name, :age def initialize(attrs) attrs.each do |key, value| instance_variable_set("@#{key}", value) end end def self.all customers = get('v1/customer') customers.map { |cust| new cust } end def save params = { id: self.id, age: self.age name: self.name, } put("v1/customers/#{self.id}", params) end end end
所以在这里你看到Customer#all
类方法我正在调用Request#get
方法,因为我扩展Request
了Customer
.然后我在Request
模块中使用self.extended方法在课堂上Request#put
可用Customer
,所以我有一个问题就是使用这样的mixins这个好方法,或者你有什么建议吗?
Mixins是一个奇怪的野兽.最佳做法取决于您与谁交谈.就重用而言,你已经通过mixins实现了这一点,并且你可以很好地分离关注点.
但是,mixins是一种继承形式(你可以看看#ancestors
).我会挑战你说你不应该在这里使用继承,因为Customer
它没有"is-a"关系Connection
.我建议你改用合成(例如传入Connection/Request
),因为在这种情况下对我来说更有意义并且具有更强的封装性.
写混入其中一个原则是使一切在"-able"结束了,所以你有Enumerable
,Sortable
,Runnable
,Callable
,等.在这个意义上,混入是提供某种形式的被依赖一个非常特殊的接口上佣工(通用扩展如Enumerable
取决于要实施的类#each
).
您也可以使用mixins来解决交叉问题.例如,我们过去在后台作业中使用了mixins,因此我们可以添加日志记录,而无需触及类的源代码.在这种情况下,如果一个新工作想要记录,那么他们只是混合与框架耦合的关注点并将正确地注入自己.
我的一般经验法则是,如果您不需要,请不要使用它们.在大多数情况下,他们使代码的理解变得更加复杂
编辑:添加组合的示例.为了维护上面的接口,你需要有某种全局连接状态,所以它可能没有意义.这是使用合成的替代方案
class CustomerConnection # CustomerConnection is composed of a Connection and retains isolation # of responsibilities. It also uses constructor injection (e.g. takes # its dependencies in the constructor) which means easy testing. def initialize(connection) @connection = connection end def all_customers @connection.get('v1/customers').map { |res| Customer.new(res) } end end connection = Connection.new CustomerConnection.new(connection).all_customers