🍫카카오 테크 캠퍼스 2기 BE

[카카오 테크 캠퍼스] 1단계 4주차 학습 일지

pkyung 2024. 5. 12. 23:37
반응형

 

 

 

 

📚학습 내용

객체 지향 프로그래밍과 객체 지향 설계에  대한 강의를 모두 수강했습니다. 상속, override, 다형성 활용, 추상 클래스와 인터페이스에 대한 강의였습니다. 이를 통해 알고 있던 내용을 한 번 더 확실하게 정리할 수 있는 시간이 되었습니다. 

 

그리고 피드백 폼을 활용하여 잘 설계된 객체 지향은 어떤 것인지에 대한 질문을 드렸습니다. 이번 주에는 미니 과제가 나왔고, 이를 설계하다보니 내가 하고 있는 게 맞는건지, 어떤 식으로 짜야하는지 고민이 되었기 때문입니다.

 

제가 이해한 바로는 객체는 현실 세계와 같이 행위를 행하는 것(유저, 컴퓨터 등)으로 분리하여 만든 뒤, 이 객체가 갖고 있는 역할과 책임에 대해 정의합니다. 그리고 정의한 기능에 대해 이 객체들이 어떻게 협력할 것인지 설계합니다. 이를 더 잘 이해하기 위해서는 객체지향의 사실과 오해 책을 읽어보아야 알 것 같습니다. 

 

 

 

객체 지향 프로그래밍 - static과 jvm 메모리 모델

 

객체 생성과 static과의 관계

어떤 클래스의 모든 멤버가 static 멤버이면서 기본 생성자가 private인 경우

  • 인스턴스는 생성할 수 없음
  • static 접근 방법으로 접근하면 됨
  • 자바 API에서 private 생성자를 갖고 있는 클래스가 있음
    • 예) System, Math
  • 생성자가 반드시 public인 것은 잘못된 것

 

 

class, object, instance의 상호관계

Class

  • 객체를 모델링하는 도구 (설계도)
public class Student {
	private String name;
	private String dept;
	private int age;
	private String email;
}

 

Object

  • 클래스를 통해서 선언되는 변수
  • 변수가 구체적인 대상을 가리키지 않은 상태 (객체 변수)
  • 객체가 서로 구분이 되지 않은 시점
Student st

 

Instance

  • 객체 생성에 의해 메모리(heap)에 만들어진 객체를 인스턴스라고 한다.
  • 객체가 구체적인 실체를 가리키는 상태 (인스턴스 변수)
  • 객체가 서로 구분이 되는 시점
st = new Student();

 

 

 

 

 

객체 지향 설계 - 자바에서 상속이란

 

수평적 구조와 수직적 구조의 설계 방법

상속 : 부모가 자신의 것을 사용하라고 허락한다면 자식은 부모의 것을 얼마든지 사용 가능하다.

 

회사의 사원 클래스를 설계 (수평적 구조)한다고 가정하자.

비슷한 클래스이다보니 중복적인 요소(이름, 나이, 전화번호, 주소, 입사일, 근무부서)가 발생한다. 

 

회사의 사원 클래스를 설계 (수직적 구조)한다고 가정하자.

사원 : 이름, 나이, 전화번호, 주소, 입사일, 근무부서

일반 사원, 관리 사원, 비서, 일용직은 사원을 상속 받고 본인의 필드를 추가함

 

수직적 구조 = 계층화 = 상속 = 클래스와 클래스의 관계 설계

 

 

클래스를 계층화 해서 사용할 때 장점

클래스를 계층화 하는 것을 상속이라고 함

  • 코드 중복을 최소화
  • 새로운 요구사항 발생 시 반영이 쉬움
  • 확장성이 좋아짐 vs 코드가 복잡해짐

 

 

메모리를 통한 상속의 이해

protected : 상속 관계에서 하위 클래스가 상위 클래스이 접근을 허용하는 접근 권한

public class Employee {
	protected Stringname;
	protected int age;
	protected String phone;
	protected Strign empDate;
	protected String dept;
	protected boolean marriage;
	public Employee() {
		super(); // Object 생성자 호출
	}
}
public class RempVO extends Employee {
	public RempVO() {
    	    super(); // 부모인 Employee() 생성자 호출
	}
}

 

