2020-12-21 Topic!
현실세계에서 '추상화'란?
포토샵을 사용하여 컬러 사진을 흑백 사진으로 변경한다고 생각해보자.
포토샵은 이미지 프로세싱이라는 복잡한 연산을 수행하는 프로그램이지만,
우리는 포토샵이 제공하는 여러가지 기능들을 사용하여 사진 보정이라는 행위에만 집중할 수 있다.
실제로 컬러 사진을 흑백 사진으로 변경할 때는 행렬로 이루어진 사진의 픽셀 데이터를 순회하며
RGB 값의 평균을 내거나하는 등의 과정을 수행해야한다.
하지만 그런 복잡하고 귀찮은 과정이 추상화되어있기 때문에 사용자는
그저 포토샵이 외부로 노출해준 기능인 Image > Adjustments > Desaturate을 사용하면 되는 것이다.
객체 지향적 관점에서 '추상화'란?
즉, 모델화하는 것으로 데이터의 공통된 성질을 추출하여 슈퍼 클래스를 선정하는 개념이다.
모델링을 통해 코드를 추상화하면 코드의 재사용성을 높일 수 있고 코드의 가독성을 높여
코드를 이해하기 훨씬 더 쉽게 만들어 주기도 한다. 그리고 추상화된 코드는 자연스럽게 일관성을 가지게 된다.
따라서, 생산성이 높아지고 에러가 감소되는 영향을 미치게 된다.
프로그래머가 기능 개발에 투자하는 시간보다 유지 보수, 버그 해결에 많은 시간을 쓴다는 것을 감안하면,
추상화의 중요성이 크다는 것을 인식할 수 있다.
우리가 많은 것을 기억해야 할 때 모든 것을 외운다기보다는 패턴이나 맥락을 외우면
기억해야 할 양이 줄면서 기억에 남는 것을 생각하면 이해할 수 있다.
간단하게 생각해보면 추상화를 했을 때, 내부를 몰라도 그냥 사용할 수 있다.
반대로 생각하면 추상화된 것은 조금 다르게 수정해서 사용하고 싶더라도 내부의 어떤 것이 있을지 또는 얼마나 복잡한지 몰라서 쉽지 않다.
따라서 작게작게 추상화시켜서 조금 다른 기능을 만들기 위해서 블럭을 조립하듯 만들어진 것을 잘 정리된 추상화라고 할 수 있다.
자바의 경우에는 바이트 코드와 JVM(Java Virtual Machine)이라는 개념을 사용하여 한번만 코드를 작성해도 모든 OS에서 동작하는 프로그램을 작성할 수 있다는 점이 굉장한 강점이었다.
OS나 CPU에 종속되어있던 기존의 프로그래밍 언어들에 비해 추상화 수준이 높아진 것이다.
그러나 처음 자바가 나왔을 당시에는 C에 비해서 너무 느려서 못 써먹을 물건이라고 상당히 많이 까였다.
또한 가비지 컬렉터도 개발자가 일일히 메모리를 할당하고 해제하지 않아도 되는 편리함을 제공하지만 GC가 객체의 메모리 해제 시점을 매번 추적하고 있어야하는 성능 문제나, 개발자가 객체가 메모리에서 해제되는 시점을 정확히 알기 어렵다던가, 참조 횟수 계산 방식을 사용할 때 순환 참조 객체를 해제하지 못하는 등 문제들이 여전히 존재하기 때문에 늘 완벽하게 동작하지는 않는다.
하지만 그렇다고 자바나 가비지 컬렉터나 자바를 사용할 수 있는 환경에서 굳이 C를 사용하고 수동으로 메모리를 관리하고 싶어하는 사람이 많지는 않을 것이다.
애초에 이런 높은 수준의 추상화를 제공하는 기술이 가지는 성능 상의 단점을 머신이 어느 정도 커버할 수 있기 때문에 사람들이 많이 사용하는 것이기도 하다.
참고 : https://evan-moon.github.io/2019/12/15/about-functional-thinking/
개념
어떤 객체에 대한 명제로 이 객체가 어떤 메서드들을 제공하고 어떤 역할을 하는지에 대한 일종의 설명서
자식 클래스가 여러 부모 클래스를 상속받을 수 있다면, 다양한 동작을 수행할 수 있다는 장점을 가진다.
하지만 클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않는다.
즉, 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원하고 있다.
인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다.
자바에서 추상 클래스는 추상 메소드뿐만 아니라 생성자, 필드, 일반 메소드도 포함할 수 있다.
하지만 인터페이스는 오로지 추상 메소드와 상수만을 포함할 수 있다.
//1번
public interface Calc {
//2번
double PI = 3.14; //나중에 상수가됨
int ERROR = -9999999;
//3번
int add(int num1, int num2);
int substract(int num1, int num2);
int times(int num1, int num2);
int divide(int num1, int num2);
}
1번 : 계산을 기능을 하는 객체에 인터페이스를 작성하기 위해 Calc클래스를 만들고 클래스 선언부(public class Clac)에 class를 지우고 interface를 적으면 인터페이스로 선언된다.
또한 클래스를 생성할 때 클래스로 생성하지 않고 바로 인터페이스를 생성하는 방법도 있다.
2번 : 변수 PI와 ERROR은 실제 변수가 아닌 상수로 컴파일과정에서 public static final이 다 붙어서 상수화된다.
3번 : 계산 기능을 하는 add(), substract(), times(), divde() 메서드를 만든다. abstract 키워드가 생략됬지만 이들은 구현부가 없는 추상메서드 이다. 이 메서드들도 컴파일 과정에서 추상 메서드로 변환한다.
메서드를 함수의 이름, 매개변수 반환값을 선언할 수 있다는 것은 결국 이 메서드가 어떤 기능을 대략적으로 보여주는 기능을 한다.
인터페이스는 이렇게 객체의 기능을 간략하게 설명하는 기능을 하고 하위 클래스에서 어떻게 구현되야 하는지를 안내한다.
공통점
자기 자신이 new를 해서 객체를 생성할 수 없으며
추상클래스를 extends 받거나, interface를 implements 한 자식만이 객체를 생성할 수 있다.
타입이 지정되있기 때문에 선언된 타입과 자식의 타입이 같아야만 한다.
차이점
그래서 상속을 받음에도 불구하고 클래스에선 상속이라고 쓰지만 interface는 implemets(구현) 이라고 쓴다.
추상클래스의 정의는 abstract 메소드가 하나라도 존재하는 클래스를 일컫는다.
때문에 일부는 구현된 메소드도 있고, abstract라고 붙어있는 메소드는 구현이 안되어있다.
추상클래스를 상속받는 클래스는 반드시 추상메소드를 구현해야한다.
그래서 필수적으로 구현해야할 메소드가 있을 때 추상클래스를 쓰게된다.
인터페이스는 구현체 없이, 메소드에 대한 명세만 되어있다.
인터페이스를 상속받는 클래스에서는 반드시 인터페이스에 있는 메소드를 다 구현해야한다.
자바는 단일상속을 지원하기 때문에 추상클래스는 단일상속이지만,
interface를 사용하게 되면 implements를 구현하는 부분에서 extends 또한 사용할 수 있다.
즉, 다중상속이 가능해진다.
'이러이러한 메소드를 쓸 것이다.' 인터페이스에 선언을 해놓고, 가져다가 반드시 선언된 그대로 모두 구현하면 되는게 인터페이스이고,
이러이러한 메소드가 있지만 가져다 쓰거나 오버라이드 하거나, abstract가 붙은 메소드는 반드시 구현하면 되는게 abstract class이다.
인터페이스는 다형성에 대한 개념(Polymorphism)으로 추상화를 구현하였고
추상 클래스는 상속에 대한 개념으로 추상화(Overriding)를 구현하였다.
즉, 인터페이스는 무조건 implemets 되어야 하는 상수,추상메소드를 추상화한 구현체로
자식 클래스에서 다양한 형태로 재정의할 때 사용한다.
(예 - 동물 인터페이스를 동물 클래스인 곰, 병아리에 implemets 한다.)
추상클래스는 부모 추상 클래스의 기능을 유전받으면 그에 대한 기능을 더 추가하거나 수정한다.
(예 - 곰 추상 클래스를 반달가슴곰 클래스에 extends 한다.)
// 추상 클래스
public abstract class EmptyCan {
public abstract void printContent(); //추상 메서드
public abstract void printName(); //추상 메서드
}
public class BeerCan extends EmptyCan{
public void printContent() { //EmptyCan의 printContent() 구현
System.out.println("흑맥주");
}
public void printName() { //EmptyCan의 printName() 구현
System.out.println("맥주캔입니다.");
}
public void sayHello() { //새로운 멤버 메서드 추가
System.out.println("안녕하세요 맥주캔입니다.");
}
public static void main(String args[]) {
BeerCan b = new BeerCan();
b.printContent();
b.printName();
b.sayHello();
}
}
interface hello {
public void sayHello();
}
interface bye {
public void sayBye();
}
class TalkinRobot implements hello, bye {
public void sayHello(){
System.out.println("안녕!");
}
public void sayBye(){
System.out.println("잘가!");
}
}
public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry<?,?> e = (Map.Entry) o;
return Objects.equals(e.getKey(), getKey()) && Objects.equals(e.getValue(), getValue());
}
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
@Override
public String toString() {
return getKey() + "=" + getValue();
}
}
추상화라는 개념이 너무나 추상적(?)이라서 사람마다 이해하는 추상화가 다 다를 거라고 생각이 됩니다. 메트가 생각하는 추상화라는 개념이 궁금합니다~
제가 이해한 추상메소드와 인터페이스의 용도가 정확한가요~?
인터페이스는 다형성에 대한 개념(Polymorphism)으로 추상화를 구현하였고
추상 클래스는 상속에 대한 개념으로 추상화(Overriding)를 구현하였다.
즉, 인터페이스는 무조건 implemets 되어야 하는 상수,추상메소드를 추상화한 구현체로
자식 클래스에서 다양한 형태로 재정의할 때 사용한다.
(예 - 동물 인터페이스를 동물 클래스인 곰, 병아리에 implemets 한다.)
추상클래스는 부모 추상 클래스의 기능을 유전받으면 그에 대한 기능을 더 추가하거나 수정한다.
(예 - 곰 추상 클래스를 반달가슴곰 클래스에 extends 한다.)
public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry<?,?> e = (Map.Entry) o;
return Objects.equals(e.getKey(), getKey()) && Objects.equals(e.getValue(), getValue());
}
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
@Override
public String toString() {
return getKey() + "=" + getValue();
}
}
추상화의 의미와 필요성이 확 와닿네요. 좋은 글 감사합니다