오늘은 비교적 여유로운 날이다.

image

오늘의 수업은 보이는 라디오와 개인 블로그 작성하는 시간이다.
여태까지 공부해왔던 것을 조금씩 되짚어 보는 시간을 가질 수 있도록
코드스테이츠쪽에서 배려해준 것 같다.
물론 내일이 추석인 것도 감안해주신듯하다!! ㅎㅎ

코드스테이츠 보이는 라디오에 내 사연이 소개가 되었다.
미래에 취업 수요와 시장전망의 관련된 얘기였다.
대표주제로 선정되었다는 뜻은 나와 같은
고민한 사연들이 많았던 것 같은 느낌이다.

사실 모든 걱정들은 사람을 지치게하기도하고 원동력을
만들어준다고 생각하면서 살고있다.
모든게 적당한게 좋듯이 미래에 대한 걱정은 제쳐두고
내가 선택한 길이 맞다고 믿으며, 지금에 집중하려고한다.
걱정만한다고 해결되는 것은 없고, 그 걱정에 대한
대비는 공부하고 코딩하는 것!

오늘도 스스로에게 화이팅을 외쳐본다 !! 🔥


추상화

추상이라는 의미를 먼저 생각해보자
보통 무언가 확실하지 않거나 정해져있지 않을때 우린
음..? 조금 추상적인데? 라는 말을 자주쓴다.
사전적의미로는 “사물이나 표상을 어떤 성질,본질에 착안하여 그것을 추출하여 파악하는 것”
이라고 정의한다고한다. 말이 너무 어렵고 핵심적으로 알면 될 것은
공통성과 본질을 모아 추출하는 것이다.

사람을 예로 들어보자

image
[파블로 피카소 - 우는 여인]

사람이라는 추상화로 그린 작품이다.
우리는 사람하면 생각하는 것이 무엇일까?
공통적으로 눈이 2개있고, 입술,치아,코, 머리카락 등
공통적으로 가지고 있는 특성이 많다.

하지만 이 공통적인 부분도 다를 수 도 있다.
머리카락같은 경우 색깔이 전부 다르듯이
우리는 추성적으로 공통적인 특성을 가지고 사람은 이렇다!
라고 생각을 하기 때문에 추상화라는 것은 공통성과 본질을
모아 추출한 것 이라고 정의하면 될 것 같다.


abstract 제어자

위의 제어자는 기타제어자에 포함되어 있는 제어자이다.
abstract를 메서드 앞에 붙이면 ‘추상 메서드
클래스 앞에 붙이면 ‘추상 클래스‘라고 불른다.
그럼 이 abstract 제어자가 붙은 추상 메서드와 추상 클래슨느 어떤 역할을하는가?
가장 큰 핵심적인 개념은미완성‘에 있다.

abstract class Person { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
	abstract void run(); // 메서드 바디가 없는 추상메서드
}

간단한 예제로 알아보자 Person이라는 클래스의 run(); 달리는 기능이 있다.
abstract를 제어자로 붙이 class는 최소 하나 이상 추상 메서드가 포함되어있어야하고
메서드는 바디가 없어야한다. 바디가 없어야한다? 즉 run();이라는 기능에 대해
상세하게 정해진게 없다는 뜻이된다.


추상 클래스 (abstract class)

그럼 abstract로 정의된 class인 추상 클래스에 대해 알아보자

Person man1 = new Person(); // 에러발생.

위에서 예제로든 Person 클래스는 abstract 제어자를 사용하기때문에
추상 클래스가 되어진다. 추상 클래스는 기본적으로 객체를 생성하지 못한다.
이유는 미완성 클래스이기 때문이다.

추상 클래스의 큰 특징은 추상 메서드가 하나 이상 포함되어야 한다는 점
외에는 기본적으로 일반 클래스와 동일하다고 볼 수 있다.
추상 클래스는 상속 관계에 있어서 새로운 클래스
작성하는데 매우 용이하다. 예제를 하나 들어보자.

abstract class Person {
	public String hair;
	public abstract void run();
}

class Programmer extends Person { // Person 클래스로부터 상속
	public Programmer() {
		super.hair = "검정색";
	}

	public void run() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("5km/sec");
	}
}

class Singer extends Person { // Person 클래스로부터 상속
	public Singer() {
		super.hair = "갈색";
	}

	public void run() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("10km/sec");
	}
}

class Main {
    public static void main(String[] args) throws Exception {
        Person programmer = new Programmer(); // 다형성 상위클래스로 하위클래스 참조
        programmer.run();
        System.out.println(programmer.hair);

        Person singer = new Singer(); // 다형성 상위클래스로 하위클래스 참조
        singer.run();
        System.out.println(singer.hair);
    }
}

//출력
5km/sec
검정색
10km/sec
갈색

추상적인 클래스인 Person에 hair라는 머리색을 담는 변수와
run();이라는 달리는 기능의 메서드를 만들었다.
이기능은 완성되지 않은 미완성 메서드이다.

