LoginSignup
40
41

オブジェクト指向で作りたい!クラス実装やリファクタリングする時のヒント

Posted at

はじめに

みなさんオブジェクト指向開発してますか?

大規模な開発であれば実装前にきっちりとクラス設計を行っていることでしょう。
また、大規模でなくても機能を跨いだり、複数の担当者で利用するクラスなどは、事前に慎重に設計すべきです。

開発現場にきちんとクラスの設計ルールがある場合は、この記事は読まなくて大丈夫です。

しかし、小規模の機能追加なんかの場合、クラス設計も実装も個人に任されることがあると思います。何ならオブジェクト指向で作らなくても全然構わない状況かもしれません。

でも、オブジェクト指向で作りたいですよね?

この記事では、オブジェクト指向について基本的な知識がある人向けに、実装の最初の取っ掛かりや、リファクタリングする際の観点とか、そういったヒントを紹介したいと思います。

(注意)チームの開発・設計・コーディング等、各種ルール・規約を遵守してくださいね。

サンプルケース

ここではカラーコードを取り扱うものをサンプルケースとしました。
HTMLなどで色を指定したりするのに出てくるカラーコードです。

256諧調のグレイスケールあるいは8色カラーコードからRGB24bitのカラーコードを求めるコードを実装します。

オブジェクト指向、手続き型のどちらで実装しても何の問題もないケースですが、あえてオブジェクト思考で書いてみます。

カプセル化

オブジェクト思考で開発するなら、まずはカプセル化から始めましょう。

中級者以上はバリューオブジェクトなどが良いのでしょうが、それらの説明はちょっと置いておいて、まずはカプセル化しましょう。

サンプルケースを1つのクラスで実装してみます。
RGB24bitのカラーコードを求めるということから、RGBの値をデータ構造として持つクラスを考えてみます。

#
# カラー
#
class RgbColor

  def initialize(red, green, blue)
    @red = red
    @green = green
    @blue = blue
  end

  # RGBの値からカラーコードを求める
  def code
    (@red * 256 * 256) + (@green * 256) + (@blue)
  end

  # 16進数のカラーコード
  def to_s
    '#' + format("%06x", code)
  end

  # グレイスケールで作成
  def self.create_gray_scale(level)
    # RGPを同値にする
    new(level, level, level)
  end

  # 8色カラーで作成
  def self.create_eight_color(color_code)
    # color_code は、3bit
    # 各桁は、green, red, blue を表す
    # 000 : 黒
    # 001 : 青
    # 010 : 赤
    # 011 : 紫(マゼンタ)
    # 100 : 緑
    # 101 : 水色(シアン)
    # 110 : 黄色(イエロー)
    # 111 : 白
    green = color_code[2] * 255
    red = color_code[1] * 255
    blue = color_code[0] * 255
    new(red, green, blue)
  end

end

#
# 実行
#
puts '---------------------------------'
puts 'gray scale'
puts RgbColor.create_gray_scale(0).to_s
puts RgbColor.create_gray_scale(1).to_s
puts RgbColor.create_gray_scale(2).to_s
puts RgbColor.create_gray_scale(127).to_s
puts RgbColor.create_gray_scale(255).to_s
puts '---------------------------------'
puts 'eight color'
puts RgbColor.create_eight_color(0).to_s
puts RgbColor.create_eight_color(1).to_s
puts RgbColor.create_eight_color(2).to_s
puts RgbColor.create_eight_color(3).to_s
puts RgbColor.create_eight_color(4).to_s
puts RgbColor.create_eight_color(5).to_s
puts RgbColor.create_eight_color(6).to_s
puts RgbColor.create_eight_color(7).to_s

実行結果

---------------------------------
gray scale
#000000
#010101
#020202
#7f7f7f
#ffffff
---------------------------------
eight color
#000000
#0000ff
#ff0000
#ff00ff
#00ff00
#00ffff
#ffff00
#ffffff

