Ruby 기본 정리 (타입, 변수, 클래스, 모듈)
Overview
Ruby 언어에 관한 정보를 정리합니다.
나중에 더 추가될 수도 있습니다.
1. Variables
number = 1
puts number # => 1
large_number = 2
변수 이름이 길어질 때는 snake_case 를 사용합니다.
2. Data Types
Ruby 에는 다음과 같은 타입들이 있습니다.
기본적으로 최상위 타입은 전부 Object 입니다.
- Numbers
- Strings (texts)
- True, False, and Nil
- Symbols
- Arrays
- Hashes
2.1. Numbers
a = 1 # Integer
b = 1.2 # Float
c = 1_000_000 # == 1,000,000
숫자 타입입니다.
Integer, Float, BigDecimal 등등이 존재합니다.
2.2. Strings
"hi" + "hi" # => "hihi"
"hi" * 3 # => "hihihi"
"hello".upcase # => "HELLO"
"hello".capitalize # => "Hello"
"hello".length # => 5
"hello".reverse # => "olleh"
문자열입니다.
큰 따옴표가 아니라 작은 따옴표로 묶어서 사용할 수도 있습니다.
2.3. Symbols
Symbol 은 문자열과 비슷하지만 조금 다릅니다.
앞에 콜론이 붙어있으면 Symbol 입니다. (:something
)
Symbol 은 보통 텍스트를 데이터
가 아닌 코드
로 사용할 때 많이 사용합니다.
예를 들면 Hash Key 같은 경우 Key 로 사용한다는 것에 의미가 있죠.
그리고 String 과 비교했을 때 가장 두드러지는 부분은 같은 Symbol 은 동일한 객체를 참조한다는 점입니다.
String 은 매번 다른 객체를 생성하지만 Symbol 은 동일한 객체를 재사용합니다.
"string".object_id # => 2409957680
"string".object_id # => 2682952200
"string".object_id # => 2409974840
:symbol.object_id # => 881948
:symbol.object_id # => 881948
:symbol.object_id # => 881948
2.4. Array
a = [1, 2, 3]
a[0] # 1
a << 4 # a == [1, 2, 3, 4]
a[6] = 7 # a == [1, 2, 3, 4, nil, nil, 7]
a.first # 1
a.last # 7
a.length # 7
a.compact # [1, 2, 3, 4, 7]
a.compact
처럼 새로운 배열을 뽑을 때 현재 배열 자체를 바꾸고 싶다면 마지막에 ! 를 붙여주면 됩니다. (a.compact!
)
2.5. Hash
dictionary = { "one" => "eins", "two" => "zwei", "three" => "drei" }
dictionary["one"] # "eins"
dictionary["zero"] = "null" # insert
Key, Value 로 지정할 수 있는 타입에는 제한이 없습니다.
위 예시에서는 둘다 String 이었지만 Key 로 Integer 를 사용해도 되고 Value 에 배열이 들어가도 됩니다.
가장 많이 사용되는 Key 타입은 Symbol 입니다.
Symbol Key Hash 는 좀더 간편하게 표현할 수 있게 지원해줍니다.
아래 두 문법은 동일한 기능을 갖습니다.
a = { one: "eins", two: "zwei", three: "drei" }
a = { :one => "eins", :two => "zwei", :three => "drei" }
a[:one] # => "eins"
2. String interpolation
str = "Ruby"
puts "Hello, #{str}"
puts "1 + 2 = #{1 + 2}"
#{}
로 감싸면 다른 변수를 넣을 수 있습니다.
3. Method
def add(a, b)
a + b
end
add(1, 2) # => 3
add 1, 2 # => 3
Ruby 의 메서드는 return 이 없어도 맨 마지막 값을 자동으로 리턴합니다.
메서드를 호출할 때는 괄호로 감싸지 않아도 호출 가능합니다.
3.1. 괄호 생략
def print
puts "Hello, World!"
end
print() # => Hello, World!
print # => Hello, World!
메서드를 정의할 때 파라미터가 필요 없다면 생략할 수 있습니다.
호출 역시 마찬가지로 생략 가능합니다.
3.2. Default parameter
def print(greeting = "Hello", target = "World")
puts "#{greeting} #{target}"
end
print # Hello World
print("Hi") # Hi World
print("Hi", "Ruby") # Hi Ruby
파라미터 값이 없을 때의 기본값을 넣어줄 수 있습니다.
4. Class
class Person
end
클래스는 위와 같이 정의할 수 있습니다.
person = Person.new
처럼 객체를 생성할 수 있습니다.
4.1. Ruby Class 의 인스턴스, 클래스란?
Ruby 에서는 인스턴스 변수, 인스턴스 메서드, 클래스 변수, 클래스 메서드 등 인스턴스와 클래스라는 단어가 많이 나옵니다.
Ruby 에서 말하는 인스턴스, 클래스는 다음과 같습니다.
- 인스턴스
- 객체를 생성 (
new
) 해야 사용할 수 있는 변수, 메서드 - 인스턴스 변수는 객체끼리 독립된 값을 가짐
- 객체를 생성 (
- 클래스
- 객체 생성 없이 클래스 자체로 생성할 수 있는 변수, 메서드
- 클래스 변수는 같은 클래스로 생성된 여러 객체가 공유함
4.2. 인스턴스 변수
class Person
def print_name
puts "My Name is #{@name}"
end
end
p1 = Person.new
p1.print_name # My Name is
p1.name = "woody"
p1.print_name # My Name is woody
인스턴스 변수는 클래스 내에서 @
가 붙어있는 변수를 의미합니다.
일회성으로 사용되고 끝나는 변수와 달리 생성된 인스턴스 내에서 계속 사용할 수 있습니다.
4.3. 클래스 변수
class Person
@@name = "default"
def name
@@name
end
def name=(name)
@@name = name
end
end
p1 = Person.new
p2 = Person.new
p1.name # default
p2.name # default
p1.name = "woody"
p1.name # woody
p2.name # woody
클래스 변수는 모든 객체가 공유하는 변수입니다.
위 코드에서 볼수있듯이 p1
의 변수를 바꾸었지만 p2
의 변수도 똑같이 바뀐 것을 볼 수 있습니다.
이것이 인스턴스 변수와의 차이점입니다.
4.4. 상수
class Person
AGE = 24
end
Person::AGE # 24
상수는 변하지 않는 변수입니다.
객체를 직접 생성하지 않아도 바로 사용할 수 있습니다.
4.5. 인스턴스 메서드, 클래스 메서드
class Sample
def print
puts "Instance Method"
end
def self.print
puts "Class Method"
end
end
Sample.new.print # Instance Method
Sample.print # Class Method
인스턴스 메서드는 객체를 생성한 뒤에 사용하는 메서드, 클래스 메서드는 그대로 사용하는 메서드입니다.
인스턴스 메서드는 def
처럼 평범하게 정의하면 되지만 클래스 메서드는 def self
를 사용해서 정의해야 합니다.
4.6. 생성자
class Person
def initialize(name)
@name = name
end
end
Person.new("woody") # => #<Person:0x000000012cebfeb0 @name="woody">
Person.new # ArgumentError (wrong number of arguments (given 0, expected 1))
Ruby 에서는 initialize
메서드로 생성자를 선언할 수 있습니다.
생성자를 선언하지 않으면 파라미터가 없는 기본 생성자를 사용할 수 있습니다.
인스턴스 변수를 초기화할 때는 일반적으로 생성자를 사용합니다.
4.7. Accessor (Getter, Setter)
class Person
attr_accessor :name
attr_reader :age
attr_writer :address
end
p = Person.new
# accessor (getter, setter)
p.name # => nil
p.name = "adsf"
p.name # => "asdf"
# reader (getter)
p.age # => nil
p.age # NoMethodError (undefined method `age=' for #<Person:0x000000012cb9ce90 @name="asdf">)
# writer (setter)
p.address # NoMethodError (undefined method `address' for #<Person:0x000000012cb9ce90 @name="asdf">)
p.address = "Seoul"
attr_accessor
, attr_reader
, attr_writer
를 사용해서 Getter, Setter 를 생성해줄 수 있습니다.
반대로 private 을 사용해서 Getter, Setter 를 숨길 수도 있습니다.
Accessor 로 지정한 변수는 인스턴스 변수로 사용 가능합니다.
4.8. 상속
class Fruit
attr_accessor :name
def print
puts "This is Fruit"
end
def super_method
puts "This is super class method"
end
end
class Apple < Fruit
def print
puts "This is Apple"
end
end
<
키워드를 사용해서 상속을 표현할 수 있습니다.
상속받은 클래스에서는 상위 클래스의 메서드나 변수를 사용할 수 있으며 오버라이드도 가능합니다.
4.9. 클래스 확장
클래스 확장이란 기존에 정의된 클래스에 새로운 메서드나 변수를 추가하는 것을 의미합니다.
새로운 기능을 추가하는 방법은 총 세가지지만 기존에 정의된 메서드를 중복 정의하는 경우 새롭게 덮어 씌운다는 공통점이 있습니다.
우선 Game
이라는 기본적인 클래스를 정의합니다.
class Game
def initialize
@name = "lol"
end
end
1. <<
키워드 사용
# 인스턴스
game = Game.new
class << game
attr_accessor :age
def print_one
puts "print_one: #{@name}"
end
end
# 클래스
class << Game
def print_class
puts "print_class"
end
end
<<
키워드를 사용하면 동적으로 새로운 변수, 메서드를 추가할 수 있습니다.
2. 확장 함수로 정의
# 인스턴스
def game.print_two
puts "print_two: #{@name}"
end
# 클래스
def Game.print_class
puts "print_class"
end
Kotlin 의 확장 함수와 비슷한 문법입니다.
3. 클래스 분할 정의
class Game
def print_three
puts "print_three: #{@name}"
end
def self.print_class
puts "print_class"
end
end
Ruby 는 클래스를 여러 번 정의할 수 있습니다.
분할 정의된 클래스의 내용은 기존 클래스에 그대로 추가됩니다.
5. Module
모듈 (Module) 이란 여러 가지 기능을 모은 것을 의미합니다.
모듈은 크게 두 가지 목적으로 사용합니다.
- Namespace 구분
- 중복된 이름의 클래스를 사용할 때 충돌이 발생하지 않도록 합니다
- V1, V2 처럼 버전 구분을 할 때 유용
- 중복된 기능 모으기
- 여러 곳에서 사용하는 중복된 기능을 분리해서 각각의 모듈에 담을 수 있습니다
5.1. Definition
module Sample
end
모듈은 위와 같이 정의할 수 있습니다.
5.2. Namespace
module V1
class API
def self.call
puts "Call API v1"
end
end
end
module V2
class API
def self.call
puts "Call API v2"
end
end
end
V1::API.call # Call API v1
V2::API.call # Call API v2
원래 동일한 이름의 클래스와 메서드를 선언하면 분할 정의로 취급하여 기존 메서드는 사라졌습니다.
하지만 모듈을 사용해 분리했기 때문에 두 클래스는 같은 이름과 같은 메서드를 같지만 다른 클래스, 메서드처럼 동작합니다.
5.3. Module Function
module Person
ADDRESS = "Seoul"
def age
@age
end
def age=(age)
@age = age
end
def company
"company"
end
module_function :age, :age=
end
Person::ADDRESS # => "Seoul"
Person.age # => nil
Person.age = 24
Person.age # => 24
Person.company # NoMethodError (undefined method `company' for Person:Module)
모듈에서 변수나 메서드를 정의할 수 있습니다.
모듈은 객체처럼 생성이 불가능하기 때문에 사실상 모든 변수와 메서드는 클래스 변수, 메서드라고 볼 수 있습니다.
정의된 메서드를 모듈에서 직접 사용하기 위해선 module_function
키워드를 사용해서 지정해줘야 합니다.
5.4. Mixin
Module 을 Class 에서 참조하면 마치 클래스의 메서드인것처럼 사용할 수 있습니다.
이걸 mix-in 이라고 부릅니다.
모듈을 mixin 하는 방법에는 include
, prepend
, extend
총 세가지가 있습니다.
5.4.1. Include
module IncludeModule
def hello
puts "Hello, Include"
end
end
class Person
include IncludeModule
def hello
puts "Hello, Person"
end
end
person = Person.new
person.hello # Hello, Person
include
키워드를 사용해서 모듈을 참조하면 모듈의 메서드를 인스턴스 메서드로 사용할 수 있습니다.
만약 동일한 이름의 메서드가 모듈과 클래스 양쪽에 정의되어 있다면 클래스의 메서드를 우선시합니다.
5.4.2. Prepend
module PrependModule
def hello
puts "Hello, Prepend"
end
end
class Person
prepend PrependModule
def hello
puts "Hello, Person"
end
end
person = Person.new
person.hello # Hello, Prepend
prepend
는 include
와 마찬가지로 모듈의 메서드를 인스턴스 메서드로 사용할 수 있습니다.
하지만 include
와 다르게 동일한 이름의 메서드가 정의되어 있을 경우 모듈의 메서드를 우선시합니다.
5.4.3. Extend
module ExtendModule
def hello
puts "Hello, Extend"
end
end
class Person
extend ExtendModule
end
Person.hello # Hello, Extend
extend
는 다른 mixin 과 다르게 클래스 메서드로 사용 가능합니다.
중복 정의된 메서드는 include
와 마찬가지로 클래스에 있는 걸 우선시합니다.
5.4.4. Ancestors
만약 여러 개의 모듈을 한번에 include
하면 어떻게 될까?
include
, prepend
가 여러개 섞여 있으면 어떻게 될까?
Ruby 에서는 어떤 오브젝트가 젤 우선시 되는지 ancestors
메서드로 확인할 수 있습니다.
module Include1
end
module Include2
end
module Prepend1
end
module Prepend2
end
class Human
end
class Person < Human
include Include1
include Include2
prepend Prepend1
prepend Prepend2
end
Person
클래스에서는 여러 모듈을 동시에 mixin 하고 있습니다.
prepend
-> Person
-> include
순서까지는 쉽게 짐작할 수 있지만 같은 키워드를 사용한 모듈들은 어떤게 우선시 되는지 알 수 없습니다.
게다가 상위 클래스에도 중복된 메서드가 정의되어 있다면 더욱 더 헷갈립니다.
이를 확인할 수 있는게 ancestors
메서드입니다.
Person.ancestors
=> [Prepend2, Prepend1, Person, Include2, Include1, Human, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, ActiveSupport::Dependencies::Loadable, ActiveSupport::Tryable, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]
Person.ancestors
를 사용하면 해당 클래스가 참조하고 있는 모든 오브젝트가 나옵니다.
그리고 중복 정의된 메서드가 있으면 앞 순서에 있는 오브젝트의 메서드가 우선시됩니다.
그래서 Person
클래스에서 중복된 메서드가 있으면 Prepend2
모듈의 메서드가 사용 됩니다.
모든 클래스는 오브젝트라서 BasicObject
가 최상단에 있는 걸 알 수 있습니다.