여기서 추상클래스 Person을 상속받아
하위 클래스로 Programmer와 Singer를 만들었다.
이 두사람은 각각 다른 머리색깔과 달릴때 속도가 다르다.
각각 run();달리는 기능을 오버라이드해서 재정의 해주었고
각각 다른생성자로 인스턴스를 생성할때 각기 다른 속도와 머리색으로
정의되는 모습을 볼 수 있다.

여기서 중요한점은 구체화가 되는 순서는, 상속계측도의 상층부에 위치 할 수록
추상화의 정도가 높고 아래로 내려갈 수록 구체화 된다..


final 키워드

말그대로 최종,마지막이라는 의미를 가진 키워드이다.
클래스,메서드,변수 앞에 위치할 수 있고 차이점이 있다.

위치 의미
클래스 변경, 확장 불가능한 클래스, 상속불가
메서드 오버라이딩 불가
변수 상수, 값 변경이 불가능

코드 예제를 들어보자

final class Person { // 확장/상속 불가능한 클래스
	final String hair = "검은색"; // 변경되지 않는 상수

	final int run() { // 오버라이딩 불가한 메서드
		final int speed = 15; // 상수
		return speed;
	}
}

위에서 부터
Person 클래스는 확장,상속이 불가능해진다.
이말은 즉 다른클래스에 확장도 안되며, 다른클래스를 상속하지도 못한다.
hiar 인스턴스 변수는 검은색이라는 값에서 변경될 수가 없다.

말은 즉슨 다른 곧에서 hair = “노란색”이라고 재정의가 불가능하단 얘기다.
다른 값을 넣으려고할 경우 cannot assign a value to final variable 이라는
오류를 확인할 수 있을 것이다.
run(); 메서드는 다른 클래스에서 오버라이딩이 불가능해진다.

위에 예제에서 보앗듯이 추상클래스에서는
바디를 정의하지않고 오버라이딩해 하위 클래스에서 재정의를 한다.
이럴때 오버라이딩을 사용하지 못하는 고정메서드가 되어버리는 것이다.
함나디로 정의하자면 final 제어자가 추가되면
더이상 변경이 불가하거나 확장되지 않는 성질을 가지게 된다.


인터페이스 (interface)

이제 우리가 핵심적으로 배울 인터페이스이다.
우리가 Git을 사용할때나 컴퓨터를 사용할때 GUI를 통해 사용할 수 있게된다.
Graphic User Interface의 약자로 사용자와의 그래픽 인터페이스라고 해석된다.
여기서 인터페이스란? 이어준다는 느낌으로 받아들이면 될 것 같다.

image

자바의 클래스를 만들때 interface를 선택할 수 있다.

C = 클래스 / I = 인터페이스
그럼 자바의 interface는 어떤 역할을 하냐?

image

이제 고객이라는 클래스에서 확장받은
고객들 (고객A,고객B,고객C ….)은 음료주문이라는 기능을 공통적으로 사용할 수 있다.
여기서 인터페이스(초록색 원)이 없다면 고객과 고객들이 의존,확장하게 되어지는데
인터페이스를 사용하면 이사이에서 징검다리 역할로
모든 고객들은 인터페이스에 의존하게되어진다.

즉, 인터페이스를 통해서 소통하게 되어지는 느낌이다!!

여기서 다형성을 적용해보자면 인터페이스를 통해서 하위 클래스인
고객A,고객B,고객C….. 등 무한히 늘어나는 고객에
참조를 할 수 있다는 점이다. 굳이 고객마다 매개변수를 전달해줄
필요없이 인스턴스 생성시 인자에 하위클래스를 생성자로 넣어주기만하면
고객들의 역할과 음료를 시키는 구현을 구분해서 작업이 된다는점이다.
여기서 그럼 그냥 추상클래스에 다형성을 적용해서 쓰면되지않냐?
라는 물음이 생기는데 아래에서 알아보자
기본적인 인터페이스 클래스를 만들고 그안에서 사용하는 방식이다.

public interface InterfaceEx{
	//상수 (값이 고정되어있음)
   	//추상 메서드 (구현부가 완성되지않음)
}

이전에 추상클래스에대해 배웠는데 추상클래스보다
더더더더 추상성을 가지는 것이 인터페이스이다.
그러므로 공통적인 필드만 모아놓은 상수와
구현체가 구현되지않은 추상 메서드만 사용이 가능하다.

public interface InterfaceEx {
    public static final int rock =  1; // 인터페이스 인스턴스 변수 정의
    final int scissors = 2; // public static 생략
    static int paper = 3; // public & final 생략

    public abstract String getPlayingNum();
		void call() //public abstract 생략 
}

그래서 기본적으로 인터페이스 안에서는 자동으로 타입이 지정되어진다.

인터페이스 내부의 모든 필드
public static final 로 정의가된다.

인터페이스 내부의 모든 메서드
public abstract 로 정의가 되고
인터페이스 안에 있는 경우 컴파일러가 생략된 부분을 자동으로 추가해주게된다.

클래스의 인터페이스를 확장할때 쓰는 방법이다.

class 클래스명 implements 인터페이스명 {
		... // 인터페이스에 정의된 모든 추상메서드 구현
}

