객체지향언어에서 abstract class또는 interface를 사용하는 이유가 궁금합니다.

lmk378의 이미지

객체지향언어(java?)에서 abstract class또는 interface를 사용하는 이유가 궁금합니다.
실질적으로 객체지향언어로 프로젝트를 진행하면서 어떤 경우에 class로 구현하고 어떤경우에 interface로
작성해야 할지 감이 잘 않잡힙니다.

aruee의 이미지

abstract 나 interface 가 비슷하긴 한데요...
보통 내부에 감춰진 속성을 맴버변수에...
외부에 보이는 행동양식을 메소드로 표현하겠죠.

약간 억지스럽긴 하지만....
사람,개,고등어,로봇 이 있다고 가정해 봅니다.
로봇은 생물을 흉내낸 것으로 숨도 쉬고, 밥도 먹는다고 해 보죠.

사람, 개, 고등어 는 동물 이므로 "동물" 이라는 한개의 부모로 묶을 수 있을 겁니다. 로봇은 동물이 아니므로 일단 제외 하구요.

공통된 특성으로 "숨쉰다"와 "먹는다" 를 부모에 정의 합니다. 다만 숨쉬는 방법, 무엇을 먹는지는 다르므로 각각의
행동은 각 개체에 정의 합니다. (폐로 숨을 쉰다/아가미로 숨을 쉰다, 밥을먹는다/개밥을 먹는다/플랑크톤을 먹는다)

각각 파생된 "사람", "개", "고등어" 는 "동물" 로 대입해서 고유의 행동을 수행 할 수 있습니다. (여기까지는 abstract calss)

그런데 "로봇" 도 생물의 흉내를 낸 것으로 숨도 쉬고, 밥도 먹습니다. 왠지 "동물" 처럼 동일한 방법으로 다루고 싶은 경우가 있죠.

이때 interface로 먹는다/숨쉰다 를 정의하고, "동물"에서 이 interface를 가져다 두가지 행동을 구현합니다. (물론 구현은 각각의 자식들에게서..)
물론 "로봇" 도 이 interface를 이용하여 행동을 구현합니다. (숨쉬는것처럼 행동한다/밥먹는 것처럼 행동한다)

이제 "사람","개","물고기","로봇" 이 동일한 인터페이스로 동일하게 "숨쉬는것"과 "먹는것"을 표현 할 수 있게 되었습니다. 감이 오시나요?

머.. 위 예에서.. "로봇" 을 "동물" 의 범주에 넣으시겠다면 인터페이스는 필요 없을 수 있습니다. ㅋ

근데.. 실제로 먼가를 만들다 보면 상속 관계에 엮이지 않는데, 동일한 메소드로 표현하는게 좋아 보일 수 있습니다.
java 라면 thread class 가 좋은 표본이 될것 같은데요.. (abstract class 와 interface 를 둘다 지원하죠..)
java 라면 listener 들이 전부 interface 들이죠. 왜 그럴까요?

lmk378의 이미지

먼저 답변 감사합니다.
아직 잘 이해가 가지 않습니다.
동물이라는 클래스를 만들고 로봇과 사람이 동물클래스를 상속해서 사용하는 것과
동물이라는 인터페이스를 만들고 로봇과 사람이 상속해서 사용하는 방식을 비교했을때
인터페이스를 사용하는 것에 어떤 장점이 있죠?
숨쉬는 것과 먹는것이라는 메소드는 각각 사람과 로봇이 오버라이딩해서 다른방식으로 구현해
사용하는 것과 동일하지 않나요?

마지막줄에 "java 라면 listener 들이 전부 interface 들이죠. 왜 그럴까요?"<<사실 이것이 궁금해서 질문드렸습니다.

neocoin의 이미지

>>마지막줄에 "java 라면 listener 들이 전부 interface 들이죠. 왜 그럴까요?"<<사실 이것이 궁금해서 질문드렸습니다.

