[패스트캠퍼스: Java&Spring 웹 개발] Week 3 - Java OOP Advanced

2023. 6. 14. 02:45Learn

728x90

본 게시글은 내일배움카드로 신청한 패스트캠퍼스의 국비지원교육 강의: Java&Spring 웹 개발입니다.

본 게시글은 패스트캠퍼스의 열공 챌린지 형식을 준수하며, 개인적인 정리 목적으로 학습 내용을 정리한 글입니다.

- 제목, 본문, 해쉬태그 키워드 포함

- 사진 1장 이상, 글자수 500자 이상 (공백 포함, 코드 제외)

https://gitlab.com/easyspubjava/javacoursework/-/tree/master/Chapter3

객체 간의 상속은 어떤 의미일까?

  • 코드 재사용만을 위한 개념은 아니다. 확장성(extends)이 더 중요하다.
  • 단순하고 안정적인 언어의 설계를 위해 잠재적 모호성(Ambiguity)을 배제하고자 다중 상속을 금지

상속을 활용한 멤버십 클래스 구현하기

  • 기본값을 생성자에서 정의해주는 편이 좋은 것 같다.
  • if-if else로 인해 조건문이 난무하는 상황에서는 클래스 생성을 고려해보자.
    기존 클래스가 새로운 조건을 충족하지 못할 때, 그 이유가 클래스의 본질적 한계라면 새로운 클래스로 확장이 필요하다.
  • 멤버 변수는 캡슐화가 필요하지만, 반드시 private 변수로 선언할 필요는 없다.
    상속이 이루어지는 관계와 사용 빈도를 고려하여 private / protected 중 선택하는 것이 좋다.

상속에서 클래스 생성 과정과 형 변환

상속 관계에서 하위 클래스의 생성은 상위 클래스의 생성을 선행한다.
이는 하위 클래스의 생성자에서 super 메소드를 통해 이루어진다.
하위 클래스의 생성자에서 명시적으로 super를 호출하지 않으면 기본 형태: super(); 명령이 자동적으로 삽입된다.
이 때, 상위 클래스에 기본 생성자가 정의되지 않았다면, 자동 삽입된 super() 명령을 실행할 수 없으므로 오류가 발생한다.
이를 해결하려면 1) 상위 클래스에 기본 생성자를 생성하거나, 2) 하위 클래스에서 상위 클래스에 존재하는 생성자를 참고하여 명시적으로 호출하면 된다.

교안 참조

? 그렇다면 상위 클래스의 private형 변수를 상속받는 하위 클래스를 통해 해킹할 수 있을까?
- 물론 private 변수도 모두 포함하여 생성되지만, 그렇다고 접근 지정자의 의미가 사라지는 것은 아님.
- 상기 설명은 자식 클래스가 부모를 참조할 수 있음을 설명할 뿐. 아래 사진 참조.

하위 클래스가 생성 후 업케스팅 된다면, 업캐스팅된 클래스가 갖는 멤버에 한해서만 접근이 가능하다.
그렇다고 생성 당시 하위 클래스의 정보(멤버)가 사라지는 것은 아니다.
즉, 다시 다운 캐스팅하면 원상태로 복구가 가능하다.

 

? 부모 클래스 P가 서로 다른 자식 클래스 C1, C2를 갖는 상황이다.
C1으로 생성한 인스턴스를 P로 업캐스팅하고, C2로 다운 캐스팅 할 수 있을까?
- 결론은 불가능하다. 최초 생성되는 시점에 결정된 상속 트리가 캐스팅 범위가 된다.
(다시 태어나지 않는 이상, 성격의 본질은 변하지 않는다.)

메서드 재정의 하기(overriding)

재정의: 메소드 원형이 부모와 완전히 동일한 형태로 작성되어야 한다.

오버라이딩할 메소드는 @Override Annotation을 활용하면, 문법적 오류에 대한 컴파일 에러 피드백을 받을 수 있다.
이러한 기능적 주석은 철자 오류 등의 상황을 방지하는데 유용하며, IDE를 활용하면 편리하게 삽입할 수 있다.

