継承 & ミックスイン
継承とミックスインは、Rubyのオブジェクト指向プログラミングの強力な機能です。T-Rubyはこれらの概念を型安全性で拡張し、強力な型保証を維持しながら複雑なクラス階層を構築し、モジュールを通じて機能を共有できるようにします。
基本的な継承
クラスは親クラスから継承し、そのメソッドとインスタンス変数にアクセスできます:
class Animal
attr_accessor :name: String
attr_accessor :age: Integer
def initialize(name: String, age: Integer)
@name = name
@age = age
end
def speak(): String
"Some sound"
end
def info(): String
"#{@name} is #{@age} years old"
end
end
class Dog < Animal
attr_accessor :breed: String
def initialize(name: String, age: Integer, breed: String)
super(name, age)
@breed = breed
end
def speak(): String
"Woof!"
end
def fetch(item: String): String
"#{@name} fetched the #{item}"
end
end
class Cat < Animal
def speak(): String
"Meow!"
end
def scratch(): void
puts "#{@name} is scratching"
end
end
# 使用法
dog = Dog.new("Buddy", 5, "Golden Retriever")
puts dog.speak() # "Woof!"
puts dog.info() # "Buddy is 5 years old"
puts dog.fetch("ball") # "Buddy fetched the ball"
cat = Cat.new("Whiskers", 3)
puts cat.speak() # "Meow!"
cat.scratch()
メソッドのオーバーライド
子クラスは同じシグネチャで親メソッドをオーバーライドできます:
class Shape
def initialize()
@sides: Integer = 0
end
def area(): Float
0.0
end
def perimeter(): Float
0.0
end
def describe(): String
"A shape with #{@sides} sides"
end
end
class Rectangle < Shape
attr_accessor :width: Float
attr_accessor :height: Float
def initialize(width: Float, height: Float)
super()
@width = width
@height = height
@sides = 4
end
def area(): Float
@width * @height
end
def perimeter(): Float
2 * (@width + @height)
end
def describe(): String
"Rectangle: #{@width} x #{@height}"
end
end
class Circle < Shape
attr_accessor :radius: Float
def initialize(radius: Float)
super()
@radius = radius
@sides = 0 # 技術的には無限
end
def area(): Float
3.14159 * @radius * @radius
end
def perimeter(): Float
2 * 3.14159 * @radius
end
def describe(): String
"Circle with radius #{@radius}"
end
end
# 使用法
rect = Rectangle.new(10.0, 5.0)
puts rect.area() # 50.0
puts rect.perimeter() # 30.0
puts rect.describe() # "Rectangle: 10.0 x 5.0"
circle = Circle.new(5.0)
puts circle.area() # 78.53975
Superキーワード
親クラスのメソッドを呼び出すには super を使用します:
class Employee
attr_accessor :name: String
attr_accessor :salary: Float
def initialize(name: String, salary: Float)
@name = name
@salary = salary
end
def annual_bonus(): Float
@salary * 0.1
end
def total_compensation(): Float
@salary + annual_bonus()
end
end
class Manager < Employee
attr_accessor :team_size: Integer
def initialize(name: String, salary: Float, team_size: Integer)
super(name, salary)
@team_size = team_size
end
# オーバーライド: マネージャーはより良いボーナスを受け取る
def annual_bonus(): Float
base_bonus = super()
base_bonus + (@team_size * 1000.0)
end
def team_bonus(): Float
@team_size * 500.0
end
# チームボーナスを含めるためにオーバーライド
def total_compensation(): Float
super() + team_bonus()
end
end
# 使用法
employee = Employee.new("Alice", 50000.0)
puts employee.total_compensation() # 55000.0
manager = Manager.new("Bob", 80000.0, 5)
puts manager.annual_bonus() # 8000.0 + 5000.0 = 13000.0
puts manager.total_compensation() # 80000.0 + 13000.0 + 2500.0 = 95500.0
モジュールとミックスイン
モジュールを使用すると、複数のクラス間で機能を共有できます:
module Timestampable
def created_at(): Time?
@created_at
end
def updated_at(): Time?
@updated_at
end
def touch(): void
@updated_at = Time.now
end
def set_created(): void
@created_at = Time.now
@updated_at = Time.now
end
end
module Searchable
def search(query: String): Boolean
searchable_fields().any? { |field| field.include?(query) }
end
def searchable_fields(): Array<String>
[]
end
end
class BlogPost
include Timestampable
include Searchable
attr_accessor :title: String
attr_accessor :content: String
attr_accessor :author: String
def initialize(title: String, content: String, author: String)
@title = title
@content = content
@author = author
@created_at: Time? = nil
@updated_at: Time? = nil
set_created()
end
def searchable_fields(): Array<String>
[@title, @content, @author]
end
def update_content(new_content: String): void
@content = new_content
touch()
end
end
class Product
include Timestampable
include Searchable
attr_accessor :name: String
attr_accessor :description: String
attr_accessor :sku: String
def initialize(name: String, description: String, sku: String)
@name = name
@description = description
@sku = sku
@created_at: Time? = nil
@updated_at: Time? = nil
set_created()
end
def searchable_fields(): Array<String>
[@name, @description, @sku]
end
end
# 使用法
post = BlogPost.new("Hello World", "This is my first post", "Alice")
puts post.created_at()
post.update_content("Updated content")
puts post.updated_at()
puts post.search("first") # true
product = Product.new("Laptop", "Gaming laptop", "SKU-001")
puts product.search("Gaming") # true
モジュールメソッド
モジュールはインスタンス変数と連携するメソッドを定義できます:
module Validatable
def valid?(): Boolean
errors().empty?
end
def errors(): Array<String>
@errors ||= []
end
def add_error(message: String): void
@errors ||= []
@errors.push(message)
end
def clear_errors(): void
@errors = []
end
end
class User
include Validatable
attr_accessor :email: String
attr_accessor :age: Integer
def initialize(email: String, age: Integer)
@email = email
@age = age
@errors: Array<String> = []
end
def validate(): Boolean
clear_errors()
if !@email.include?("@")
add_error("Email must contain @")
end
if @age < 18
add_error("Must be 18 or older")
end
valid?()
end
end
# 使用法
user = User.new("invalid-email", 15)
if !user.validate()
user.errors().each { |e| puts e }
# "Email must contain @"
# "Must be 18 or older"
end
複数のミックスイン
クラスは複数のモジュールをインクルードできます:
module Comparable
def ==(other: self): Boolean
compare_fields() == other.compare_fields()
end
def !=(other: self): Boolean
!self.==(other)
end
def compare_fields(): Array<String | Integer>
[]
end
end
module Serializable
def to_hash(): Hash<String, String | Integer | Boolean>
{}
end
def to_json(): String
to_hash().to_json
end
end
module Cloneable
def clone(): self
self.class.new(*clone_params())
end
def clone_params(): Array<String | Integer>
[]
end
end
class Person
include Comparable
include Serializable
include Cloneable
attr_accessor :name: String
attr_accessor :age: Integer
attr_accessor :email: String
def initialize(name: String, age: Integer, email: String)
@name = name
@age = age
@email = email
end
def compare_fields(): Array<String | Integer>
[@name, @age, @email]
end
def to_hash(): Hash<String, String | Integer | Boolean>
{ "name" => @name, "age" => @age, "email" => @email }
end
def clone_params(): Array<String | Integer>
[@name, @age, @email]
end
end
# 使用法
person1 = Person.new("Alice", 30, "alice@example.com")
person2 = Person.new("Alice", 30, "alice@example.com")
person3 = Person.new("Bob", 25, "bob@example.com")
puts person1 == person2 # true
puts person1 == person3 # false
clone = person1.clone()
puts clone.name # "Alice"
継承と型安全性
T-Rubyは継承階層全体で型安全性を保証します:
class Vehicle
attr_accessor :make: String
attr_accessor :model: String
def initialize(make: String, model: String)
@make = make
@model = model
end
def start(): void
puts "Vehicle starting"
end
end
class Car < Vehicle
attr_accessor :num_doors: Integer
def initialize(make: String, model: String, num_doors: Integer)
super(make, model)
@num_doors = num_doors
end
def start(): void
puts "Car engine starting"
end
end
class Motorcycle < Vehicle
attr_accessor :has_sidecar: Boolean
def initialize(make: String, model: String, has_sidecar: Boolean)
super(make, model)
@has_sidecar = has_sidecar
end
def start(): void
puts "Motorcycle roaring to life"
end
end
def start_vehicle(vehicle: Vehicle): void
vehicle.start()
end
# 使用法 - CarとMotorcycleがVehicleを継承しているのですべて動作する
car = Car.new("Toyota", "Camry", 4)
motorcycle = Motorcycle.new("Harley", "Street 750", false)
start_vehicle(car) # "Car engine starting"
start_vehicle(motorcycle) # "Motorcycle roaring to life"
# 型チェックが機能する
vehicles: Array<Vehicle> = [car, motorcycle]
vehicles.each { |v| v.start() }
実践例: コンテンツ管理システム
継承とミックスインを示す完全な例です:
module Publishable
def publish(): void
@published = true
@published_at = Time.now
end
def unpublish(): void
@published = false
@published_at = nil
end
def is_published?(): Boolean
@published || false
end
def published_at(): Time?
@published_at
end
end
module Taggable
def tags(): Array<String>
@tags ||= []
end
def add_tag(tag: String): void
@tags ||= []
@tags.push(tag) unless @tags.include?(tag)
end
def remove_tag(tag: String): void
@tags ||= []
@tags.delete(tag)
end
def has_tag?(tag: String): Boolean
tags().include?(tag)
end
end
module Commentable
def comments(): Array<Comment>
@comments ||= []
end
def add_comment(comment: Comment): void
@comments ||= []
@comments.push(comment)
end
def comment_count(): Integer
comments().length
end
end
class Content
attr_accessor :title: String
attr_accessor :author: String
attr_reader :created_at: Time
def initialize(title: String, author: String)
@title = title
@author = author
@created_at = Time.now
end
def summary(): String
@title
end
end
class Article < Content
include Publishable
include Taggable
include Commentable
attr_accessor :body: String
attr_accessor :excerpt: String
def initialize(title: String, author: String, body: String, excerpt: String)
super(title, author)
@body = body
@excerpt = excerpt
@published: Boolean = false
@published_at: Time? = nil
@tags: Array<String> = []
@comments: Array<Comment> = []
end
def summary(): String
@excerpt
end
def word_count(): Integer
@body.split.length
end
end
class Page < Content
include Publishable
attr_accessor :slug: String
attr_accessor :content: String
def initialize(title: String, author: String, slug: String, content: String)
super(title, author)
@slug = slug
@content = content
@published: Boolean = false
@published_at: Time? = nil
end
def url(): String
"/pages/#{@slug}"
end
end
class Comment
attr_reader :author: String
attr_reader :body: String
attr_reader :created_at: Time
def initialize(author: String, body: String)
@author = author
@body = body
@created_at = Time.now
end
end
# 使用法
article = Article.new(
"Introduction to T-Ruby",
"Alice",
"T-Ruby is a typed superset of Ruby...",
"Learn about T-Ruby's type system"
)
article.add_tag("ruby")
article.add_tag("types")
article.add_tag("programming")
article.publish()
comment = Comment.new("Bob", "Great article!")
article.add_comment(comment)
puts article.is_published?() # true
puts article.has_tag?("ruby") # true
puts article.comment_count() # 1
puts article.word_count() # 5
page = Page.new("About", "System", "about", "This is the about page")
page.publish()
puts page.url() # "/pages/about"
ベストプラクティス
-
継承階層を浅く保つ: 深い継承チェーンよりもコンポジションを優先します。
-
共有動作にはモジュールを使用する: 複数の関連のないクラスが同じ機能を必要とする場合、モジュールを使用します。
-
すべてのメソッドパラメータに型を付ける: モジュールでも、すべてのパラメータと戻り値に型を付けます。
-
モジュールのインスタンス変数を初期化する: モジュールがインスタンス変数を使用する場合、インクルードするクラスで初期化します。
-
superを適切に使用する: メソッドをオーバーライドする場合、親の実装を呼び出す必要があるかどうかを検討します。
-
モジュールの要件を文書化する: モジュールが特定のメソッドやインスタンス変数を期待する場合、明確に文書化します。
一般的なパターン
テンプレートメソッドパターン
class DataImporter
def import(file_path: String): void
data = read_file(file_path)
parsed = parse_data(data)
validate_data(parsed)
save_data(parsed)
end
def read_file(file_path: String): String
# 共通実装
File.read(file_path)
end
def validate_data(data: Array<Hash<String, String>>): void
# 共通バリデーション
raise "Empty data" if data.empty?
end
def parse_data(data: String): Array<Hash<String, String>>
raise "Must implement parse_data"
end
def save_data(data: Array<Hash<String, String>>): void
raise "Must implement save_data"
end
end
class CSVImporter < DataImporter
def parse_data(data: String): Array<Hash<String, String>>
# CSVパース
[]
end
def save_data(data: Array<Hash<String, String>>): void
# データベースに保存
end
end
モジュールを使用したデコレータパターン
module Logging
def log(message: String): void
puts "[#{Time.now}] #{message}"
end
def with_logging(&block: Proc<[], void>): void
log("Starting operation")
block.call
log("Operation completed")
end
end
module Caching
def cache(): Hash<String, String>
@cache ||= {}
end
def cached(key: String, &block: Proc<[], String>): String
if cache().key?(key)
cache()[key]
else
result = block.call
cache()[key] = result
result
end
end
end
class DataService
include Logging
include Caching
def fetch_data(id: String): String
cached(id) do
with_logging do
# 高コストな操作
"Data for #{id}"
end
end
end
end
まとめ
T-Rubyの継承とミックスインは、型安全性を備えた強力なコード再利用メカニズムを提供します:
- 継承は型保証付きの「is-a」関係を作成します
- モジュールは関連のないクラス間で機能を共有します
- メソッドのオーバーライドは親クラスの型シグネチャを維持します
- Superは型を保持しながら親の実装を呼び出します
- 複数のミックスインは直交する関心事を組み合わせます
階層的な関係には継承を、横断的な関心事にはモジュールを使用してください。クラス階層全体で常に型安全性を維持しましょう。