함수 포인터라고 생각하시면 됩니다.

------------------------------------------

여러 이유가 있지만, 주로 inteface , abstract 는 의존성 제거에 사용합니다.
너무 단순한 예제이지만.. 코드 참고하세요.

List 는 인터페이스 입니다. ( http://download.oracle.com/javase/6/docs/api/java/util/List.html )

import static java.lang.String.format; 
import static java.lang.System.out; 
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
 
public class Main {
	public static void main(String[] args) {
		List<String> sample = Arrays.asList("1,2,3".split(","));
 
		List<String> list1 = new ArrayList<String>();
		list1.addAll(sample);
 
		List<String> list2 = new LinkedList<String>();
		list2.addAll(sample);
 
		out.println(format("sample: %s %s", sample.getClass().getName(), sample));
		out.println(format("list2 : %s %s", list1.getClass().getName(), list1));
		out.println(format("list3 : %s %s", list2.getClass().getName(), list2));
	}
}

결과

sample: java.util.Arrays$ArrayList [1, 2, 3]
list2 : java.util.ArrayList [1, 2, 3]
list3 : java.util.LinkedList [1, 2, 3]

List 를 인자로 받는 모듈은 위의 ArrayList, LinkedList, Arrays$ArrayList 와 의존성을 가지지 않아도 됩니다.

lmk378의 이미지

답변 감사합니다.
의존성 관련되서 잘 이해가 되었습니다.

"java 라면 listener 들이 전부 interface 들이죠. 왜 그럴까요?" << class 일 수 없는 이유가 있나요?

jeongheumjo의 이미지


여러 이유가 있지만, 주로 inteface , abstract 는 의존성 제거에 사용합니다. 

neocoin 님이 이미 설명해주신 문장에 님의 물음에 대한 답변이 있습니다.
class 를 사용하면 해당 class 에 의존적이게 되기 때문이리라... 답변할 수 있을 것 같아요..

neocoin의 이미지

jeongheumjo 께서 잘 말씀해주셨지만, 제가 쓴것 중 정정이 필요한 부분이 있습니다.

interface 의존성을 얼마나 효과적으로 제거하느냐로 생각하시면 무리 없습니다.

abstract class는 제가 잘못 이야기 했습니다. 의존성 제거 보다는 중복 로직 제거에
더 많이 사용됩니다. 의존성 제거에도 사용할수 있지만, 이건 원래 의도가 아니죠.

JDK 보면 interface를 abstract class로 구현하고 이를 다시 상속해서 로직 구현하는 걸 관찰할 수 있습니다.
(일하다 보니 잘못쓴걸 깨달았네요. 죄송합니다;; 요즘 java 를 안한다는 핑계 댑니다.)

interface는 상속에 관여하지 않습니다. 이게 가장 큰 이점입니다.
영문 단어의 의미 그대로 interface라고 생각하시면 됩니다..

이는 Java가 다중 상속을 지원하지 않기 때문에 반드시 필요한 부분이기도 합니다.

----------------------

흔한 정렬 예제

import static java.lang.String.format;
import static java.lang.System.out;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
 
public class Main implements Comparator<String> {
	public static void main(String[] args) {
		List<String> sample = Arrays.asList("3,2,1".split(","));
 
		List<String> list1 = new ArrayList<String>();
		list1.addAll(sample);
 
		List<String> list2 = new ArrayList<String>();
		list2.addAll(sample);
 
		// 1. using anonymous class
		Collections.sort(list1, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				int result = 0, l = Integer.parseInt(o1), r = Integer
						.parseInt(o2);
				result = l > r ? 1 : -1;
				if (l == r)
					result = 0;
				return result;
			}
		});
 
		//2. using instance
		Collections.sort(list2,new Main());
 
		out.println(format("sample: %s", sample));
		out.println(format("list1 : %s", list1));
		out.println(format("list2 : %s", list2));
	}
 
	@Override
	public int compare(String o1, String o2) {
		int result = 0, l = Integer.parseInt(o1), r = Integer.parseInt(o2);
		result = l > r ? 1 : -1;
		if (l == r)
			result = 0;
		return result;
	}
}

