객체 지향 프로그래밍 입문

잡동사니

이 문서는 엽우가 객체 지향 프로그래밍(OOP, Object-oriented programming)을 공부하려는 분들이 좀더 쉽게 OOP를 이해할 수 있도록 돕기 위해 작성하였습니다.

목차

요소

Entities

객체

Object의 자세한 내용은 객체 문서를 보십시오.

객체(object)란 #속성(#필드)과 #연산(#메서드)(이 둘을 #멤버라고 합니다)을 포함하는 구조화된 자료(자료 구조를 참고하세요)입니다. 따라서 객체는 프로그램 코드에서 조작하는 대상이 됩니다. 객체는 종종 #인스턴스와 유사한 의미로 쓰이나 객체는 보다 일반적이고 추상적인 의미를 포함하고 있습니다.

절차적 프로그래밍의 자료구조와 객체 지향 프로그래밍의 자료구조에 대한 내용을 준비중입니다.

인스턴스

이 주제의 자세한 내용은 인스턴스 문서를 보십시오.

인스턴스(instance)는 #객체와 매우 유사한 의미를 지닙니다. 엄밀히 정의하자면 인스턴스는 실행시(runtime)에 생성되어 실체가 있는 객체를 의미합니다.

클래스

이 주제의 자세한 내용은 클래스 문서를 보십시오.

클래스(class)는 #객체를 생성할 때 생성할 객체가 어떤 #멤버를 가지고 있을 지 정해놓은 요소입니다. 클래스는 생성자(constructor)나 초기자(초기화 메서드, initializer)등을 통해 생성된 객체의 각 #속성이 가지는 값을 정합니다. 클래스 기반 프로그래밍 언어로는 자바, C++, C#, 루비 등이 있습니다.

프로토타입

이 주제의 자세한 내용은 프로토타입 문서를 보십시오.

프로토타입(기본형, prototype)은 #클래스와 더불어 #객체를 생성할 객체의 멤버를 정하는 또 다른 요소입니다. 클래스가 새로운 #인스턴스를 생성한 다음 초기화하는 반면, 프로토타이핑(prototyping)은 프로토타입의 사본을 만들고, 이 사본에 필요한 멤버를 추가하는 방식으로 초기화합니다. 프로토타입 기반 프로그래밍 언어로는 자바 스크립트가 있습니다.

자바스크립트의 프로토타입을 이용한 확장에 대한 내용을 준비중입니다.

이름공간

이 주제의 자세한 내용은 이름공간 문서를 보십시오.

이름공간(namespace)이란 각 요소의 이름이 유효한 범위로서 절차적 프로그래밍에서 전역지역으로 만 구분되어 함수나 전역에 존재하는 변수(전역 변수)의 이름 충돌을 피하기 위해 접두어를 길게 쓰던 불편함을 해소하기 위해 도입된 개념입니다. 각 이름공간은 다른 이름공간에 간섭받지 않으며, 이름공간을 통해 짧고 한눈에 들어오는 코드를 작성할 수 있습니다. 이름공간은 OOP만의 특징은 아니지만 많은 OOP 언어에서 여러 형태의 이름공간을 제공하고 있습니다.

모듈

이 주제의 자세한 내용은 모듈 문서를 보십시오.

모듈(Module)은 루비에서 제공하는 이름공간입니다.

패키지

이 주제의 자세한 내용은 패키지 문서를 보십시오.

패키지(package)은 자바에서 클래스를 위해 제공하는 이름공간입니다. 강제적인 것은 아니지만 보통 com이나 net, org와 같은 최상이 도메인에서 시작하여 각 호스트네임이 이어지고 그 다음에 소프트웨어에서 지정한 이름이 마침표(.)로 이어집니다.

예: com.hisfy.mysoftware

주의할 점은 패키지는 서로 아무런 상/하 관계가 없습니다. 즉 foo 패키지와 foo.bar 패키지는 전혀 다른 패키지로 인식됩니다.

클래스의 이름공간

이 주제의 자세한 내용은 클래스의 이름공간 문서를 보십시오.

클래스 이름공간(class namespace)는 클래스 자체가 가지는 이름공간과 클래스의 인스턴스가 가지는 이름공간 두 종류를 지칭합니다.

클래스 이름공간
OOP는 모든 자료를 객체로 다루기 때문에 클래스 또한 인스턴스를 생성하고, 특정 클래스의 클래스 인스턴스는 단 하나만 생성됩니다. 즉, 어디서건 문자열(string) 클래스의 클래스 인스턴스는 동일한 인스턴스가 됩니다. 따라서 클래스가 가지는(정확히는 클래스 인스턴스가 가지는) 멤버는 모든 곳에서 동일합니다.
인스턴스 이름공간
인스턴스 이름공간은 인스턴스 자체가 가지는 이름공간으로 각 인터페이스는 자신만의 멤버를 가지게 됩니다.
하지만 대부분의 언어 구현에서는 성능의 이유로 필드는 각 인스턴스마다 다르지만 메서드는 공유합니다.

멤버

이 주제의 자세한 내용은 멤버 문서를 보십시오.

멤버(member)는 객체를 구성하는 모든 요소를 지칭합니다. 보통 #속성#연산, 혹은 #필드#메서드를 의미합니다.

속성

이 주제의 자세한 내용은 속성 문서를 보십시오.

속성(attribute)은 객체의 요소 중 다른 객체를 값으로 가지고 있는 요소입니다.

필드
이 주제의 자세한 내용은 필드 문서를 보십시오.

필드(field)는 자바와 같은 언어의 #속성입니다.

특성

특성(property)은 C#과 같은 언어에서 추가하는 #속성으로 코드에서 #필드를 쉽게 접근하도록 도와줍니다. 예를 들면 자바의 경우 다음과 같이 작성해야 합니다.

