2

两个模型:一个Owner和一个Dog

owner.rb

class Owner < ActiveRecord::Base
  has_one :dog
end

dog.rb

class Dog < ActiveRecord::Base
    belongs_to :owner
end

这是架构:

schema.rb

ActiveRecord::Schema.define(version: 123) do

  create_table "dogs", force: true do |t|
    t.string   "name"
    t.integer  "energy"
    t.integer  "owner_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "dogs", ["owner_id"], name: "index_dogs_on_owner_id"

  create_table "owners", force: true do |t|
    t.string   "name"
    t.string   "energy"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end

很简单的设置。

我想owner带他dog去散步。步行结束时,主人的能量会下降 5,狗的能量会下降 20。

很明显,这个walk_the_dog动作/方法,无论它在哪里存在,都会影响两个对象:一个owner对象和一个dog对象(当然,这个狗对象恰好与这个所有者相关联)。

我不知道把这段代码放在哪里。我知道我可以简单地在 中创建一个动作owners_controller.rb,但这似乎是个坏主意。它看起来像这样:

owners_controller.rb

class OwnersController < ApplicationController
    def walk_the_dog
        @owner = Owner.find(params[:id])
        @owner.energy -= 5
        @owner.dog.energy -= 20   # this line in particular seems like bad OO design
        @owner.save
        @owner.dog.save
    end
    ...
 end

据我了解,对象应该只改变自己的状态,而不应该改变其他对象的状态。所以这似乎是个坏主意,因为在所有者控制器中,我们不仅要更改owner对象的状态,还要更改关联dog对象的状态。

我已阅读有关服务的信息。对于服务来说,这似乎walk_the_dog是一个很好的案例,因为据我了解,服务允许对象之间的交互和多个对象的状态更改。我只是不知道该怎么做/实施它。

是否应该有一个名为的服务对象walk_the_dog?它们是否应该只是服务目录中的一个文件,其中包含一堆服务方法——其中一个被调用walk_the_dogowners_controller.rb在它的控制器中简单地利用这个方法?我不知道下一步是什么。

注意:我可以看到有人说“谁在乎这是否会破坏 OO 设计。只要去做,如果它有效,它就有效。” 不幸的是,这不是一个选择。我现在正在开发的应用程序遵循了这种心态。应用程序变得非常大,现在维护它非常困难。我想在应用程序的重大重新设计中解决这种情况。

4

2 回答 2

1

如果我要重构这段代码,我会做以下几件事:

在代码中写数字是一件坏事,要么你将它们定义为常量,要么ENERGY_PER_WALK_FOR_DOG = 20更好的方法是在Dog模型表中定义一个字段。这样,管理和分配这些值会更好。

add_column :dogs, energy_per_walk, :integer, default: 20
add_column :owners, energy_per_walk, :integer, default: 5

我会在ApplicationController类中创建一个方法:

def walk(resources = [])
  resources.each do |resource|
    resource.lose_walk_energy # you can refine it more!
  end
end

在文件夹app/models/concerns中,我将编写以下模块:

module Walkable
  extend ActiveSupport::Concern


  # subtract energy_per_walk form the energy saved in db
  def lose_walk_energy
    self.energy -= self.energy_per_walk
    save
  end

end

现在,您的方法简化为以下方法:

def walk_the_dog
  @owner = Owner.find(params[:id])
  walk([@owner, @owner.dog])
end
于 2015-06-11T16:22:37.697 回答
0

我会说这应该是 Owner 模型中的一种方法。您还需要在一个事务中执行这两项操作,以确保两个模型都已更新。

class Owner
  has_one :dog

  def walk_the_dog
    return false if dog.nil?

    transaction do
      decrement!(:energy, 5)
      dog.decrement!(:energy, 20)
    end
  end
end
于 2015-06-11T16:22:47.937 回答