아마 Android 의 listener 가 난무하는 느낌과 크게 다르지 않으리라 생각합니다.
1. 은 anonymous class이고, 2. Main의 instance 입니다.

2번 형태로 재사용시, interface로 정의하면 Main 클래스가 무엇을 상속하던지
구현에 영향 받지 않습니다. 하지만 abstract class 혹은 class를 받게 하면 Main은
이들을 상속받아야하며, 이미 다른 class를 상속했다면 구현이 불가능해 집니다.

abstract class 는 이러한 경우보다는 하나를 상속하는 여러 모듈에 동일 기능이
존재할때 묶어주기 위한 용도로 사용됩니다. 처음 언급했듯 이는 의존성 보다
중복 코드 제거에 촛점을 둡니다. See Also java.util.AbstractList

결과

sample: [3, 2, 1]
list1 : [1, 2, 3]
list2 : [1, 2, 3]

ps. interface와 abstract에 익숙해지면 그 다음에는 패턴들 문서를 보시면 다시
머리가 아파집니다. ;;

lmk378의 이미지

답변 감사합니다.
이해가 확실히 잘 되었습니다.
밥이라도 한끼 사드리고 싶네요~

semmal의 이미지

대부분의 클래스 기반의 OO언어에서 상속은 의미적으로 크게 두 가지로 나눌 수 있는데요.
하나는 다형성을 쓰기 위해 subtyping으로 타입을 이어받는 것이고, 또 하나는 작업 추가를 위해 delegating으로 기능을 이어받는 겁니다.
따지고 보면 다형성은 나누는 것인데, 작업 추가는 확대하는 것을 의미합니다.
이 두 가지가 상속이라는 같은 기능에 묶여있기 때문에 인식의 괴리가 생기고 프로그래밍이 힘들어집니다.
또한 다중상속 문제가 생기는 이유는, 이런 두 가지를 제대로 분리해내지 못했기 때문에 생깁니다.

delegating은 하나의 모듈에서 다른 모듈을 불러쓰는 #include나 import와 비슷한겁니다.
거기에다 밖에서 쓸 때 다른 모듈의 기능도 같이 쓸 수 있게 해주는 거지요.

다형성은 다른 글에도 자주 다뤄졌지만, 간단히 말하자면 같은 이름에 대해서 비슷한 다른 형태를 가질 수 있게 만들어주는겁니다.

예를들어서, Integer, Rational, Real, Number에 관련된 라이브러리를 만든다고 칩시다.
이걸 타입를 이용한 상속 개념으로 만든다면,
Number를 먼저 만든다음에, Real이 Number를 상속받고, Rational과 Integer가 차례대로 상속 이어서 받으면 됩니다.
그러면 Number에 있는 사칙연산은, 실수냐 유리수냐 정수냐에 따라서 다르게 동작하게 될 겁니다.

하지만, 모듈을 확장하는 듯한 상속 개념으로 만든다면,
Integer에 구현된 사칙연산을 가져와서, 그걸 조합해서 유리수 사칙연산을 만든다음,
그걸로 다시 실수 사칙연산을 만들고, 마지막으로 전체 수에 대한 사칙연산 모듈을 만들 수 있을 겁니다.

저 두 가지 방법을 섞어쓰면 대부분의 경우 결국에는 파탄이 옵니다.
그걸 구별하는 방법은, 기술적이라기보다는 수학적이고 개념적, 철학적입니다.
어떻게 바라보느냐에 따라 한쪽이 다른 한쪽보다 개발하기 쉬워질 수 있으므로, 어느 한쪽이 맞다고만을 할 수 없습니다.
그러니 다양한 책이나, 다른 개념을 공부해서, 같은 기능이라도 여러가지 시점으로 바라볼 수 있게 되면 구별이 쉬워집니다.

