web-dev-qa-db-ja.com

Ruby apiデザインのgetterメソッド名とsetterメソッド名の競合

私が書いているSDKでAPIの決定を継承しています。ここでは、次のようにサーバーからドメインオブジェクト(エントリ)をフェッチする必要があります。

blogEntries = client.content_type('blog').entries

ご覧のとおり、ここのcontent_typeプロパティのセッターはパラメーター化されています。この設計を実装するために、私のClientクラスには、他のオブジェクトに渡す前に@content_typeインスタンス変数を設定する次のようなメソッドがあります。

def content_type(content_type_uid)
  @content_type = content_type_uid
  // do something with @content_type
end

これで、クラスの他の場所でcontent_typeをフェッチする必要があるときに、上記のメソッドと競合するため、attr_readerのようなconfiguration.content_typeメソッドを呼び出すことができなくなりました。これで、get_content_typeという別のゲッターメソッドが必要になります。これは実際には非慣用的なRubyです。

content_type=のような従来のセッターの代わりに、異なる署名のセッターがあるというこの矛盾する状況から自分自身をどのように解決しますか?どのようなトレードオフが最も理にかなっていますか?

1
Amit Erandole

理想的には、ゲッターやセッターを完全に避ける必要があります。 OOの基本的な信条は動作の抽象化であり、オブジェクトはdoものだけでなく、「保存」するもの。

絶対にmustにゲッターとセッターが必要な場合は、実際に標準のRuby規則に従うことをお勧めします) :

def content_type=(content_type_uid)
  @content_type = content_type_uid
  # do something with @content_type
end

それが不可能な場合は、アリティに基づいてメソッドを「オーバーロード」してみてください。

def content_type(content_type_uid = getter = true)
  return @content_type if getter
  @content_type = content_type_uid
  # do something with @content_type
end

これを行う場合は、使用しているドキュメントシステムで、このメソッドを2つの異なる「オーバーロード」メソッドとしてドキュメント化する方法を調べる必要があります。例えば。 [〜#〜] yard [〜#〜] には @overload タグ:

# @overload content_type
#   Gets the content type
#   @return [ContentType] The content type
# @overload content_type(content_type_uid)
#   Sets the content type to +content_type_uid+
#   @param content_type_uid [ContentType::Uid] The UID of the content type to set
def content_type(content_type_uid = getter = true)
  return @content_type if getter
  @content_type = content_type_uid
  # do something with @content_type
end

または、合成属性/メソッドに対するYARDのサポートを使用できます。

#   Sets the content type to +content_type_uid+
#   @param content_type_uid [ContentType::Uid] The UID of the content type to set
def content_type(content_type_uid = getter = true)
  return @content_type if getter
  @content_type = content_type_uid
  # do something with @content_type
end

# !attribute [r] content_type
#   Gets the content type
#   @return [ContentType] The content type

ただし、この「オーバーロード」には重大な欠点があることに注意してください。ドキュメントで表現することはできますが、言語で表現する方法はありません。したがって、(私がよく行うように)リフレクションを使用して見慣れないAPIを探索すると、オプションのパラメーターを持つ1つのメソッドのみが表示され、1つのメソッドが実際には2つの異なるメソッドに変装しているという事実は示されません。

method(:content_type).parameters
#=> [[:opt, :content_type_uid]]

通常、オプションのパラメータは、2つの完全に異なる動作を切り替えるのではなく、オプションの引数を提供するために使用されます。

3
Jörg W Mittag

この質問への答えは、 腐敗防止レイヤーとは何ですか、そしてそれはどのように使用されますか? への答えにあるかもしれません。

受け入れられた回答 からの引用の引用:

上記の例は、 Anticorruption Layer がc2wikiでどのように説明されているかに基づいています。

アプリケーションが、自分のアプリケーション内で必要なモデルにモデルが望ましくない、または適用できないデータベースまたは別のアプリケーションを処理する必要がある場合は、AnticorruptionLayerを使用して、そのモデルと自分のモデルとの間で変換を行います。

ファンキーな振る舞いのクラスがあります。奇妙な動作を「修正」してクライアントを非表示にするラッピングクラスを作成することをお勧めします。

class ClientWrapper
  attr_reader :content_type

  CONTENT_TYPE_BLOG = 'blog'

  def initialize(client)
    @client = client
  end

  def blog_entries
    @content_type = CONTENT_TYPE_BLOG
    @client.content_type(@content_type).entries
  end
end

今、あなたはすることができます:

client = ClientWrapper.new SomeClient.new
blog_entries = client.blog_entries
puts client.content_type # -> 'blog'

別の方法として、腐敗防止レイヤーとともに、コンテンツタイプとデータを一緒にパッケージ化する必要があるかもしれません。

class ClientWrapper
  CONTENT_TYPE_BLOG = 'blog'

  def initialize(client)
    @client = client
  end

  def blog_entries
    ClientData.new CONTENT_TYPE_BLOG, @client.content_type(CONTENT_TYPE_BLOG).entries
  end
end

class ClientData
  attr_reader :content_type, :data

  def initialize(content_type, data)
    @content_type = content_type
    @data = data
  end
end

これを使用すると、次のようになります。

client = ClientWrapper.new SomeClient.new
blog_entries = client.blog_entries
puts blog_entries.content_type # -> 'blog'
puts blog_entries.data

私のRubyは少しさびていますが、サブクラスを使用してクライアントデータに独自のバージョンのeachメソッドを作成できます。

class ClientArrayData < ClientData
  def each(&block)
    if data.respond_to? :each
      data.each block
    else
      block.call data
    end
  end
end

または、ClientDataクラスにメソッドを追加するだけです。次に、他の反復可能なオブジェクトと同様に使用できます。

client.blog_entries.each do |entry|
  # Do stuff with `entry`
end
2
Greg Burghardt