ちなみに、このサンプルコードは、RGBというデータ構造について凝集度が高いコード。通信的凝集度が高いコードと言えます。

ファクトリークラス

値を求めるクラスと、クラスを生成するファクトリークラスに分割してみます。

サンプルコード2

#
# カラー
#
class RgbColor

  def initialize(red, green, blue)
    @red = red
    @green = green
    @blue = blue
  end

  # RGBの値からカラーコードを求める
  def code
    (@red * 256 * 256) + (@green * 256) + (@blue)
  end

  # 16進数のカラーコード
  def to_s
    '#' + format("%06x", code)
  end

end

# グレイスケールで作成
class GrayScaleColorFactory
  def self.create(level)
    # RGPを同値にする
    RgbColor.new(level, level, level)
  end

end

# 8色カラーで作成
class EightColorFactory
  def self.create(color_code)
    # color_code は、3bit
    # 各桁は、green, red, blue を表す
    green = color_code[2] * 255
    red = color_code[1] * 255
    blue = color_code[0] * 255
    RgbColor.new(red, green, blue)
  end

end

#
# 実行
#
puts '---------------------------------'
puts 'gray scale'
puts GrayScaleColorFactory.create(0).to_s
puts GrayScaleColorFactory.create(1).to_s
puts GrayScaleColorFactory.create(2).to_s
puts GrayScaleColorFactory.create(127).to_s
puts GrayScaleColorFactory.create(255).to_s
puts '---------------------------------'
puts 'eight color'
puts EightColorFactory.create(0).to_s
puts EightColorFactory.create(1).to_s
puts EightColorFactory.create(2).to_s
puts EightColorFactory.create(3).to_s
puts EightColorFactory.create(4).to_s
puts EightColorFactory.create(5).to_s
puts EightColorFactory.create(6).to_s
puts EightColorFactory.create(7).to_s

RGBの値からカラーコードを求めるRGBカラークラス。
グレイスケールからRGBカラークラスを生成するクラス。
8色カラーコードからRGBカラークラスを生成するクラス。

ファクトリークラスがバリーエーションを生み出すパターン。
カラーコードを保持する責務、カラークラスを生成する責務。

それぞれのクラスの責務を分けてみました。

単一責任の原則に則っているように見えますが、私的には次のサンプルコードの方がより単一責任だと感じています。

クラスでバリエーションを表現する

最初に作ったクラスを別な観点でクラス分割してみます。
グレイスケールを表すクラスと、8色カラーを表すクラスに分割します。
さっきは、値を保持するクラスと、生成するクラスに分割しましたが、今回はバリエーションに着目して分割してみます。

サンプルコード3

#
# グレイスケールカラー
#
class GrayScaleColor

  def initialize(level)
    @level = level
  end

  # RGB毎にlevelを適用してラーコードを求める
  def code
    (@level * 256 * 256) + (@level * 256) + (@level)
  end

  # 16進数のカラーコード
  def to_s
    '#' + format("%06x", code)
  end

end

# 8色カラー
class EightColor

  def initialize(color_code)
    @color_code = color_code
  end

  # RGBの値からカラーコードを求める
  def code
    green = @color_code[2] * 255
    red = @color_code[1] * 255
    blue = @color_code[0] * 255
    (red * 256 * 256) + (green * 256) + (blue)
  end

  # 16進数のカラーコード
  def to_s
    '#' + format("%06x", code)
  end

end

#
# 実行
#
puts '---------------------------------'
puts 'gray scale'
puts GrayScaleColor.new(0).to_s
puts GrayScaleColor.new(1).to_s
puts GrayScaleColor.new(2).to_s
puts GrayScaleColor.new(127).to_s
puts GrayScaleColor.new(255).to_s
puts '---------------------------------'
puts 'eight color'
puts EightColor.new(0).to_s
puts EightColor.new(1).to_s
puts EightColor.new(2).to_s
puts EightColor.new(3).to_s
puts EightColor.new(4).to_s
puts EightColor.new(5).to_s
puts EightColor.new(6).to_s
puts EightColor.new(7).to_s