저 두 가지 방법을 섞어써도 파탄이 오지 않는 경우가 있긴 합니다.
abstract의 경우에는 저 두 가지가 유일하게 목적에서 일치하는 경우에 씁니다.
예를들어서, 나눗셈의 경우에는 실수, 유리수, 정수가 각자 다르게 동작해야 하지만,
덧셈의 경우에는 동일하게 동작해도 큰 무리가 없을 수 있습니다.
이 경우에 인터페이스도 다 따로 만들고, 그 인터페이스마다 같은 내용을 3번이나 다시 구현하는 것보다는
AbstractNumber와 같은 구현 클래스를 두고, 타입 상속과 함께 기능까지 같이 받으면 편하게 개발할 수 있습니다.
이것은 두 가지 방법을 같이써도 의미적이나 개념적으로 충돌이 나지않기 때문에 유효하고 편하게 쓸 수 있는 방법이 됩니다.

------------------------------
How many legs does a dog have?

winner의 이미지

semmal님의 글은 관념적으로 흘러갈 때가 종종 있어서 글읅 읽다 보면 '맞는 말인데 그래도 이건 좀 심한데...' 라는 생각이 들 때가 간혼 있습니다만, 이번 글은 제가 공감할 수 있는 좋은 예시가 있어 좋은 공부가 되었습니다.
정성껏 쓴 글에 아무런 답글이 없는 것이 아쉬워 이렇게 답글 답니다.

lmk378의 이미지

답변 감사합니다.
이해하기 좀 어렵긴 하나 winner님 말씀처럼 좋은 예시가 있어서
잘 이해되었습니다.

익명 사용자의 이미지

어렵게 생각할거 없이 간단합니다.

객체의 인터페이스와 구현을 분리하기 위해서 쓰는겁니다.

jeongheumjo의 이미지

물론 OOP 의 다른 개념도 적용 가능하겠지만,
그리고 위에 좋은 답변을 많이 해주셨지만,
KLDP 에서 어깨너머 배운 저도 한마디 거들면,

의존성을 뒤집기 위해서랍니다.

http://alankang.tistory.com/249

위 글이 이해하는데 도움이 될 것 같습니다.

emptynote의 이미지

아마도 Abstract class or interface 를 왜 사용하는가? 에 대한 답변으로

Factory pattern 이 가장 좋은 답변이 아닌가 합니다.

참고 url : http://en.wikipedia.org/wiki/Factory_method_pattern

cinsk의 이미지

오해의 소지가 있어서,

> 실질적으로 객체지향언어로 프로젝트를 진행하면서 어떤 경우에
> class로 구현하고 어떤경우에 interface로 작성해야 할지...

정확히 말하면 (객체지향언어->Java)에 해당하는 말이겠죠?

semmal의 이미지

C++ 이라고 하면 "어떤 경우에 class로 구현하고 어떤 경우에 pure virtual class로 작성해야 할지"라고 말할 수 있기 때문에

꼭 Java라고 지정할 필요는 없을 것 같습니다.

개인적으로는

#define interface class
#define method public: virtual

이런걸 심심풀이로 짜는 코드에는 즐겨쓰면서 장난도 치고 그러거든요.

------------------------------
How many legs does a dog have?

winner의 이미지

COM 보면 hell이 보여서 싫다능~.

semmal의 이미지

아뇨
그냥 C++로 자바처럼 짜기 이런거죠.
C++로 하스켈처럼 짜기나
C++로 리습처럼 짜기
C++로 프롤로그처럼 짜기도 해봤는데, 프롤로그는 쪼매 힘들더라구요.

------------------------------
How many legs does a dog have?

lmk378의 이미지

java라고 지정하는게 맞을 수도 있습니다.
c++에서는 자바의 interface와 동일하게
순수가상함수만으로 구성된 순수가상클래스를 만들수도 있겠지만
다중상속을 지원하는 c++에서는 그럴 이유가 없지 않나요?