인터페이스는 다중확장이 가능하다.
이점이 추상클래스와 가장 큰차이다. 추상 클래스는 extends를 해주기때문에
다중상속이 불가능하다. 또한 추상성이 명확한 인터페이스는
구현체를 반드시 오버라이드하여 구현해줘야한다.

이말이 무슨말이냐면
인터페이스에서 추상적으로 정의한 메서드에는 바디가 없다.
공통적인 기능인 메서드를 오버라이드하여 클래스에서
그 바디를 만들어줘서 덮어써준다는 느낌이다.
인터페이스를 사용하면 역할과 구현을 분리시켜 복잡한 구현의 내용 또는
변경 상관없이 해당 기능을 사용할 수 있다는 점.
코드 변경의 번거로움을 최소화하고 손쉽게 해당 기능을 사용할 수 있다는 점이다.

이 내용을 확인 시켜줄 코드 예제를 확인해보자

public class InterfaceExample {
    public static void main(String[] args) {
        User user = new User(); // User 클래스 객체 생성
        user.callProvider(new Provider()); // Provider 객체 생성 후에 매개변수로 전달
    }
}

class User { // User 클래스
    public void callProvider(Provider provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
        provider.call();
    }
}

class Provider { //Provider 클래스
    public void call() {
        System.out.println("바보");
    }
}

class Provider2 { // 새로 생긴 Provider2 클래스
    public void call() {
        System.out.println("천재");
    }
}

위와 같은 경우는 User 클래스가 Provider를 의존하고있다.
main 메서드 호출 -> User -> Provider 순으로 호출되기 때문이다.
여기서 보면 우리는 Provider라는 클래스를 호출하고 있는데
Provider2라는 새로운 클래스가 생겼다? 라고 가정을하면

class User { // User 클래스
    public void callProvider(Provider provider) {
        provider.call();
    }
    public void callProvider(Provider2 provider2) { //새로 만듬
    	provider2.call();
    }  
}

User 클래스안에 Provider2를 새로 생성하거나
기존의 Provider를 교체하거나 두가지 방법 뿐이다.
그렇게되면 매번 Provider가 늘어날 경우마다
우리는 User 클래스를 매번 수정해줘야하는 번거로움이 있다.
이럴 경우를 위해 인터페이스가 등장한다.

interface Cover { // 인터페이스 정의
    public abstract void call();
}

public class InterfaceExample {
    public static void main(String[] args) {
        User user = new User();
        user.callProvider(new Provider());
        user.callProvider(new Provider2());
    }
}

class User {
    public void callProvider(Cover cover) { // 매개변수의 다형성 활용
        cover.call();
    }
}

class Provider implements Cover {
    public void call() {
        System.out.println("바보");
    }
}

class Provider2 implements Cover {
    public void call() {
        System.out.println("천재");
    }
}

여기서 인터페이스를 사용해 call이라는
구현은 각각 Provider에 적용되어있다.
“바보”, “천재”로 오버라이딩되어 구현되어있고

시작의 흐름을 보면 main메서드에서
User 클래스의 객체를 생성했다.

user 객체로 callProvider라는 메서드를 호출하고 있고
인자는 new Provider();new Provider2();를 넘겨주고있다.

호출되어진 callProvider 메서드의 매개변수 타입은 인터페이스 Cover이고
최종적으로 cover를 참조변수로 받고 있다.
즉, Cover cover = new Provider(); 가 되는 셈이고
Cover가 더 상위 클래스이고 이것을 확장받은 Provider가 하위 클래스이므로
다형성을 이용한 매개변수의 활용이라고 볼 수 있다.

이렇게 되면 상위클래스 Cover가 하위클래스인 Provider를 참조할 수 있게되어지고
결과 적으로 cover라는 참조변수의 멤버는 인터페이스에 정의된
public abstract void call(); 하나의 멤버만 가지게된다.
그렇게 cover.call();을 호출하게되면 오버라이딩된

하위클래스의 값들을 참조하게되면서 “바보”와 “천재”가 순서대로 출력되게 되어지는 것이다.
이코드만 참조해도 알 수 있는 것은 어찌보면 최종 구현인 User 클래스의 cover.call();
하나로 구현을 해놓고 Provider가 100명~1000명~ 몇명이 늘어나던
cover.call();이라는 구현은 딱 1개로 정의되고
더이상 수정할 필요가 없다는 점이다.


마치며..

여태까지 드디어 길고긴
객체지향 심화까지 달려보았는데
상속,캡슐화,다형성,추상화
이렇게 중요한 핵심 요소에 대해 공부했다.

사실 이전부다 이해했다면 거짓말이고
어느정도 추상적으로^^ 감이 잡힌 것 같다.
앞으로 스프링을 들어가고 나서도 계속 보게될 개념들이니
이번 추석을 통해서 조금더 체득해볼 예정이다

오늘 공부는 여기서 끝!!


오늘의 커피량: ☕️ ☕️ ☕️ ☕️️️️
오늘의 점심: 간장계란밥, 스팸