If you're seeing this message, it means we're having trouble loading external resources on our website.

웹 필터가 올바르게 작동하지 않으면 도메인 *. kastatic.org*.kasandbox.org이 차단되어 있는지 확인하세요.

주요 내용

검토: 객체지향적 설계

이제 객체지향적 설계에 관한 본 수업에서 살펴본 내용을 복습하겠습니다.
프로그램을 만들 때 서로 비슷한 프로퍼티를 공유하는 다양한 객체를 만들어야 하는 경우가 많습니다. 예를 들면, cat 프로그램을 짜면서 서로 털 색깔이나 크기가 다른 값을 가지는 배열을 만들 때, 혹은 서로 다른 이름과 위치를 가지는 여러 버튼을 만들어야 하는 경우를 떠올려 보세요. 말로 "고양이들은 대체로 이러하고" "특정 고양이는 이렇고 다른 고양이는 저렇고, 서로 비슷한 점이 있고 다른점이 있어요" 라고 표현하듯이 프로그램을 짜야 합리적이겠죠. 이럴 때는 객체지향적 설계(object-oriented design) 이용하여 객체 유형을 정의하고 해당 객체에 대한 새로운 인스턴스를 생성해야 합니다.
JavaScript에서 객체 유형을 정의하려면 먼저 "생성자 함수"를 정의해야 합니다. 생성자 함수는 해당 객체 유형의 새로운 인스턴스를 생성하려고 할 때마다 사용하는 함수입니다. 다음은 Book 객체 유형에 대한 생성자 함수입니다.
var Book = function(title, author, numPages) {
  this.title = title;
  this.author = author;
  this.numPages = numPages;
  this.currentPage = 0;
};
함수는 각각의 책에 관한 인수로 제목(title), 저자(author), 페이지 수(numPages) 를 입력 받을 수 있습니다. 이 인수를 기반으로 this 키워드를 이용하여 객체의 초기 프로퍼티를 설정합니다. 객체에서 this를 이용할 때 자기 자신을 참조하면서 객체의 현재 인스턴스를 참조합니다. 나중에 확실하게 기억하기 위해서 this에 프로퍼티를 저장할 필요가 있습니다.
book 객체의 인스턴스를 생성하려면 인스턴스를 저장하기 위한 새로운 변수를 선언한 후 new 키워드 다음에 생성자 함수명을 인용하고 생성자가 예상하는 인수를 입력합니다.
var book = new Book("Robot Dreams", "Isaac Asimov", 320);
그런 다음 dot 표기법을 이용하여 객체에 저장된 모든 프로퍼티에 접근할 수 있습니다.
println("I loved reading " + book.title); // I loved reading Robot Dreams (Robot Dreams를 재미있게 읽었습니다.)
println(book.author + " is my fav author"); // "Isaac Asimov" is my fav author (Isaac Asimov는 제가 좋아하는 작가입니다.)
이와 대조적으로 생성자 함수를 잘못 설정하면 어떤 일이 발생하는지 살펴보겠습니다.
var Book = function(title, author, numPages) {
};
var book = new Book("Little Brother", "Cory Doctorow", 380);
println("I loved reading " + book.title); // I loved reading undefined (undefined를 재미있게 읽었습니다.)
println(book.author + " is my fav author"); // undefined is my favorite author (undefined는 제가 좋아하는 작가입니다.)
인수들을 생성자 함수로 전달할 때 this에 명시적으로 저장하지 않으면, 나중에 해당 인수들에 접근할 수 없습니다! 객체는 인수들은 잊어버릴 것입니다.
객체 유형을 정의할 때 프로퍼티와 행위를 동시에 연관시켜야 하는 경우가 있습니다. 모든 고양이 객체가 meow(), eat() 따위의 행위를 해야하는 것 처럼요. 그래서 객체 유형 정의에 함수를 추가하여야 하고 이는 객체 프로토타입(object prototype)에 함수를 정의해서 실현할 수 있습니다.
Book.prototype.readItAll = function() {
  this.currentPage = this.umPages;
  println("You read " + this.numPages + " pages!");
};
보통은 함수를 전역으로 정의하지만, 이번에는 Book 프로토타입에 연결했습니다. 이를 제외하면 일반적으로 함수를 정의하는 방법과 비슷합니다. 이 과정을 통해 JavaScript는 해당 함수가 임의의 Book 객체에서 호출될 수 있으며 이 함수가 호출한 책의 this에 접근할 수 있다는 사실을 인지하게 되는 것입니다.
이제 다음과 같이 함수(객체에 부가되기 때문에 메소드(method)라 부릅니다.)를 호출할 수 있습니다.
var book = new Book("Animal Farm", "George Orwell", 112);
book.readItAll(); // You read 112 pages! (112 페이지를 읽었습니다!)
객체지향적 설계의 중요한 점은 이렇게 설계하면 서로 관련 있는 여러 객체들(객체 인스턴스(object instance))을 만들 수 있는 것을 기억하시기 바랍니다. 다음 코드를 살펴봅시다.
var pirate = new Book("Pirate Cinema", "Cory Doctorow", 384);
var giver = new Book("The Giver", "Lois Lowry", 179);
var tuck = new Book("Tuck Everlasting", "Natalie Babbit", 144);

pirate.readItAll(); // You read 384 pages! (384 페이지를 읽었습니다!)
giver.readItAll(); // You read 179 pages! (179 페이지를 읽었습니다!)
tuck.readItAll(); // You read 144 pages! (144 페이지를 읽었습니다!)n
이 코드는 통해 비슷한 책 세 개를 생성합니다. 책은 모두 동일한 형태의 프로퍼티와 행위을 가지지만 서로 다릅니다. 우리가 바로 원했던 거에요!
실생활에서 생각하면 고양이와 개는 서로 다른 유형의 객체이므로 CatDog를 프로그래밍할 때 직관적으로 서로 다른 객체 유형을 생성해야 된다는 느낌이 들 수 있습니다. 고양이는 meow()라고 하며 개는 bark()라고 소리를 낼 겁니다. 그러나 고양이와 개는 비슷한 점도 있습니다. 개와 고양이는 모두 eat() 행위를 할 수 있고 둘 다 age, birth, death 와 같은 속성을 가집니다. 고양이와 개는 포유동물이고, 이 사실은 서로 다르지만, 공통점도 많다는 것을 의미합니다.
이 경우 객체 상속성(object inheritance)를 사용합니다. 객체 유형은 부모 객체 유형으로부터 프로퍼티와 행위를 상속받기도 하지만 자기만의 프로퍼티를 가질 수도 있습니다. 모든 CatDogMammal이므로 이로부터 프로퍼티와 행위를 상속받아 매번 eat()를 만들 필요가 없도록 만들고 싶습니다. 그러면 JavaScript에서 어떻게 하면 될까요?
Book 예제로 다시 돌아가봅시다. Book은 "부모" 객체 유형이고 이를 상속받는 두 개의 객체 유형인 PaperbackEBook을 만들어 봅시다.
Paperback은 book과 비슷하지만 프로그램에서 최소한 한 가지 확실히 다른 점이 있습니다. Paperback은 표지 이미지가 있습니다. 그러므로 생성자에 추가 정보를 포함하기 위해 네 개의 인수를 입력해야 합니다.
var PaperBack = function(title, author, numPages, cover) {
  // ...
}
처음 세 개의 인수를 불러오기 위해서 Book 생성자에서 이미 했던 모든 일을 다시 할 필요는 없습니다. 해당 코드가 동일하다는 사실을 이용하면 됩니다. 그러므로 실제로 PaperBack 생성자에서 Book 생성자를 호출한 후 인수를 전달합니다.
var PaperBack = function(title, author, numPages, cover) {
  Book.call(this, title, author, numPages);
  // ...
};
그렇다고 해도 객체에 cover 프로퍼티를 저장할 필요가 있으므로 코드에 이를 위해 한 줄을 더 추가해야 합니다.
var PaperBack = function(title, author, numPages, cover) {
  Book.call(this, title, author, numPages);
  this.cover = cover;
};
이제 PaperBack에 대한 생성자가 생겼습니다. 이 생성자를 이용하면 Book과 동일한 프로퍼티를 공유할 수 있습니다. 그러나 PaperBack이 해당 메소드를 또한 상속받도록 하고 싶습니다. 하는 방법은 다음과 같습니다. 프로그램에서 Book 프로토타입을 PaperBack 프로토타입의 기반으로 하면 됩니다.
PaperBack.prototype = Object.create(Book.prototype);
이제 Paperback에 특화된 불태우는 행위를 첨부하고 싶습니다. 이렇게 하기 위해 바로 위의 줄 다음의 프로토타입에 함수를 정의해 봅시다.
PaperBack.prototype.burn = function() {
  println("Omg, you burnt all " + this.numPages + " pages");
  this.numPages = 0;
};
이제 새로운 Paperback을 생성하여 읽고 불태울 수 있습니다!
var calvin = new PaperBack("The Essential Calvin & Hobbes", "Bill Watterson", 256, /n/n "http://ecx.images-amazon.com/images/I/61M41hxr0zL.jpg");

calvin.readItAll(); // You read 256 pages! (256 페이지를 읽었습니다!)
calvin.burn(); // Omg, you burnt all 256 pages! (256 페이지 전부 다 불태웠습니다!)
(저 책은 정말 좋은 책이기 때문에 정말로 태우지는 않을 것입니다. 그러나 빙하 사막에 갇혀 열원이 절실하게 필요하고 거의 죽기 직전이라면 태울 수도 있겠네요.)
JavaScript 프로그램에서 보다 복잡한 데이터를 생성하고 프로그램을 좀 더 나은 방법으로 설계하기 위해 객체지향적 설계를 이용하는 방법을 알아보았습니다.

대화에 참여하고 싶으신가요?

영어를 잘 하시나요? 그렇다면, 이곳을 클릭하여 미국 칸아카데미에서 어떠한 토론이 진행되고 있는지 둘러 보세요.