Ruby

본 토픽은 현재 준비중입니다. 공동공부에 참여하시면 완성 되었을 때 알려드립니다.

객체지향 프로그래밍 1

객체지향 프로그래밍

객체지향 프로그래밍(Object-Oriented Programming)은 좀 더 나은 프로그램을 만들기 위한 프로그래밍 패러다임으로 로직을 상태(state)와 행위(behave)로 이루어진 객체로 만드는 것이다. 이 객체들을 마치 레고블럭처럼 조립해서 하나의 프로그램을 만드는 것이 객체지향 프로그래밍이라고 할 수 있다. 다시말해서 객체지향 프로그래밍은 객체를 만드는 것이다. 따라서 객체지향 프로그래밍의 시작은 객체란 무엇인가를 이해하는 것이라고 할 수 있다.

객체지향 프로그래밍을 학습하는데 장애 중의 하나는 번역이다. Object를 번역한 객체는 현실에서는 거의 쓰지 않는 말이고, 머랄까 철학적인 느낌을 자아낸다. 그래서 객체지향 프로그래밍을 처음 접하는 입문자들은 객체지향 프로그래밍을 철학적인 탐구의 대상으로 파악하는 경향을 보이는데, 필자의 생각에 이것은 공부를 어렵게 할 뿐 도움이 되지 않는다. 쉽게 생각하자. 객체는 변수와 메소드를 그룹핑한 것이다.

객체가 없다면

하나의 프로그램은 변수와 메소드로 이루어져있다. 변수에 값을 담고, 연산을 메소드로 묶는다. 그리고 이것들을 하나씩 실행하면서 프로그램이 동작하게 된다. 아래의 코드는 우항과 좌항을 더한 결과를 출력하는 매우 간단한 프로그램이다. 두개의 변수와 하나의 메소드가 있고, 메소드 sum을 호출하고 있다. 결과는 3이다.

@left = 1
@right = 2
def sum
    return @left + @right
end
puts sum() #3

이 정도 규모의 코드에서는 나쁜일이 일어날 가능성이 거의 없다. 하지만 프로그램은 점점 커지고 그에 따라 더 많은 사람이 코드의 작성에 참여하게 된다. 프로그램의 규모가 커지기 시작하면서 위의 코드가 약 1만줄에 이르는 방대한 코드가 되었다고 가정해보자. 코드의 복잡도는 기하급수적으로 늘어날 것이고 다양한 어려움에 봉착하게 될 것이다.

예를들어, 새로 합류한 개발자가 사용자의 적립금을 출력하는 로직을 작성하기 위해서 로직을 아래와 같이 고쳤다고 해보자.

이 예제는 프로그램으로써 좋은 예제는 아니다. 서로 다른 개발자가 right의 의미를 다르게 사용했을 때 발생할 수 있는 문제를 위한 인위적인 예제다
@left = 1
@right = 2
def sum
    return @left + @right
end

@right = 10000
def my
    return @right
end

puts sum() #10001
puts my()  #10000

메소드 my의 실행결과(10000)는 정상적으로 출력 되지만 메소드 sum은 엉뚱한 값(10001)을 출력하고 있다. 이것은 나중에 합류한 개발자에 의해서 right의 의미가 계산식의 오른쪽항에서 사용자의 권리에 해당하는 금액으로  달라졌기 때문이다. 프로그램의 규모가 커지고 개발자가 많아질수록 이런 일은 빈번해질 것이다. 즉 서로 관련이 없는 로직인 sum과 my가 하나의 프로그램 안에서 동작하고 있기 때문에 서로에게 영향을 끼치고 있는 것이다.

객체화

객체지향 프로그래밍은 입문자에게는 다소 어려운 토픽이고, 객체지향이 무엇인가를 모른다고 프로그램을 만들지 못하는 것은 아니다. 따라서 객체지향 프로그램을 이해하는데 어려움이 있다면 객체지향적인 기법 없이 코드를 작성하면 된다. 그러다보면 차차로 다양한 객체들을 만나게 될 것이고 객체지향에 대해서 이해하기 좋은 상태가 될 것이다. 그 때 다시 도전해도 된다. 하지만 이 언어를 깊게 이해하고, 더 고도화된 프로그램을 만들기 위해서는 꼭 짚고 넘어가야 할 주제라는 점도 잊지 말자.

