Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

デコレータとサブクラス、どちらを選ぶべきか?Railsのdelegate_missing_toで学ぶDecoratorパターン

前回の記事では、Rails 5.1の注目の新機能であるdelegate_missing_toを紹介しました。delegate_missing_toを使うと、あるオブジェクトに存在しないメソッドが呼び出された場合に、代わりに別のオブジェクトの同名メソッドが実行されます:

class Player
  delegate_missing_to :@user

  def initialize(user)
    @user = user
  end

  def points
    Game.points_for_user(@user.id)
  end
end

Player.new(user).name # user.nameが呼び出される

しかし、コメント欄でGavinが指摘したように、継承を避けるためだけにこの機能を使うのは奇妙に思えます。なぜ単純にサブクラスを使わないのでしょうか?同じ効果が得られるうえ、わざわざ新機能を追加する必要もありません。

とはいえ、delegate_missing_toが追加されたのには何らかの理由があるはずです。Railsの機能については、プルリクエストを調べるのがその理由を突き止める最良の方法です。この機能のプルリクエストの中で、DHHは提案の背景について次のように述べています:

「デコレータを作りたいときに、よく使われるパターンがあります」

これは調査の出発点としてかなり有力な手がかりになりそうです。

なぜデコレータなのか?

デコレータを作ると、新しいサブクラスを定義することなく、オブジェクトの振る舞いを変えることができます。例えば、先ほどのコードを見てみましょう:

class Player
  delegate_missing_to :@user

  def initialize(user)
    @user = user
  end

  def points
    Game.points_for_user(@user.id)
  end
end

この場合、「PlayerはUserをデコレートしている」と言えます。PlayerはほぼUserのように振る舞いながら、追加のメソッドpointsを持っているからです。しかも、継承を使わずに実現しています。

そもそも、なぜこんな仕組みが必要なのでしょうか?これは答えるのが難しい質問です。多くのデザインパターンと同様、「他の手法ではなくデコレータを使うべき場面」が必ずしも明確ではないからです。

デコレータはどんなときに使うのか?

デコレータは、単に継承をより複雑な方法で置き換えているだけとも言えます。実際、次の2行のコードのうち、どちらが優れているでしょうか?

player = Player.new(User.new(name: "Justin")) # PlayerがUserをデコレート
player = Player.new(name: "Justin")           # PlayerがUserを継承

明らかに2番目ですよね?このケースでは、サブクラスではなくデコレータとしてPlayerを作るのは、単なるコードの無駄でしかありません。

しかし時には、オブジェクトの生成場所から離れた場所で、後から機能を追加したいこともあります。例えば、次のようなコードがあるとしたらどうでしょうか?

user = User.find(1)

# ... しばらく時間が経って ...

player = Player.new(user)

こう書けば、Userオブジェクトは好きな場所で、好きな方法で自由に生成できます。Userを生成するコードは、Playerクラスが存在することすら知る必要がなく、気にかけることもありません。さらに、追加メソッドが不要になった場合は、元のUserオブジェクトをそのまま使い続けることもできます。

このアプローチによって、振る舞いをそれぞれ異なるクラスに分離できるのです。各クラスは、特定の状況におけるUserオブジェクトの使い方——Player、Employee(従業員)、Developer(開発者)など——に専念できます。継承を使うと、これらすべてがひとつのクラスに混在してしまいがちです。

コメント欄では、MrChrisがデコレータのもう一つの利点を挙げています。

オブジェクトをデコレートする場合、呼び出せるのはそのオブジェクトの公開メソッド(publicメソッド)だけです。一方、サブクラス化すると、privateメソッドを含むあらゆるメソッドを呼び出せてしまいます。そのため、サブクラスは親クラスの実装詳細にうっかり依存してしまい、壊れやすくなりがちです。実装詳細は通常、公開メソッドよりも頻繁に変更されます。

また、巨大なクラスを分割する際にも、デコレータは特に役立ちます。デコレータを使えば、単一責任の原則(Single-Responsibility Principle)に従いやすくなります。各デコレータは一つのことに集中してそれを見事にこなし、複数のデコレータを組み合わせれば、より複雑な振る舞いを実現できるのです。

まとめ

Rubyには振る舞いを共有する方法が数多くあります。サブクラス化、モジュールのミックスイン、さらにはあるクラスからメソッドを取り出して別のクラスへ付け替えることさえ可能です。しかし、デコレータパターンは少し違ったアプローチを提供します。使うのはインスタンス変数とメソッド呼び出し——あらゆるオブジェクト指向言語の基礎となる構成要素だけです。この基本要素だけで、アプリケーションの実行時に柔軟な振る舞いを実現でき、コードを不必要に複雑にすることもありません。

  1. Discordでユーザーを通報する方法【PC・スマホ完全ガイド】

    Discordは、世界中のゲーマーの間で最も人気のあるプラットフォームの一つに成長しました。しかし、ユーザー数が膨大である分、不正な行為を行うユーザーや、Discordの利用規約に違反するユーザーに出会ってしまう可能性もあります。そのためにDiscordには通報(レポート)機能が用意されており、攻撃的・不適切なコンテンツを投稿するユーザーを報告できるようになっています。ソーシャルメディア上での通報は、プラットフォームの健全性を保つために今や一般的な手段となっています。 通報自体はシンプルな操作ですが、ITに不慣れな方にとっては少し難しく感じられることもあるでしょう。この記事では、デスクトップP

  2. MyIPTV Playerのダウンロード方法と使い方!初期設定からバッファリング対策まで完全解説

    旅行中にお気に入りのテレビ番組を見逃してしまうのが心配ではありませんか?MyIPTV Playerは、インターネット経由で遠隔地のテレビチャンネルを視聴できる人気の無料アプリです。Francis Bijumon氏によって開発され、Vbfnet Appsから公開されています。このメディアプレーヤーを使えば、URLまたはローカルファイルを通じてチャンネルを再生できます。他の同種プレーヤーと比較しても、MyIPTVの評価は非常に好評で、必要なのはアクティブなインターネット接続だけです。この記事では、MyIPTV Playerのダウンロード方法と、テレビ番組を視聴するための具体的な使い方について詳しく