오늘(9월 20일)부터 기다려왔던 백엔드 데브코스 교육을 시작하게 되었어요. 평소 배운것들을 블로그에 정리하는 성격이기에 데브코스에 참여하면서 TIL(Today I Learned)을 작성하면 좋겠다. 라는 생각을 하게 되었고, TIL은 꾸준하게 작성하는 것이 굉장히 중요하다고 하는데 끝까지 할 수 있겠지 ?... 화이팅 (조금이라도 나태해진다면 이 글을 다시 보러 와야겠다.)
TIL은 말그대로 Today I Learned의 약자로 오늘 내가 공부한거나 새로 배운 것들을 기록하기 위한 용도입니다. 그래서 저는 중요하다고 생각하는 개념들과 새롭게 알게된 개념들을 중점적으로 정리하려고 해요 !
📌 TIL
1) 객체지향적으로 개발을 해야 하는 이유 ?
: 라는 키워드에 대한 질문을 보고 막상 대답을 할 수 없었습니다. 무작정 작성했기 때문이 아닐까... 기존 절차지향의 방식으로는 클래스 간 의존 관계가 강하게 연결되어 있어 캡슐화가 깨질 뿐더러, 테스트 시에도 용이하지 않다라는 등의 점 때문에 객체지향이 탄생했다고 해요. 객체지향을 통해 캡슐화를 지키고, 테스트도 용이해지며 응집도까지 높아지는 결과를 얻을 수 있습니다.
2) 클래스와 상속
1. 부모 클래스를 상속한다고 부모의 모든 것을 사용할 수 있는 것은 아니다. (접근 제어자에 의해 상속 여부가 결정된다)
2. 자식 클래스의 메서드를 사용하려면, 자식 타입의 레퍼런스 변수를 선언해야 합니다.
3. 자식 인스턴스를 생성했다 하더라도, 부모 타입의 변수라면 자식의 메서드는 사용이 불가능 합니다.
4. 자식 클래스에서 부모의 메소드를 오버라이딩 하고, 자식 인스턴스를 생성한다면 부모 타입이던 자식 타입이던 선언한 타입의 변수로 오버라이딩한 메서드를 호출하면? 무조건 오버라이딩한 메소드가 호출됩니다.
5. 오버로딩은 파라미터의 차이로만 인정되며, 리턴 타입으로는 구분이 불가능하여 오버로딩이 불가능합니다.
상속 관련 주의사항
1) 상속은 필드에 대한 재사용이 필요할 때 사용하도록 해요.
2) 메서드 재사용을 위해선 전략 패턴을 구성하도록 해요.
3) 리스코프 치환 원칙 (부모 타입이 할 수 있는 일 -> 자식 타입도 할 수 있어야 함)
4) 내적 동질성 : 어떤 형으로 객체를 참조해도 원래 형으로 동작합니다. 이게 바로 4번에 해당하는 내용이에요.
여기서 3)리스코프 치환원칙과 , 4)내적 동질성을 만족하는 언어가 바로 "객체지향 언어"라고 해요.
그리고 만약... 클래스 중 다운캐스팅을 하는 부분이 단 하나라도 존재한다면 LSP(리스코프 치환 원칙)를 위반하는 것이라고 해요 😱
3) 추상 클래스와 인터페이스
💡 인터페이스
- 다형성을 제공해주는 추상적인 존재로, 인터페이스에 의존하도록 설계하도록 해요.
- 상속에 비해 여러 개의 인터페이스를 구현할 수 있으며, 구현을 강제하는데 목적이 있어요!
- Java 8부터 도입된 디폴트 메소드 내에서 추상 클래스와 마찬가지로 추상 메소드를 호출할 수 있습니다.
💡 추상 클래스
- 인스턴스 생성이 불가능, 하지만 ? 방법이 있긴합니다. (생성하고자 하는 부분에 메소드를 오버라이딩 해주면 됩니다.)
- 구현을 강제하는 인터페이스와 달리, 공통적인 로직을 작성해놓고 각 구현이 필요한 부분만 따로 구현할 수 있게 추상 메서드로 선언하는데 목적이 있어요 ! (디자인 패턴 : 템플릿 메서드 패턴)
- 이미 정의된 메소드 내에서 추상 메소드를 호출하는 것이 가능합니다.
아! 그리고 만약 한 클래스가 두 개 이상의 인터페이스를 구현한다면
public class MyClass implements Interface1, Interface2 {
...
}
Interface1 if1 = new MyClass();
위와 같이 MyClass() 인스턴스를 생성했더라도, Interface1 타입으로 변수를 선언한다면 MyClass 내의 구현해놓은 Interface1의 메소드만 호출이 가능합니다. Interface2의 메소드는 호출할 수 없어요 !!
마지막으로 추상 클래스와 인터페이스가 비슷하게 느껴질텐데.. 어떤 것을 사용해야할 지 고민이라면 가급적 인터페이스를 사용해요. (추상 클래스를 꼭 사용해야하는 것이 아니라면)
그렇다면 언제 추상 클래스를 사용해야 할까요 ? 🤔
1) 인스턴스 변수(필드)가 필요한 경우
: 클래스와 달리 인터페이스는 필드를 가질 수 없다는 특징이 있습니다. 따라서 필드가 필요하다면 추상 클래스를 사용해야 해요 !
2) 생성자가 필요한 경우
: 추상 클래스는 생성자 선언이 가능한 반면에, 인터페이스는 생성자 선언이 불가능하다고 해요.
3) Object 클래스의 메서드를 오버라이딩 하고 싶은 경우
: 추상 클래스는 'Object'에서 상속받은 메서드 예를 들어, toString() 같은 메서드를 오버라이딩 할 수 있다고 합니다. 반면 인터페이스는 오버라이딩 할 수 없다고 해요 !
abstract class MyAbstractClass {
@Override
public String toString() {
return "이것은 오버라이딩한 toString() 구현이에요~";
}
}
4) 접근 제어자
: 인터페이스는 반드시 public으로 열어둬야 한다는 특징이 있습니다. 대신 추상 클래스를 사용하면 protected나 default 등 외부에서의 접근을 제어할 수 있겠죠 ? 이런 사항도 고려한다고 해요.
4) Enum
- 열거형으로, 서로 연관된 상수의 집합이라고 해요.
enum Day {
MON,TUE,WED,THU,FRI,SAT,SUN;
}
오늘 공부하면서 새로 알게된 것은 enum을 활용하여 람다식을 사용하는 방법이에요. 배운 내용을 바탕으로 응용 해봤습니다.
public enum Day {
MON(() -> "월요일 입니다."),
TUE(() -> "화요일 입니다."),
WED(() -> "수요일 입니다."),
THU(() -> "목요일 입니다."),
FRI(() -> "금요일 입니다.");
private Supplier<String> supplier;
Day(Supplier<String> supplier){
this.supplier = supplier;
}
public String today(){
return supplier.get();
}
}
public class DayMessage {
private Day day;
public DayMessage(Day day) {
this.day = day;
}
public Day getDay(){
return this.day;
}
}
public class Today {
public String today(DayMessage dayMessage) {
Day day = dayMessage.getDay();
return day.today();
}
}
Today today = new Today();
DayMessage dayMessage = new DayMessage(Day.MON);
String result = today.today(dayMessage);
System.out.println(result);
- enum 타입에 따라 요일에 해당하는 문구를 출력해줍니다.
5) 예외
자바에서 예외라고 한다면, CheckedException(체크 예외)과 UncheckedException(언체크 예외)을 많이 생각하실텐데요. 저는 기존에 둘의 차이가 어느 시점에 나느냐에 따라 나뉜다고 생각하고 있었습니다.. (아주 잘못된 생각)
둘의 차이는 예외에 대한 처리를 강제하느냐 여부에 따라 나뉘게 되고 말 그대로 체크 예외는 예외처리를 체크한다! 라는 개념에서 예외를 반드시 처리해줘야 하며, 언체크 예외는 예외처리를 반드시 처리하지 않아도 된다.로 구분됩니다. 결론적으로 기존에 생각하던 컴파일 시점이냐 런타임 시점이냐는 잘못된 개념이였고, 예외는 모두 런타임 시점에 발생합니다.
예외에서 가장 중요한 것은 실제 사용시 체크예외 사용을 지양하고 언체크예외를 사용을 지향하는 것입니다.
why ?
1. throws로 처리할 경우
: 예외를 throws로 처리하게 되면, 결국 예외를 던지는 쪽 뿐만 아니라 해당 예외를 받는 부분에서도 예외처리를 진행해줘야 하기 때문에 예외에 의존하게 됩니다.
2. 복구가 불가능한 예외
: 대부분의 체크예외는 복구가 어렵다고 합니다. 그래서 예외를 던지거나 처리하려고 해도 처리할 수 없다는 문제가 있어요.
따라서 체크 예외는 가급적 사용하지 말고, 정말 예외처리가 필수적으로 필요한 상황에만 사용하면 됩니다. (ex. 계좌 처리 관련 문제 등)
정리
- 객체지향은 절차지향의 문제점을 보완하고자 나온 개념이다.
- 상속 시 리스코프 치환 원칙과 내적 동질성을 지켜야 비로소 객체지향 언어라고 할 수 있다.
- 추상 클래스를 꼭 사용해야 하는 상황이 아니라면 default 메소드를 사용하는 인터페이스를 사용하자.
- Enum(열거형)에 람다식을 적용하여 값에 따른 다양한 기능을 구현할 수 있다.
- CheckedException과 UncheckedException의 차이는 예외 처리를 강제하느냐에 달려 있다.
- 예외 처리시 UncheckedException을 사용하도록 하자.
마무리
TIL을 작성하면서 배웠던 내용들을 한번 더 정리하고, 응용해보는 시간을 가질 수 있었습니다. 정리를 해야 나중에 또 볼 수 있고 기억에도 잘 남으니까.. 앞으로도 꾸준히 작성해보겠습니다.