public class Post {
    private String subject;
    private String content;
    // other fields
    public String getSubject() {
        return subject;
    }
    public void setString(String subject) {
        this.subject = subject;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    // other accessor
}
Post post = new Post();
post.setSubject("Hello, World!");
System.out.println(post.getSubject());

반면 C#은 다음과 같이 작성할 수 있습니다.

class Post
{
    private string subject;
    private string content;
    public string Subject
    {
        get { return subject; }
        set { subject = value; }
    }
    public string Content
    {
        get { return content; }
        set { content = value; }
    }
}
Post post = new Post();
post.Subject = "Hello, World!";
System.Console.WriteLine(post.Subject);

연산

이 주제의 자세한 내용은 연산 문서를 보십시오.

연산(operation)이란 #객체가 가지는 수행 코드로 #메서드 외에도 연산자를 포함한 개념이다. 루비와 같은 언어에서는 다음과 같이 연산자도 연산에 포함됨을 알 수 있다.

irb(main):000:0> Array.method(:"[]")
=> #<Method: Array.[]>
메서드
이 주제의 자세한 내용은 메서드 문서를 보십시오.

메서드(method)는 #클래스#인스턴스를 기본 #이름공간으로 가지는 프로시져다. 클래스나 인스턴스의 이름공간을 동등한 수준으로 접근할 수 있기 때문에 #접근 제한자의 제한 없이 접근이 가능하며 이는 #정보 은닉을 할 수 있게끔 한다.

멤버 접근

접근 제한자
이 주제의 자세한 내용은 접근 제한자 문서를 보십시오.

접근 제한자(access modifier)는 멤버에 대한 참조가 어디까지 가능한지 제한합니다. #정보 은닉을 위해 #캡슐화를 할 때 멤버 중 해당 #클래스(및 #객체)에서만 접근(private)하거나, #상속 관계의 클래스에서만 접근(protected)하거나 모든 곳에서 접근(public)하게 지정할 수 있습니다.

메시지 전달
이 주제의 자세한 내용은 메시지 전달 문서를 보십시오.

루비와 같은 언어에서는 #멤버에 접근을 할 때 #필드#메서드를 구분하고 별도의 호출 절차를 거치는 게 아니라 메시지 전달(message passing) 개념으로 접근합니다. 즉 myString.length와 같은 코드를 해석할 때 myString 객체의 length 멤버(필드)에 직접 접근하는 게 아니라 myString 객체에 length 메시지를 보냅니다. 메서드 호출도 코드가 myString.slice(2, 3)일 때 length 메시지와 함께 인수인 23을 보냅니다. 자바와 같은 정적 형식 언어도 동일하게 해석할 수 있으며 동적 형식 언어와 비교하면 컴파일시에 정의되지 않은 메시지가 발견될 경우 오류를 일으키냐 마냐의 차이입니다.

정적으로 검출되지 않은, 즉 변환(translation)시 정의되지 않은 메시지 전달을 허용하면 다음과 같은 특징을 가집다. 첫 째, 실행시(runtime)에 동적으로 객체를 변경할 수 있다. 여기서 변경은 상태를 말하는 게 아니라 멤버 및 메시지에 대한 반응을 말한다. 둘 째, 루비와 같이 인수가 메서드의 괄호를 생략할 수 있는 경우 필드와 메서드의 구분이 모호해질 수 있는 반면 코드는 간결하게 유지할 수도 있습니다.

원칙과 특징

Principles And Features

결합도와 응집도

결합도

결합도(coupling)는 한 모듈이 다른 모듈에 대해 의존하는 정도로서, 한 모듈 안에서 다른 모듈에 대한 참조가 많을 수록 결합도가 높아집니다. 여기서 참조는 #필드, #메서드 반환 값과 인자(parameter), 지역 변수 등의 자료형이나 다른 모듈의 메서드 호출, 필드 참조 등을 포함합니다.

응집도

응집도(cohesion)는 모듈의 각 내부 요소들 사이의 긴밀한 정도입니다. 예를 들어 객체의 필드를 객체 내부에서 전혀 사용하지 않는다면 응집도는 떨어집니다. 또 한 메서드는 파일 입출력을 담당하지만 다른 메서드는 자료 처리를 담당한다면 응집도는 떨어집니다. (관련 항목: #단일 책임 원칙)

역으로 서로 관련된 기능이 여러 곳에 분리되어 있어도 응집도는 떨어집니다. 설정 파일을 읽는 메서드와 설정 파일을 변경하는 메서드가 서로 다른 클래스에 정의되어 있다면 설정 파일을 처리하기 위해서 두 클래스를 수정해야 합니다. 이는 응집도가 떨어지는 경우이며, 중복을 만들어 오류가 생기기 쉽게 됩니다.

낮은 결합도와 높은 응집도

이와 같은 특성에서 나온 원칙이 낮은 결합도와 높은 응집도(low coupling and high cohesion)입니다.

모듈 사이의 결합도가 높을 수록 참조된 모듈에서 변경이 발생했을 때, 그 변경에 영향을 받는 다른 부분이 많이지게 됩니다. 즉, 변경의 전파가 많이 이뤄지게 되고 이는 코드를 변경하기 어렵게 만듭니다. 따라서 결합도는 낮을 수록 좋습니다.

반면 응집도가 낮은 모듈은 모듈의 기능을 정확히 파악하기 어렵습니다. 응집도가 낮은 모듈은 특정 기능을 위해서 상관 없는 기능까지 포함한 채 사용해야 하며, 때로는 기능이 잘못된 모듈에 위치하여 개발자가 해당 기능을 찾기 어렵게 만듭니다. 따라서 응집도는 높을 수록 좋습니다.

단일 책임 원칙

#개방 폐쇄 원칙과 마찬가지로 #응집도가 요소(entity)가 가지는 성질(property) 그 자체라면 단일 책임 원칙은 응집도를 어떻게 높일지에 대한 지침입니다. 단일 책임 [객체지향 SW 설계의 원칙] ③ 인터페이스 분리의 원칙원칙(single responsibility principle)은 한 모듈이 하나의 책임(기능에 대한)을 가져야 한다는 원칙입니다.

개방 폐쇄 원칙

#결합도가 요소(entity)가 가지는 성질(property) 그 자체라면 개방 폐쇄 원칙은 결합도를 어떻게 낮출지에 대한 지침입니다. 개방 폐쇄 원칙(open/closed principle)은 확장에는 개방되고 변경에는 폐쇄되어야 한다는 원칙입니다.

변경 폐쇄

먼저 모듈의 요소를 변할 수 있는 것과 그렇지 않는 것으로 나눕니다. 일반적으로 상수나 메서드가 수행할 기능 등은 잘 변하지 않습니다. 그러나 필드(내부 변수로서)나 메서드의 실제 구현은 변하기 쉽습니다. 이렇게 변경되지 않을 것들을 모아서 #인터페이스로 정의하고 외부에서는 모듈에 직접 접근하지 않고 인터페이스를 통해 접근하도록 합니다. 이렇게 함으로써 변경을 최대한 폐쇄합니다.

정보 은닉

Information Hiding

캡슐화

Encapsulation

확장 개방

새로운 요소나 기능이 필요할 경우 기존의 모듈 자체가 아닌 인터페이스를 #상속받도록 하여 확장에는 개방합니다.

인터페이스

Interface

리스코프 치환 원칙

Liskov Substitution Principle

추상화

Abstraction

상속

Inheritance

다형성

Polymorphism

참고자료

개인 도구