객체지향 언어의 특징

캡슐화(Encapsulation)

보통의 사용자가 스마트폰을 사용하는데는 스마트폰의 구조와 작동원리에 대한 지식 보다는 사용에 필요한 기능이 무엇인지 또 어떤 때에 어떻게 사용하는지에 대해서만 알면 됩니다. 터치를 했을 경우에 소프트웨어가 어떻게 동작하고 하드웨어가 어떻게 동작하는지에 대해서 몰라도 사용하는데는 지장이 없습니다.

만약 기기가 전부 오픈되어 있어서 사용자가 조작하지 말아야 할 부분까지 만질 수 있도록 되어 있다면 친절하다기 보다는 사용자의 실수나 기기의 고장의 원인이 될 것입니다.

프로그램도 마찬가지로 사용자가 내부의 주요 소스코드에 접근하는 것은 오히려 좋지 않습니다. 많은 변수들이 내부에서 생성되고 소멸되는데 그런 민감한 변수들을 조작하는 것은 프로그램 오류의 원인이 됩니다.

개발자는 이러한 것을 미연에 방지하도록 주요 로직과 변수들을 숨기고 오직 사용자가 최소한의 프로그램 동작에 필요한 것들만 접근하고 사용할 수 있도록 하는 것이 좋습니다.

이러한 것을 객체지향의 캡슐화 혹은 정보은닉이라고 표현합니다.

추상화(Abstraction)

추상화는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념이나 기능을 간추려 내는 것이라고 할 수 있습니다.

예를 들어 여러 브랜드의 다양한 자동차가 있습니다. 모든 자동차들은 그 종류에 따라서 크기, 무게, 넓이, 높이 등의 차이가 있습니다. 그러나 이러한 차이에도 불구하고 모든 자동차들을 잘 관찰하면 몇가지 공통된 특징을 찾아 낼 수 있습니다.

스티어링 휠, 바퀴, 주행장치, 제동장치, 문, 헤드라이트 등 공통점을 찾아내서 하나의 기본 개념을 만듭니다. 이렇게 만들어진 객체를 추상화된 객체라고 할 수 있습니다. 추상화된 객체는 “IS A”관계로 나타낼 수 있습니다. 즉, “기아자동차의 K7은 자동차이다”라는 관계를 만들어 낼 수 있습니다. 이와 마찬가지로 “현대자동차의 그랜저는 자동차이다”라는 관계가 맺어집니다. 이렇게 하면 자동차로부터 추상화된 객체는 “IS A” 관계가 됩니다.
그러나 “자동차는 K7이다.” 관계는 좀 어색합니다.

이 개념은 간단하지만 추상화의 매우 중요한 개념입니다.

같은 개념을 이전 프로그램 코드로 본다면 사람의 모든 특징을 조합하여 Human이라는 추상클래스를 만들었다고 생각해 봅니다. 그리고 Man 이라는 인터페이스 클래스를 만듭니다. 이제 Human + Man = Adam 이라는 클래스를 만들었습니다.

이 Adam이라는 객체를 통해서 “David”, “John” 객체를 만듭니다.
이렇게 하면 David ≠ John 이지만 Human = David, Human = John의 관계는 성립하게 됩니다.

public class HumanDemo {
	public static void main(String[] args) {
		Adam david = new Adam("David", 180, 80);		
		Human john = new Adam("John", 178, 75);
		Man mark = new Adam("Mark", 178, 75);
	}
}

이렇게 추상화한 객체와 이를 상속 받은 객체 사이에 “IS A”의 관계가 성립한다는 것이 추상화의 중요한 개념입니다.

상속(Inheritance)

상속은 추상화된 클래스 즉, 객체의 일반적인 특성이 정의된 클래스를 사용하여 새로운 객체를 만드는 것을 의미합니다. 단 추상화된 클래스는 반드시 추상클래스라는 의미는 아니니 혼동 없으시기 바랍니다.

이는 보통 부모-자식 클래스 혹은 상위-하위 클래스로도 이름할 수 있습니다. 앞으로는 자식 클래스로 이름하겠습니다. 부모 클래스는 super를 통해서 접근이 가능합니다.

자식클래스는 부모 클래스의 속성을 물려 받을 수 있습니다. 별도로 정의하지 않아도 되지만 필요하다면 정의할 수도 있는데 이것을 Overriding이라고 합니다.

