지난번에 만들었던 Person 클래스를 가지고 조금 더 이야기를 해보자. 아래 Person 클래스 코드가 있다.
class Person
def initialize(name, phone, address)
@name = name
@phone = phone
@address = address
end
def name
@name
end
def phone
@phone
end
def address
@address
end
def phone=(phone)
@phone = phone
end
def address=(address)
@address = address
end
def info
"#{name} / #{phone} / #{address}"
end
end
위 코드를 보면 현재의 속성 정보를 돌려주는 메서드(getter 라고 부름.)와 속성 정보를 변경할 수 있게 해주는 메서드(setter라고 부름.)가 모두 단순히 해당 속성에 대한 인스턴스 변수를 그냥 있는 그대로 돌려주거나 인수로 받은 값을 그냥 그대로 인스턴스 변수에 설정하는 역할만 하고 있다.
이렇게 단순한 getter 와 setter 를 만드는 일은 클래스를 작성하다 보면 흔하게 발생하는 경우라서 Ruby 는 이런 getter 와 setter 를 보다 간단하게 만들 수 있는 방법을 제공한다.
attr_reader, attr_writer, attr_accessor 가 바로 그러한 단순한 작업에서 우리 손가락을 쉴 수 있게 해주는 고마운 메서드들이다.
attr_reader 는 getter 를 자동으로 만들어 주고 attr_writer 는 setter 를 자동으로 만들어 주며, 마지막 attr_accessor 는 getter 와 setter 모두를 만들어 준다.
아래 처럼 irb를 실행하여 세 가지 메서드의 사용법을 바로 확인해 보자.
?> class Person
?> attr_reader :name
?> attr_writer :age
?> attr_accessor :job
?>
?> def initialize(name, age, job)
?> @name = name
?> @age = age
?> @job = job
?> end
>> end
=> :initialize
>>
>> p = Person.new("나가수", 30, "가수")
=> #<Person:0x000002ce59d87fa0 @name="나가수", @age=30, @job="가수">
>> p.name
=> "나가수"
>> p.name = "너가수"
NoMethodError (undefined method `name=` for #<Person:0x000002ce59d87fa0>)
>>
>> p.age
NoMethodError (undefined method `age` for #<Person:0x000002ce59d87fa0>)
>>
>> p.age = 31
=> 31
>> p.job
=> "가수"
>> p.job = "가수 및 배우"
=> "가수 및 배우"
>> p
=> #<Person:0x000002ce59d87fa0 @name="나가수", @age=31, @job="가수 및 배우">
사용법은 정말 간단하다.
name 속성처럼 getter 만 필요하다면 해당 속성명을 인수로 주고 attr_reader 메서드를 호출하면 되고 age 속성처럼 setter 만 필요하다면 attr_writer 를, job 속성처럼 getter 와 setter 모두 필요하다면 attr_accessor 를 호출하면 된다.
속성명은 문자열로 줘도 되지만 이럴 때 쓰라고 심볼이 있는거니 심볼을 사용하도록 하자. 그리고 당연히 속성명을 여러 개 콤마를 붙여 넘겨도 된다.
위의 테스트 결과를 보면 Person 클래스에 이전과 같이 메서드를 직접 정의하지 않아도 name 속성에 대해서는 getter 를, age 속성에 대해서는 setter 를, 그리고 job 속성에 대해서는 getter 와 setter 둘 다를 정상적으로 호출할 수 있게 되었다.
또한 name 속성에 대한 setter 와 age 속성에 대한 getter 를 호출한 경우에는 NoMethodError 가 발생한 것을 볼 수 있다.
이제 Person 클래스의 코드를 아래 처럼 간단하게 줄여 쓸 수 있다.
class Person
attr_reader :name
attr_accessor :phone, :address
def initialize(name, phone, address)
@name = name
@phone = phone
@address = address
end
def info
"#{name} / #{phone} / #{address}"
end
end
테스트를 위해 먼저 앞의 코드를 D:/blog/ruby/object_and_class 폴더 아래 person.rb 파일에 저장하자.
그리고 아래처럼 같은 폴더에서 irb를 실행한 후 require를 통해 person.rb 파일을 불러와 테스트를 진행해 보자.
>> require './person'
=> true
>> p = Person.new("홍길동", "010-1111-1111", "서울 특별시 강동구")
=> #<Person:0x00000198f0468428 @name="홍길동", @phone="010-1111-1111", @address="서울 특별시 강...
>> puts p.info
홍길동 / 010-1111-1111 / 서울 특별시 강동구
=> nil
>> puts p
#<Person:0x00000198f0468428>
=> nil
>> p.to_s
=> "#<Person:0x00000198f0468428>"
그런데 위에서 'puts p' 의 실행 결과를 보면 #<Person:0x...> 처럼 p 가 참조하고 있는 객체의 클래스명과 16진수로 표현된 숫자 값이 출력되는데 그 아래 코드를 보면 to_s 메서드가 그것과 동일한 문자열을 돌려주는 것을 볼 수 있다.
우리가 작성한 Person 클래스에는 to_s 메서드가 보이지 않는데 어떻게 to_s 메서드를 호출할 수 있는지는 나중에 살펴보기로 하고, 여기서 중요한 것은 puts 메서드에 객체를 인수로 줄 경우 그 객체의 to_s 메서드를 호출한 결과를 화면에 출력한다는 사실이다. 그렇다면 puts 메서드를 통해 우리가 원하는 내용을 출력하고 싶다면 그것에 맞도록 to_s 메서드를 만들면 된다는 말인데, 실제 그런지 아래 처럼 Person 클래스의 코드를 수정해서 테스트해 보자.
class Person
attr_reader :name
attr_accessor :phone, :address
def initialize(name, phone, address)
@name = name
@phone = phone
@address = address
end
def info
"#{name} / #{phone} / #{address}"
end
def to_s
info
end
end
>> require './person'
=> true
>> p = Person.new("홍길동", "010-1111-1111", "서울 특별시 강동구")
=> #<Person:0x0000025779663208 @name="홍길동", @phone="010-1111-1111", @address="서울 특별시 강...
>> puts p
홍길동 / 010-1111-1111 / 서울 특별시 강동구
=> nil
이제 Person 클래스의 객체를 puts 메서드로 출력하면 우리가 원하는 문자열이 화면에 출력되는 것을 볼 수 있다.
이미 Person 클래스에는 info 라는 메서드가 있기에 to_s 메서드에서는 단순히 info 메서드를 호출하기만 하였다.
따라서 to_s 메서드를 호출하면 먼저 info 메서드가 호출되고 info 메서드가 반환한 값을 그대로 to_s 메서드가 반환하게 된다.
See you again~~