자식 클래스로 생성한 인스턴스를 부모 클래스로 업캐스팅했을 때,
일반적으로 부모 클래스에 정의된 멤버만 접근이 가능하다.
그 사실은 변하지 않지만, 만약 생성에 사용한 자식 클래스에서 메소드 오버라이딩을 했다면, 해당 메소드는 부모 클래스에서 호출하더라도 내용은 재정의된 상태로만 사용이 가능하다. (가상 메소드의 오버라이딩) 

C++에서는 가상 함수(메소드)를 사용하기 위해 virtual 키워드를 명시적으로 사용해야 하지만,
Java는 기본적으로 가상 메소드를 사용한다.

메서드 재정의와 가상 메서드 원리

메모리 영역: 코드, 데이터

기능은 인스턴스마다 다르지 않다.

코드에 정의된 함수(명)은 후에 참조값으로 변환되며, 이는 이름의 동일 여부와 관계없이 모두 unique한 값이다.

오버로딩은 기존 함수명에 dummy parameter를 붙여서 변형(variation)을 만들고, 결국 이 또한 unique하다.

다형성과 다형성을 사용하는 이유

ex. 전략(strategy), 템플릿 메소드 패턴 등
전체적인 알고리즘은 상위 클래스에서 구현하고, 세부적인 디테일만 하위 클래스로 분리한다.
구동에 있어서 상위 클래스에 적절한 하위 클래스를 매개변수로 전달하는 방식으로 상황에 맞게 사용이 가능하다.
이 때, 하위 클래스는 상위 클래스에 종속되어 변화가 생길 때, 핵심 알고리즘을 자주 수정해야 하는 경우가 생길 수 있다.
이런 경우에는 차라리 상위 클래스에서 절차만 정의하고, 하위 클래스는 해당 절차를 재정의하는 방식으로 설계하는 것이 좋다. 이러한 판단에 유의하면 다형성을 적극적으로 활용하여 효율적인 (확장성 및 유지/보수 편의성이 높은) 프로그램을 만들 수 있다.

 

상속의 깊이는 가능하면 얕게 유지하는 것이 좋다.

새로운 요구사항에 대해 기존의 코드를 수정하면, 수정된 내용에 대한 검증에 대한 소요가 매우 크다.
이를 고려한다면 기존 내용에서 확장하는 방식으로 개발하는 것의 편의성을 체감할 수 있다.

상속은 언제 사용 할까?

코드의 재사용

새로운 클래스를 생성해야할 때, 기존 클래스를 활용할 수 있을지를 고려해서 상속을 진행한다.

IS-A 관계 (inheritance)

HAS-A 관계 (composition)

Base Class, DRIVE Class

다운 캐스팅과 instanceof

상위 클래스로의 캐스팅: 업 캐스팅 - 묵시적으로 변환 가능

하지만 다운 캐스팅은 명시적으로 캐스팅해야함.

instanceof 키워드를 통해 최초 호출된 생성자를 확인할 수 있음.

추상 클래스의 의미와 구현하는 방법

abstract class != concrete class

method signature, declaration, definition

- 추상 클래스의 추상 메소드의 정의는 상속받는 자식 클래스에서 반드시 정의하여 사용한다. (하위 클래스에 책임을 위임한다.)

- 상기한 방식이 아닌 이외의 방식으로는 사용이 불가하다. (instance 생성 불가)

- 일반적으로 프레임워크가 이러한 추상화된 뼈대를 제공해줌.

abstract class (extends) vs interface (implements): 디자인 패턴 등에서 적극적으로 활용됨

추상 클래스를 활용한 템플릿 메서드 패턴

Framework != Library

JDK: Library (내부 기능을 활용할 뿐, 모든 것은 개발자가 정함)

Android: Framework (틀/흐름은 정해져 있고, 개발자는 내용만 정함)

템플릿을 변형시키면 설계한 흐름이 깨지기 때문에, 이와 같이 내용이 변경되어서는 안 되는 메소드는 final로 정의한다.

- 이 때, 요구사항이 변경되어 주어진 템플릿이 자주 변경되다보면, 상속받는 모든 하위 클래스를 모두 수정해야 한다.

- 예를 들어, 자동차의 경우, 비가 갑자기 오면 와이퍼를 동작해야 하는데, 이것은 운전과 동시에 이루어지는 작업이므로 강의에서 제시되는 절차적인 패턴은 맞지 않는다. 다소 복잡하지만, 자동차의 구성 요소를 정의하고, 각각의 요소에 대해 상태 변화에 따른 상호작용을 정의하는 것이 차후 확장성을 위해서는 더 알맞은 설계로 보인다. (** Strategy Pattern, State Pattern)

구현 코드가 없는 인터페이스(interface)

기본적으로 public abstract 메소드public static final 변수로 선언됨

추상 클래스 Italic체로 표현한다. interface는 상속(확장; extends)이 아니라 구현(implements)한다.

타입 상속: implements 인터페이스

구현 상속: extends 클래스

인터페이스는 왜 쓰는가?

연결점: 인터페이스, 제공 기능을 명시적으로 선언(명세)

Java에서 DB 연동을 위해서는 정의된 Connection Interface를 구현해야 한다.

이는 Oracle, MS-SQL, MySQL 등 DB 측에서 구현할 일이고,
개발자는 어떤 DB를 쓰는가에 상관없이 항상 동일하게 프로그래밍 할 수 있다.

인터페이스를 활용한 다형성 구현(dao 구현하기)

Strategy Pattern

DAO: Data Access Object

프로젝트 시작 시점에 소스 패키지 분리 기준을 세워두는 것이 유지/보수 측면에서 도움이 된다.

FileInputStream fis = new FileInputStream("db.properties");
Properties prop = new Properties();
prop.load(fis);
String dbType = prop.getProperty("DBTYPE");

** Properties 클래스는 Key, Value 형식의 파일을 쌍(Pair)으로 Parsing 해준다.

인터페이스의 여러가지 요소

인터페이스를 implements하는 여러 클래스에서 동일한 구현 작업이 반복되는 문제를 해결하고자,
Java 8  이후부터 인터페이스에도 구현이 기능을 제공하게 되었다.

1. default method

2. static method

3. (Java 9 이후) 1과 2의 구현을 지원하기 위한 private method 지원

여러 인터페이스 구현하기, 인터페이스의 상속

다중 상속으로 인한 모호성 문제 (diamond problem)

C++에서는 부모를 명시하는 것으로 다중 상속 문제를 해결하는 반면,
초기 Java는 안정성을 우선하여 이런 가능성 원천 배제함

서로 다른 인터페이스 A, B에서 동일한 메소드 foo()를 구현한 상태에서 클래스 C implements A, B하는 상황이라면,
모호성 해결을 위해 C에서는 반드시 foo()를 overriding 해야 한다. (A.foo 또는 B.foo 또는 자체 재정의)

** 이렇게 구현한 C의 인스턴스가 A 또는 B의 인터페이스로 업 캐스팅되어 foo를 호출하더라도, 가상 메소드의 원리에 따라 재정의된 C.foo만 호출하게 된다. 상황에 따라 다른 동작을 바란다면 매개변수를 활용하거나, 함수 구현부에서 instanceof 키워드를 사용해야 한다.

** C instance of A 또는 C instanceof B 모두 참이다.

class extends class, interface implements interface. BUT, class implements interface

복습해보세요

if문을 최소화하는 방식으로 설계 및 리팩토링 진행


마치며...

기존에 배웠지만 잊고 있었던 내용들을 복습하고, 일부 새로운 내용들을 배울 수 있던 좋은 기회였다.

또한 디자인 패턴을 익히는 과정은 OOP 관점에서 Java 전반을 제대로 이해하기 위한 계기가 될 것 같다.

싱글 스레딩에서의 설계는 익숙한 상황이라, 멀티 쓰레딩을 의도적으로 활용하는 연습을 해야겠다.

 

 

728x90