ミックスイン

グレイスケールカラーを表すクラスと、8色カラーを表すクラスの2つのクラスで同じ処理を行うメソッドがあります。
このメソッドをモジュールとして共通化し、それぞれのクラスでインクルードします。

継承は極力使わないようにしたいですね。
スーパークラスはサブクラスの不要な責務を背負い込むことになりがちなので。

サンプルコード4

module Formatter

  # 16進数のカラーコード
  def to_s
    '#' + format("%06x", code)
  end

end

#
# グレイスケールカラー
#
class GrayScaleColor
  include Formatter

  def initialize(level)
    @level = level
  end

  def code
    (@level * 256 * 256) + (@level * 256) + (@level)
  end

end

# 8色カラー
class EightColor
  include Formatter

  def initialize(color_code)
    @color_code = color_code
  end

  def code
    green = @color_code[2] * 255
    red = @color_code[1] * 255
    blue = @color_code[0] * 255
    (red * 256 * 256) + (green * 256) + (blue)
  end

end

#
# 実行
#
puts '---------------------------------'
puts 'gray scale'
puts GrayScaleColor.new(0).to_s
puts GrayScaleColor.new(1).to_s
puts GrayScaleColor.new(2).to_s
puts GrayScaleColor.new(127).to_s
puts GrayScaleColor.new(255).to_s
puts '---------------------------------'
puts 'eight color'
puts EightColor.new(0).to_s
puts EightColor.new(1).to_s
puts EightColor.new(2).to_s
puts EightColor.new(3).to_s
puts EightColor.new(4).to_s
puts EightColor.new(5).to_s
puts EightColor.new(6).to_s
puts EightColor.new(7).to_s

委譲

さっきはミックスインを使って実現しましたが、委譲をつかった例も紹介します。
継承よりミックスイン。ミックスインより委譲を使いたいところです。
継承とミックスインは静的に依存関係が決まりますが、委譲は依存関係を動的に変更することもできます。(サンプルコードは静的な依存関係です)

サンプルコード5

class HexFormatter
  def self.to_s(color)
    '#' + format("%06x", color.code)
  end
end

#
# グレイスケールカラー
#
class GrayScaleColor

  def initialize(level)
    @level = level
  end

  def code
    (@level * 256 * 256) + (@level * 256) + (@level)
  end

  def to_s
    HexFormatter.to_s(self)
  end

end

# 8色カラー
class EightColor

  def initialize(color_code)
    @color_code = color_code
  end

  def code
    green = @color_code[2] * 255
    red = @color_code[1] * 255
    blue = @color_code[0] * 255
    (red * 256 * 256) + (green * 256) + (blue)
  end

  def to_s
    HexFormatter.to_s(self)
  end

end

#
# 実行
#
puts '---------------------------------'
puts 'gray scale'
puts GrayScaleColor.new(0).to_s
puts GrayScaleColor.new(1).to_s
puts GrayScaleColor.new(2).to_s
puts GrayScaleColor.new(127).to_s
puts GrayScaleColor.new(255).to_s
puts '---------------------------------'
puts 'eight color'
puts EightColor.new(0).to_s
puts EightColor.new(1).to_s
puts EightColor.new(2).to_s
puts EightColor.new(3).to_s
puts EightColor.new(4).to_s
puts EightColor.new(5).to_s
puts EightColor.new(6).to_s
puts EightColor.new(7).to_s

最後に

サンプルコードはどれが正解というものではなくて、クラス設計やリファクタリングする際の考え方のヒントとして、引き出しに入れておくことができれば良いなと思っています。

一人でコードを書いている時「このクラス設計で良かったのか?」と自問する時に、ここで挙げたものを照らし合わせて考えてみてはどうでしょうか?

勿論、これだけでなく様々な概念や原則

SOLID、GRASP、DDD、Clean Architecture .etc

こういったものに照らし合わせて考えることができれば、さらに良いクラスとなっていくことでしょう。

40
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
41