상속 관계에서 객체 생성

  • 부모부터 객체가 메모리에 생성된 뒤에 자식이 생성되어야 함 → super() 로 부모 생성자 실행함
  • 자식이 부모 상태 정보를 마음대로 접근할 수 있음은 정보 은닉에 위배됨
RempVO vo = new RempVO();
public class EmployeeTest {
	public static void main(String[] args) {
		RempVo vo = new RempVO(); // 부모 생성자 실행 -> 부모는 Object 생성자 실행 
		vo.name = "홍길동";
		vo.age = 12;
		vo.phone = "010-1234-1234";
	}
}

 

 

상속 관계에서 객체 생성

생성자를 이용해서 객체 생성

public class RempVO extends Employee {
	public RempVO() {
		super();
	}
	public RempVO(String name, int age, String phone, String empDate, String dept, boolean marriage) {
		this.name = name;
		this.age = age;
		this.phone = phone;
		this.dept = dept;
		this.marriage = marriage;
	}
}

 

초기화는 해당하는 객체 자신이 하는 것이 바람직하다. → 부모의 접근 제어자는 protected보다는 private가 더 안전하다. (정보 은닉 관점에서)

 

 

상속 관계에서 객체 초기화

부모의 필드는 private로 지정함

public class RempVO extends Employee {
	public RempVO(String name, int age, String phone, String empDate, String dept, boolean marriage) {
		super(name, age, phone, empDate, dept, marriage);
	}
}

 

부모의 생성자에서 초기화 하는 것이 바람직 하다.

public Employee(String name, int age, String phone, String empDate, String dept, boolean marriage) {
	this.name = name;
	this.age = age;
	this.phone = phone;
	this.empDate = empDate;
	this.dept = dept;
	this.marriage = marriage;
}

 

 

 

 

 

객체 지향 설계 - 상속 관계에서 객체 생성 및 override

 

지금까지는 데이터의 상태 정보에 초점을 맞추어 클래스 위조로 데이터를 다뤘다면 이제는 클래스의 행위 정보를 초점에 맞춰서 클래스와 클래스를 설계하는 방법에 대해 학습함

클래스를 동작하는 측면에서 상속을 바라보자 (수평적 구조 설계)

  • 중복되는 메서드 존재함
public class Dog {
	public void eat() {
		System.out.println("개처럼 먹다");
	}
}

public class Cat {
	public void eat() {
		System.out.println("고양이처럼 먹다");
	}
	
	public void night() {
		System.out.println("밤에 눈에서 빛이 난다.");
	}
}

Dog d = new Dog();
d.eat();

Cat c = new Cat();
c.eat();

 

 

클래스를 동작하는 측면에서 상속을 바라보자 (수직적 구조 설계)

public class Animal {
	public void eat() {
		System.out.println("동물처럼 먹기");
	}
}

 

Dog 클래스가 Animal 클래스를 상속해서 eat() 메서드를 사용했지만 사용자가 원하는 출력을 하지 못했다.

 

 

상속 관계에서 객체 생성 방법

upcasting : 부모가 자식을 가리키는 객체 생성 방법

Dog x = new Dog();
x.eat();

Animal x = new Dog();
x.eat();

Animal y = new Cat();
y.eat();

 

 

 

상속 체이닝과 super

상속 체이닝 : 맨 위 부모 클래스부터 객체가 생성되어 자식까지 연결되는 구조

super() : 상위 클래스의 생성자를 호출하는 메서드

  • 생성자 메서드의 가장 첫 문장에 사용해야 함
    • 왜냐하면 자식 객체가 만들어지기 전에 부모 객체가 만들어져야 하므로 자식 객체의 수행문을 먼저 실행할 수 없음
  • 상위 클래스의 기본 상성자를 호출하는 super()는 생략되어 있음

부모가 갖고 있는 eat() 를 자식이 재정의를 한다. → 오버라이드

 

 

메서드의 재정의란

메서드의 재정의(Override)가 필요한 이유