그럼 어떻게 해야할까? 이해를 돕기 위해서 비유를 들어보자. 컴퓨터에서 데이터를 보관하는 기본 단위는 파일이다. 파일이 있기 때문에 저장장치(하드디스크, SSD) 전체를 하나로 사용하는 것이 아니라 내용에 따라서 파일 단위로 쪼개서 사용할 수 있다. 마찬가지로 파일도 많아지면 관리의 어려움이 생긴다. 이것을 위해서 고안된 방법이 디렉토리(폴더)다. 즉 디렉토리를 이용해서 연관된 파일들을 그룹핑 함으로써 관리의 편의를 도모하게 되는 것이다.

객체란 디렉토리와 비슷한 목적으로 고안된 것이라고 생각하자. 연관되어 있는 변수와 메소드를 그룹핑해서 관리의 편의를 도모하는 것이 객체를 사용하는 이유다. (물론 훨씬 더 많은 이유가 있다)

다음 예제는 이전 예제를 객체화 시킨 것이다. 이 예제를 통해서 객체가 무엇인가를 알아보자.

class Calculator
    def initialize(left, right)
		@left = left
		@right = right
	end

	def sum
	    return @left + @right
	end
end

class User
	def initialize(right)
		@right = right
	end

	def my
		return @right
	end
end

s = Calculator.new(1,2)
puts s.sum

u = User.new(10000)
puts u.my

클래스와 인스턴스

클래스란 객체의 설계도이고 인스턴스는 그 설계도에 따라서 만들어진 구체적인 제품이라고 비유 할 수 있다. 이 말의 의미를 실제 코드를 통해서 알아보자.

아래의 코드는 Calculator, User라는 이름의 클래스를 만들기 위한 것이다. 이 클래스는 객체 Calculator과 User에 대한 일종의 설계도라고 할 수 있다.

class Calculator
    #코드생략
end
class User
    # 코드생략
end

익숙한 메소드로 비유를 해보자. 메소드도 설계도가 있다. 바로 def라는 키워드다. 메소드의 설계도는 아래와 같은 형식을 가지고 있다.

def 메소드명
    # 로직
end

위의 내용은 메소드를 정의 하는 것일 뿐 실제로 메소드의 로직을 실행하기 위해서는 아래와 같이 메소드를 호출해야 한다.

메소드명()

객체도 마찬가지다. 객체를 실제로 사용하기 위해서는 아래와 같이 객체의 생성을 컴퓨터에게 지시해야 한다.

s = Calculator.new(1,2)

위의 코드는 변수 s에 객체 Calculator을 담아서 저장한다는 의미다. 위의 구문이 실행되면 변수 s를 이용해서 Calculator 객체를 사용할 수 있게 된다. 이렇게 생성된 객체를 인스턴스(instance)라고 부른다. 즉 객체의 설계도를 클래스라고 부르고, 이 설계도를 바탕으로 실제로 사용할 수 있게 만들어진 객체를 인스턴스라고 한다. 이것들을 포괄해서 객체라고 부르기도 한다. 객체라는 말의 의미는 맥락적으로 파악해야 한다.

앞으로 필자는 설계도를 클래스, 만들어진 객체를 인스턴스라고 구분해서 부르겠다.

생성자

.new()는 객체를 생성할 때 사용하는 규칙이다. new가 호출되면 클래스 내의 def initialize 메소드가 실행된다. 이 메소드를 생성자(constructor)라고 부른다. 메소드 initialize는 객체의 초기화를 위한 특수한 메소드로 이 이름으로 클래스 내에 메소드를 정의하면 클래스가 인스턴스화 될 때 실행된다. 이것은 최초로 한번 실행되기 때문에 뒤에서 배울 인스턴스 변수의 초기 값을 셋팅하거나, 데이터베이스 접속을 시도하는 것과 같이 기본적으로 필요한 작업을 할 때 사용된다.

생성자는 인자를 가질 수 있다. 위의 예제에서는 .new(1, 2)를 통해서 1과 2를 인자로 전달했다. 이것은 아래 Calculator 클래스 내의 아래 코드로 인해서 인자변수 left의 값으로 1, right의 값으로 2가 전달된다. 이 변수들의 값은 각각 변수 @left, @right에 담겨진다. 그럼 '@'가 무엇인지 알아보자.

def initialize(left, right)
	@left = left
	@right = right
end

인스턴스 변수와 메소드

클래스 내에 '@' 기호가 붙은 변수를 인스턴스 변수(instance variable)라고 부른다. 인스턴스 내에서만 접근이 가능한 변수라는 것이다. 위의 예제에서 left, right는 메소드 안에서만 유효한 지역변수고 @left, @right는 인스턴스 내에서 유효한 인스턴스 변수이다. 다시말해서, 인스턴스 변수인 @left, @right는 인스턴스 안의 메소드에서는 접근이 가능 하지만, User 클래스의 인스턴스에서는 접근이 불가능하다. 물론 Calculator 클래스로 만든 인스턴스라도 다른 인스턴에서는 접근이 불가능하다. 헷갈릴 것이다. 아래 그림을 보자. 아래 그림은 같은 인스턴스 내의 메소드에서는 인스턴스 변수를 사용 가능 하지만 다른 인스턴스에서는 사용이 불가능하다는 것을 보여준다. 덕분에 인스턴스 안의 상태(인스턴스 변수)가 외부의 영향에 의해서 변경될 가능성이 현저하게 줄어든다.

캡슐화

상태와 행위

앞에서 객체를 '연관된 상태와 행위의 그룹'이라고 정의했다. 그리고 상태를 변수, 행위를 메소드라고 생각하자고 했다. 이 말의 의미를 곱씹어보면서 아래를 보자.

c1 = Calculator.new(1, 2)
c2 = Calculator.new(3, 4)

두개의 인스턴스를 만들었다. 각각의 인스턴스는 생성자로 인자1과 인자2가 전달 됐다. 인스턴스 c1의 상태는 아래와 같다.

  • @left : 1
  • @right : 2

아래는 c2의 상태다.

  • @left : 3
  • @left : 4

인스턴스 c1과 c2는 서로 다른 상태를 가지고 있는 것이다. 즉 인스턴스 변수의 값이 그 객체의 '상태(state)'가 되고, 메소드는 그 상태를 참고해서 '행위(behave)'를 하게 되는 것이다. 그 결과 c1.sum의 결과는 3이 되고, c2.sum의 결과는 7이 된다. 객체에 대해서 다시 정리해보자.

객체화란

  1. 설계도인 클래스를 바탕으로
  2. 서로 다른 상태(변수)에 따라 
  3. 동일한 행동(메소드)을 하는
  4. 구체적인 제품인 인스턴스를
  5. 사용한 것이다.
상태나 행위라는 표현은 기억하지 않아도 된다. 중요한 것은 객체가 무엇인가를 이해하는 것이다.

마치며

이번 토픽에서는 객체가 무엇인가를 설명하기 위해서 많은 노력을 들였다. 하지만 객체지향 프로그래밍은 책 한권이 별도로 존재할 수 있을 정도로 그 내용이 깊고 방대하다. 이것을 다 알면 좋겠지만 그건 차차하면 된다. 이번 토픽에서 소개된 객체지향의 개념만 알고 있어도 Ruby에서 프로그래밍을 하는데 큰 도움이 될 것이다. 객체지향에 대한 더 많은 내용은 후속 강의를 통해서 다루기로 하겠다.

  • 봤어요 (0명)

댓글

댓글 본문
  1. egoing
    단기 계획은 없지만 언젠가는 해보겠습니다!
    대화보기
    • Lomal
      루비온레일즈 혹은 시나트라등 에 관해 강좌좀 해주실수있으신가요..??
    • qwerty
      좋은 강의 감사드립니다!
    • Tw Shim
      typo가있네요.
      c1 = Calculator(1, 2)c2 = Calculator(3, 4)
      ->
      c1 = Calculator.new(1, 2)c2 = Calculator.new(3, 4)
    버전 관리
    egoing
    현재 버전
    선택 버전
    graphittie 자세히 보기