semmal의 이미지

아뇨! 어휴 큰일날 소리를 하시는군요.
C++에서 문제가 너무 많이 생겨서 Java에서 그런 제약이라도 걸었다고 보셔야해요.
즉, Java의 방법은 C++에서도 여전히 유효합니다.
다중상속 가능하다고, 함부로 쓰고 그러면 피봐요.

일부로라도 Java 방식으로 짠 다음에, 성능이나 큰 문제가 없는 부분에서만 틀을 깨는거죠.
앞서도 말했지만, 그 두 가지 상속의 괴리감은 클래스 기반의 OOPL이라면 거의 원죄에 가까워서, 무시해서는 안됩니다.
프로그래머 스스로가 충분히 멀리해야 합니다.

------------------------------
How many legs does a dog have?

lmk378의 이미지

당연히 다중상속을 무턱대고 사용하면 피를(?) 보겠죠.
java도 다중상속문제 때문에 미지원후 interface라는 순수가상클래스를 만든거구요.
제 말뜻은 무턱대고 사용하는 경우에가 아니라 굳이 c++프로젝트를 java의 구조와 동일하게 구성할 필요는 없지 않은가였습니다.
추가로 다중상속문제는 c++아키텍처 설계시 고려하지 못한 문제라기보단
설계자의 선택이라고 생각합니다만...(주관적인 생각입니다)
만약 그걸 문제로 보신다면 c++스팩에서 빠지는게 맞다고 생각합니다.

semmal의 이미지

C++ 철학은 자바하고 다르니깐 있는게 맞습니다.

조금 다른 이야기지만, DB 정규화는 원칙적으로 무조건 좋은겁니다.
다만 현실로 들어서면, 역정규화를 통해서 성능향상을 노립니다.
역정규화가 현실적으로 더 빠르다고, DB 정규화를 하지않고 그냥 DB를 만들면 문제 생길일이 많아지는게 뻔하지요.

자바와 C++의 관계도 정확하지는 않지만 비슷합니다.
자바식의 설계(일반적인 OO 설계를 애초에 이렇게 부르는게 더 이상하지만)는 자바만을 위한게 아닙니다.
C++만의 기능을 이용하는 설계가 설령 더 속도를 낼 수 있다고는 하더라도, 자바형태의 설계가 선행되어야 문제가 생길 여지가 적어진다는 말입니다.

이건 C++만의 이야기가 아니며 어떤 형태의 클래스 기반 OO언어를 쓰더라도,
문법적으로 제약을 걸어놓지 않는 다음에야 OO 프로그래밍을 한다면 같은 문제에 부딪힙니다.

물론 C++이 OO를 쓰지 않고 프로그래밍한다면, 이 논의 자체가 의미가 없습니다.

------------------------------
How many legs does a dog have?

lmk378의 이미지

제 글을 약간 오해하신거 같아서 씁니다. (제 글을 보니 오해의 소지도 있는것 같습니다.)
c++ 다중상속이 빠지는게 맞다고 쓴것이 아니라 "다중상속이 문제가 된다면 스팩에서 빠지는 것이 맞다" 고 적었습니다.
"문제"를 "오류" 바꿔서 읽으시면 될 것 같습니다. 여기서 약간 오해가 생긴것 같군요.
저도 있는게 맞다고 생각합니다. 오히려 semmal님의 글이 빠지는 것이 맞다고 쓰신걸로 생각되어 답변했던 것입니다.
어떤 말씀인지 잘 이해했습니다. 감사합니다.

lmk378의 이미지

선배님 안녕하세요. 2004년도인가 학교세미나에서 뵙고(doxygen하고 emac관련 세미나였던것 같습니다.)
kldp에서 세번째로 뵙네요. 반갑습니다^^
자바를 예로들어 질문했습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.