: Dog에서의 eat과 Cat에서의 eat 메서드의 수행이 다르기 때문이다.

  • 상속 관계에서 하위 클래스가 상위 클래스의 동작을 재정의 하는 행위 (기능 추가, 변경)
public class Animal {
	public void eat() {
		System.out.println("?");
	}
}

public Dog extends Animal {
	public void eat() {
		System.out.println("개처럼 먹다");
	}
}

 

 

동적 바인딩 : 실행 시점에서 사용될 메서드가 결정되는 바인딩

public class Dog extends Animal {
	public Dog() {
		super();
	}
	
	public void eat() {
		System.out.println("개처럼 먹다");
	}
}

public class Cat extends Animal {
	public Cat() {
		super();
	}
	
	public void eat() {
		System.out.println("고양이처럼 먹다");
	}
	
	public void night() {
		System.out.println("밤에 눈에서 빛이 난다.");
	}
}
Animal ani = new Dog();
ani.eat(); // Animal -> Dog 동적 바인딩

ani = new Cat();
ani.eat(); // Animal -> Cat 동적 바인딩

 

 

 

 

객체 지향 설계 - 객체 형 변환

 

부모와 자식 간에 형 변환이 된다

  • 부모는 여러 명의 자식을 가리킬 수 있음
  • 부모를 알면 자식을 관리하기 쉬움

💡 UpCasting💡

부모 p = new 자식A();

부모 p = new 자식B(); 

 

💡 DownCasting 💡

자식 A c = (자식A) 부모;

자식 B c = (자식B) 부모;

 

Animal ani = new Dog();
ani.eat();

ani = new Cat();
ani.eat();
ani.night(); // Animal에는 night 메서드가 없어서 오류

 

 

 

Upcasting과 Downcasting

Downcasting : 상위 클래스 타입을 하위 클래스의 타입으로 바꾸는 행위

Animal x = new Dog();
x.eat();

Animal x = new Cat();
x.night(); // 오류

Cat c = (Cat) x; // Downcasting
c.night();
((Cat)ani).night();

 

 

 

객체 지향 설계 - 다형성과 활용

다형성 : A가 B, C, D 클래스에게 x() 메서드를 실행하라는 메시지를 보내면 B, C, D 클래스가 동작은 한다. 그러나 동작 방식이 다르다. (Message Polymorphism)

 

다형성이란 무엇인가

Animal x = new Dog();
x.eat();

x = new Cat();
x.eat() // 실행 시점에서 호출될 메서드가 결정되는 바인딩 -> 동적 바인딩

((Cat)ani).night() // Downcasting

 

 

다형성 이론을 설명하기 위한 전제 조건

  1. 상속 관계
  2. Override (재정의)
  3. Upcasting (업캐스팅)
  4. 동적 바인딩

💡 Overloading (오버로딩) => 정적 바인딩 : 컴파일 시점에서 사용될 메서드가 결정되는 바인딩 => 프로그램 실행 속도와 무관

💡 Override (오버라이드) => 동적 바인딩 : 실행 시점에서 호출될 메서드가 결정되는 바인딩 => 프로그램의 실행 속도와 관계

 

 

다형성 활용 (다형성 인수)

다형성 인수 : 하나의 타입으로 여러 가지 타입을 받을 수 있음 (부모이기 때문에)

다형성 배열로도 활용 가능하다. 

public class PolyMethodTest {
	public static void main(String[] args) {
		Dog d = new Dog();
		display(d);
		Cat c = new Cat();
		display(c);
	}
	
	public static void display(Dog d) {
		d.eat();
	}
	
	public static void display(Cat c) {
		c.eat();
	}
}
public static void display(Animal ani) {
	ani.eat();
	// ani.night(); 는 오류임 -> Cat 타입인지 확인한 후 실행해야함
	
	if (ani instanceof Cat) {
		((Cat) ani).night();
	}	
}

 

 

 

 

객체 지향 설계 - 추상 클래스와 인터페이스의 등장

 

다형성을 보장한다는 것

: 부모가 명령을 내리면 자식이 반드시 동작(반응)을 해야 한다.

  • 다형성 전제조건 4가지
  • 반드지 메서드 재정의
  • 부모 클래스를 추상 클래스로 만듦

추상 클래스 : 불완전한 클래스로 구현 클래스에서 메서드를 오버라이딩 해줘야 한다. → 추상 메서드 (메서드의 구현부가 없음)

→ 객체 생성이 불가능함

 

public abstract class Animal {
	public abstract void eat();
}

public class Dog extends Animal {
	public void eat() {
		System.out.println("개처럼 먹다");
	}
}

public class Cat extends Animal {
	public void eat() {
		System.out.println("고양이처럼 먹다");
	}
}

 

 

추상 클래스와 다형성

추상 클래스란?

  • 다형성을 일부 보장하기 위해서 등작
  • 서로 비슷한 클래스의 공통 부분을 묶을 때 사용
  • 단독으로 객체를 생성할 수 없다.
  • 구현된 메서드를 가질 수 있다. → 이 부분은 다형성 보장이 안됨
public abstract class Animal {
	
	// 추상 메서드
	public abstract void eat();
	
	// 구현 메서드
	public void move() {
		System.out.println("움직인다");
	}
}

 

 

인터페이스와 다형성

인터페이스란?

  • 다형성을 100% 보장하기 위해서 등장
  • 서로 다른 클래스의 공통 부분을 묶을 때 사용
  • 단독으로 객체를 생성할 수 없음
  • 부모의 역할로 사용함
  • 추상 메서드와 final static 상수만 올 수 있음
  • 구현된 메서드를 가질 수 없음
public interface RemoCon {

	public static final int MAXCH = 99; // 바꿀 수 없는 상수
	public static final int MINCH = 1; // 바꿀 수 없는 상수
	
	public void chUp();
	public void chDown();
	public void volUp();
	public void volDown();
	public void internet();
}

 

 

 

추상 클래스와 인터페이스 비교

공통점

  • 다형성을 보장하기 위해 등장된 개념
  • 추상 메서드를 가질 수 있음
  • 단독으로 객체를 생성할 수 없음
  • 부모의 역할로 사용함

차이점

  • 서로 비슷한 클래스의 공통 부분을 묶을 때 사용 : 추상 클래스
  • 서로 다른 클래스의 공통 부분을 묶을 때 사용 : 인터페이스 → 추상 메서드와 final static 변수를 가질 수 있음

 

 

 

객체 지향 설계 - 자바 최상위 클래스 Object

 

Object 클래스를 이용해서 객체 생성

어떤 클래스 한 개를 만들면 기본적으로 생략된 부분이 있음

public class A {

}

import java.lang.*;
public class A extends Object {
	public A() {
		super();
	}
	
	public void display() {
		System.out.println("하이");
	}
}

 

A 클래스를 사용하기 위한 객체 생성 2가지 방법

A a = new A();
Object obj = new A(); // Upcasting
((A) obj).display(); // Downcasting

 

 

Object 클래스를 활용한 다형성 적용

public class A {
	public void printGo() {
		System.out.println("나는 A");
	}
}

public class B {
	public void printGo() {
		System.out.println("나는 B");
	}
}
Object obj1 = new A();
Object obj2 = new B();

 

 

 

 

 

🎓소감

객체 지향의 주 목적을 정리하면 코드의 중복을 없애고 유지 보수를 용이하게 한다는 것임을 배웠습니다.

그리고 강의를 통해 미니 과제의 일부분에 대한 해답을 얻을 수 있었습니다. 제가 작성한 코드 중 숫자 야구 게임의 입력 받는 수가 세 자리 수여서 반복문을 돌릴 때, 상수 3을 하드 코딩 했는데 설계를 잘 해서 사용하면 static final 상수 선언을 통해 가독성을 높일 수 있을 것 같았습니다.

 

의문이 있다면 다형성을 확실하게 보장하는 인터페이스가 있음에도 구현 메서드 작성이 가능해서 다형성이 깨질 수 있는 추상 클래스를 사용하는 이유는 무엇인가요?

차이점이 공통 부분을 묶는 추상 클래스와 다른 부분을 묶는 인터페이스라고 했는데 이 점의 예시 또한 궁금합니다.

 

이 또한 멘토님께 질문을 드린 것인데 피드백 폼에 대한 답변만 와서 gpt 에게 물어보았습니다. 

 

반응형