Yanonoblog!

こつこつと

クラスの適切な設計

はじめに

本記事では「良いコード 悪いコードで学ぶ設計入門」で学んだ内容と別途気になって調べた内容や知識も含めアウトプットしています。

書籍に記載されている内容を順序立ててまとめるというよりは、整理しておきたい周辺の知識と深ぼった内容を雑多に書いています。

書籍ではJavaで説明されていたのですが本記事ではRubyに置き換えながら解説しています。

クラス

クラスは、特定の機能や役割を持つコードのまとまりを表現するために使用されます。

Railsにおいては属性(データ)とメソッド(処理)を持ち、オブジェクトを生成することができます。これによってデータとそれに関連する振る舞いを1つのまとまりとして扱うことができます。

モデルクラス

ActiveRecordを継承したモデルクラスは、データベースのテーブルと対応したデータモデルを表現します。

属性はテーブルのカラムに対応し、メソッドはデータの操作やビジネスロジックを実装します。

コントローラークラス

リクエストの受け取りやレスポンスの生成を担当するコントローラークラスは、特定の機能やアクションを実行するための処理を記述します。

リクエストのルーティングやビューへのデータの提供なども行います。

サービスクラス

ビジネスロジックや複雑な処理を切り出して扱うためのサービスクラスは、コントローラーなどから呼び出され、特定のタスクを実行します。

データの操作や外部APIの呼び出しなどを行う場合に使用されます。

適切な設計

HP管理に関するコード

戦闘を伴うゲームなどで、主人公の生命力を表すヒットポイント(HP)以下のように変数で定義されているとします。

hit_point = 100

ダメージを受けてヒットポイントが減少するロジックも必要になりますので以下のように定義します。

hit_point = hit_point - damage_amount
if hit_point < 0
  hit_point = 0
end

「回復アイテムなどで回復する仕様を追加したい」となった場合、以下のような実装がまた必要です。

hit_point = hit_point + recovery_amount
if hit_point > 100
  hit_point = 100
end
アンチパターン

上記で説明したようなコードを分散して定義するとヒットポイントの管理や制約は分散してしまい、重複したコードが発生する可能性があります。

例えば、ヒットポイントの範囲制約を変更する場合には、複数の箇所で修正が必要となります。

さらに、複数のクラスで同様のヒットポイント管理の実装が行われる可能性もあります。

改善例

Railsではオブジェクト指向の原則を活用して、ヒットポイントを表現するクラスを作成することが一般的です。例えば、以下のようにクラスを作成することができます。

HitPointクラスのコンストラクタ(初期化メソッド)において、valueは引数として受け取ります。

class HitPoint < ActiveRecord::Base
    validates :value, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }

  attr_reader :value

  def initialize(value)
    validate_value(value)
    @value = value
  end

  # ダメージを受けるメソッド
  def damage(damage_amount)
    damaged = value - damage_amount
    corrected = [damaged, 0].max
    update(value: corrected) # ヒットポイントの値を更新
  end

  # 回復するメソッド
  def recover(recovery_amount)
    recovered = value + recovery_amount
    corrected = [recovered, 100].min
    update(value: corrected) # ヒットポイントの値を更新
  end
end

def validate_value(value)
    unless value.is_a?(Integer) && value >= 0 && value <= 999
      raise ArgumentError, 'ヒットポイントの値は0以上999以下である必要があります'
    end
  end
end
damageメソッド

ヒットポイントを減少させるためのメソッドです。

受け取ったdamage_amountを現在のヒットポイントから引き、その結果をdamagedに代入します。

その後、[damaged, 0].maxでヒットポイントが0未満にならないように修正し、updateメソッドを使ってデータベース上のヒットポイントの値を更新します。

recoverメソッド

ヒットポイントを回復させるためのメソッドです。

受け取ったrecovery_amountを現在のヒットポイントに加え、その結果をrecoveredに代入します。

その後、[recovered, 999].min**でヒットポイントが100を超えないように修正し、**update`メソッドを使ってデータベース上のヒットポイントの値を更新します。

validate_valueメソッド (バリデーション)

コンストラクタでは、0~100の範囲外は不正な値として弾くロジックになっています。

不正な値が紛れ込まないようにしかけをしておくと、バグ化しない頑強なクラス構造になります。このように、意図を持って適切に設計することで、保守や変更がしやすい構造になります。

参考

続く…

コメント

本記事の内容は以上になります!

書籍の続きのアウトプットも随時更新したいと思います。


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky