객체 지향 이란?
객체 지향이란 프로그램을 수많은 ‘객체(object)’라는 단위로 나누고 이들의 상호작용으로 서술하는 방식이다.
초기 프로그래밍 방식인 절차적 프로그래밍 방식을 벗어나 작은 단위의 객체들을 만든 뒤, 이 객체들을 조합하여 큰 문제를 해결하는 방식이다.
Ruby에서의 객체 지향
루비에서 프로그래머가 다루는 모든 것은 객체이고 이러한 작업의 결과 또는 객체다.
객체 지향 코드를 작성할 때 일반적으로 실생활에서 있는 사물에서 모델 개념을 찾아 코드로 표현하려고 노력한다. 이러한 모델링 단계에서는 코드로 옮기고자 하는 것들을 성질이 비슷한 것으로 분류하는 작업이 필요하다. 주크박스를 예로 들자면 ‘노래’가 하나의 분류가 될 것이다. 루비에서는 이러한 독립적인 분류를 표현하기 위해서 클래스를 정의한다. 클래스는 상태(예를 들어 노래의 제목)와 상태를 사용하는 메서드(예를 들어 노래를 재생하는 메서드)로 이루어진다.
클래스를 정의했다면 클래스를 이용하여 인스턴스들을 만들 수 있다. Song이라는 클래스가 있으면 우리는 이 클래스를 이용하여 “Ruby Tuesday”, “Hyerim’s Song”, “Python” 같은 히트곡들을 인스턴스로 만들 수 있다. 객체라는 단어는 클래스 인스턴스와 같은 의미로 사용한다.
루비에서는 클래스에 있는 생성자(constructor)라고 불리는 특별한 메서드를 호출해 객체를 생성한다. 가장 일반적인 방법은 new 메서드를 호출하는 것이다.
song1 = Song.new("Ruby Tuesday")
song2 = Song.new("Enveloped in Python")
# 다른 노래들도 생성한다.
위 인스턴스들은 같은 클래스에서 생성되었지만 서로 다른 고유한 특징들을 가지고 있다. (마치 한 배에서 나온 자매가 매우 다른 고유한 특징을 가지고 있는 것처럼 😉) 먼저 모든 객체는 고유한 객체 아이디(object identifier, 줄여서 object ID)를 가진다. 다음으로 각 객체에는 객체별로 고유한 값을 가질 수 있는 인스턴스 변수를 정의할 수 있다. 이 인스턴스 변수에는 객체의 상태가 저장된다. 예를 들어 각 노래 인스턴스들은 자신의 인스턴스 변수에 노래 제목을 담게 된다.
클래스 정의 안에서 인스턴스 메서드를 정의할 수도 있다. 이 메서드는 클래스 내부적으로 호출하거나 (접근 제한자에 따라) 외부에서 사용할 수 있는 코드 묶음이다. 인스턴스 메서드들은 객체의 인스턴스 변수, 각 객체의 상태에 접근할 수 있다. 예를 들어 Song 클래스에 play라는 인스턴스 메서드를 정의한다고 해 보자. 이 때 변수 my_way가 특정한 Song의 인스턴스를 참조하고 있다면 이 인스턴스의 play 메서드를 호출해서 이 곡을 연주할 수 있다.
메서드는 객체에 메시지를 보내 호출할 수 있다. 메시지에는 메서드 이름과 메서드에 필요한 매개 변수들이 포함된다. (메서드 호출을 메시지라고 표현한다. - Smalltalk에서 유래) 메시지에는 메서드 이름과 메서드에 필요한 매개 변수들이 포함된다. 객체가 메시지를 받으면, 자신의 클래스에서 해당 메서드를 찾는다.
puts "gin joint".length
puts "Rick".index("c")
puts 42.even?
puts blackpink.play(song)
# 실행 결과
9
2
true
마지막처럼, 마마마지막처럼
위 코드에서는 puts 메서드를 호출한다. puts는 루비 표준 메서드이며 인자들을 콘솔에 출력하고 마지막에 줄바꿈을 추가한다.
각 행에서는 puts의 매개 변수로 메서드를 실행한 결과가 넘겨진다. 메서드 호출은 마침표 문자로 구분되는데 앞부분에 있는 것은 수신자(recevier)이고, 뒷 부분에 있는 것은 실행될 메서드다.
puts blackpink.play(song)
해당 코드에서는 blackpink 객체가 song을 play하도록 메서드를 호출한다. (적절한 객체를 참조하는 blackpink 라는 변수가 미리 정의되어 있다고 가정한다.)
클래스, 객체, 변수
루비에서 다루는 모든 것은 객체라고 했다. 루비의 객체는 클래스에서 직간접적으로 생성할 수 있다.
객체 지향 시스템을 설계할 때 항상 제일 먼저 해야 하는 일은 다루고자 하는 대상들의 특징을 파악하는 것이다. 일반적으로 다루고자 하는 대상들이 속하는 형식(type)은 완성된 프로그램에서 클래스로 만들어진다. 그리고 각 대상은 이 클래스의 인스턴스가 된다.
class BookInStock
end
만약 위와 같은 BookInStock이라는 클래스가 있다면 이 클래스를 통해 new라는 메서드로 인스턴스를 생성할 수 있다.
a_book = BookInStock.new
b_book = BookInStock.new
위 코드로 BookInStock 클래스를 기반으로 하는 두 개의 서로 다른 객체가 생성된다. 하지만 두 객체는 저장된 변수 이름이 다르다는 것 외에는 차이가 없고 어떠한 데이터도 저장되어 있지 않다.
이러한 문제는 생성자 메서드를 정의하여 해결할 수 있다. 이를 통해 객체가 생성되는 시점에 생성하고자 하는 객체에 특정한 상태를 저장할 수 있다. 이러한 상태는 객체의 인스턴스 변수로 저장된다. (인스턴스 변수는 @
로 저장된다.) 루비에서 각 객체는 자신만의 인스턴스 변수들을 가지고 있으며 이를 통해 객체의 고유 상태를 저장한다.
class BookInStock
def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
end
end
위와 같이 BookInStock 클래스를 개선할 수 있다.
루비에서 initialize 메서드는 생성자를 의미하는 특별한 메서드다. 새로운 객체를 만들기 위해 BookInStock.new를 호출하면 루비는 초기화되지 않은 객체를 메모리에 할당하고 new의 매개 변수를 이용해 그 객체의 initialize 메서드를 호출한다.
BookInStock 클래스의 initialize 메서드에는 두 개의 매개 변수가 필요하다. 이 매개 변수들은 메서드 안에서 지역 변수로 사용된다. 따라서 매개 변수는 지역 변수 명명 규칙을 따른다. 매개 변수는 지역 변수와 같은 스코프를 가지므로 initialize 메서드가 끝나면 함께 사라져 버린다. 따라서 필요한 정보를 인스턴스 변수에 저장해야 한다. initialize 메서드가 끝나는 시점에 객체는 사용가능한 상태여야 한다.
b1 = BookInStock("isbn1", 10);
객체와 속성
위에서 만든 BookInStock 객체는 ISBN, 가격을 포함한 객체 내부적인 상태를 가지고 있다. 이러한 객체의 내부 상태는 각 객체 내부에 저장된 정보로 다른 객체에서는 이 정보에 접근할 수 없다. 일반적으로 이는 좋은 아이디어다. 이는 객체의 일관성을 지키기 위한 책임이 하나의 객체에 전적으로 맡겨진다는 것을 의미한다.
하지만 객체의 정보가 완전히 감추어진다면 아무짝에도 쓸모없을 것이다. 따라서 일반적으로 객체 외부에서 객체 상태에 접근하거나 조작하는 메서드를 별도로 정의해서 외부에서도 객체 상태에 접근 가능하도록 만들어 준다. 이렇듯 객체의 내부가 외부에 노출되는 부분을 객체의 속성(attribute)라고 부른다.
BookInStock 객체에 대해 외부에서 찾을 수 있어야 하는 내용은 ISBN과 가격이다. 이를 구현하는 방법 중 하나는 접근자 메서드를 직접 구현하는 것이다.
class BookInStock
def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
end
def isbn
@isbn
end
def price
@price
end
end
book = BookInStock.new("isbn1", 12.34)
puts "ISBN = #{book.isbn}"
puts "Price = #{book.price}"
isbn 메서드는 인스턴스 변수 @isbn
에 저장된 정보를 반환한다. 접근자 메서드는 매우 자주 사용되므로, 루비는 이를 쉽게 정의해 주는 편의 메서드를 제공한다. attr_reader
메서드는 앞에서 작성한 것과 같은 접근자 메서드를 대신 생성해줄 것이다.
class BookInStock
attr_reader :isbn, :price
def initialize(isbn, price)
@isbn = isbn
@price = Float(price)
end
end
book = BookInStock.new("isbn1", 12.34)
puts "ISBN = #{book.isbn}"
puts "Price = #{book.price}"
속성, 인스턴스 변수, 메서드
속성은 단지 메서드일 뿐이다. 속성은 때때로 단순히 인스턴스 변수의 값을 반환한다. 속성은 때때로 계산 결과를 반환하기도 한다. 그리고 이름 끝에 등호를 달고 객체의 상태를 바꾸는 용도로 사용하기도 한다.
클래스를 설계할 때는 내부적으로 어떤 상태를 가지고, 이 상태를 외부(그 클래스의 사용자)에 어떤 모습으로 노출할 지 결정해야 한다. 여기서 내부 상태는 인스턴스 변수에 저장한다. 외부로 보이는 상태는 속성(attribute)라고 부르는 메서드를 통해야만 한다. 그 밖에 클래스가 할 수 있는 모든 행동은 일반 메서드를 통해야만 한다.
참고
프로그래밍 루비 [도서]