이러한 상속의 개념을 이해하고 설계에 반영하면 객체의 재사용이나 기능 추가 혹은 유지보수 등에 큰 장점이 있습니다.

자바(JAVA)에서 상속은 extends를 통해서 가능하고 extends를 통해서는 단일 상속 밖에는 지원하지 않습니다. 그러나 interface class의 경우 implements를 통해서 다중 상속도 가능합니다.

다형성(Polymorphism)

객체지향의 특징에서 가장 이해하기 어려운 개념이 바로 다형성 개념일 것입니다. 다형성은 말 그대로 상속 받은 메서드가 자식 클래스에서 다양한 방식으로 개발 할 수 있도록 허용하는 것이다. 이 다형성이 상속과 연계되어서 자바의 높은 언어적인 효율성이 발휘된다고 할 수 있습니다.

이 다형성을 잘 이해하고 설계하면 코드도 간결해질 뿐더러 변화에도 유연하게 대처할 수 있습니다.

아래의 코드는 일반적인 형태의 코드입니다. 한국인, 중국인, 영국인 객체를 선언하고 각각 모국어를 말하는 클래스입니다. 이 세 객체는 “말한다”라는 하나의 기능을 수행하는 메소드를 가지고 있지만 각기 다른 이름으로 구현되어 있습니다.

package ch01;

public class SpeakDemo {
	public static void main(String[] args) {
		Korean k = new Korean();
		Chinese c = new Chinese();
		Briton b = new Briton();
		
		k.speakKorean();
		c.speakChinese();
		b.speakEnglish();
	}
}

class Korean {
	public void speakKorean() {
		System.out.println("한국어");
	}
}

class Chinese {
	public void speakChinese() {
		System.out.println("중국어");
	}
}

class Briton{
	public void speakEnglish() {
		System.out.println("영어");
	}
}

이제 위의 코드에서 Korean, Chinese, Brion 클래스에서 공통적인 요소를 추출하여 추상클래스를 만들고 이 세 언어의 공통적인 기능인 “speak()” 메소드를 정의하겠습니다. 이 추상클래스를 공통으로 상속 받는 객체들은 반드시 speak() 메소드를 구현해야 합니다.
그러나 이 speak() 메소드를 상속 받은 클래스는 각자의 상황에 맞춰서 해당 메소드를 다르게 구현합니다. 추상클래스에서는 정의만 하고 이를 상속하는 클래스에서 각기 다른 방식으로 내용을 구성하는 것입니다.

package ch01;

public class SpeakDemo2 {
	public static void main(String[] args) {
		Language ko = new Korean();
		Language ch = new Chinese();
		Language br = new Briton();
		
		ko.speak(); ch.speak(); br.speak();
	}
}

abstract class Language {
	public abstract void speak();
}

class Korean extends Language {
	@Override
	public void speak() {
		System.out.println("한국어");
	}
}

class Chinese extends Language {
	@Override
	public void speak() {
		System.out.println("중국어");
	}
}

class Briton extends Language { 
	@Override
	public void speak() {
		System.out.println("영어");
	}
}

위와 같은 방식으로 코딩하면 세 객체간의 관계가 명료해지고 또 다른 언어가 생성 될 때에 상속을 받고 speak() 부분만 구현하면 되기 때문에 코드가 간결해지고 재활용이 가능해집니다. 그리고 부모 객체의 메소드를 자식에서 구현할 때에 명시적으로 @Override를 어노테이션을 표기해주면 더욱 명시적입니다.

비슷한 개념으로 @Overload가 있습니다.
해당 개념은 하나의 메소드 이름을 각기 다른 매개 변수를 사용해서 호출하는 것으로 가장 많이 쓰는 예로는 System.out.print() 함수가 있습니다. 해당 함수는 String, boolean, long, int 등 매개변수를 다르게 해도 모두 동일한 print()를 사용합니다.

그러므로 오버로딩의 조건은 메서드의 이름이 같고, 매개 변수의 타입이 틀려야 합니다. 만약 Overload가 되지 않았다면 아마도 자바는 비슷한 이름의 지금 보다 훨씬 더 많은 메소드를 보유하면서 개발자를 힘들게 만들었을 수도 있을 것입니다.