2023. 1. 26. 08:58ㆍ개발일지
객체란
- 우리가 실생활에서 쓰는 모든 것을 객체라고 합니다.
객체 지향 프로그래밍
- 객체들을 먼저 만들고 이것들을 하나씩 조립 및 연결해서 전체 프로그램을 완성하는 기법입니다.
추상화
- 공통의 속성이나 기능을 묶어서 이름을 붙이는 것을 의미합니다.
- 예를 들어서 토끼랑 거북이에 대해서 공통속성인 이름과 속도를 묶었고, 달리기라는 메서드를 정의하면
public abstract class Animal {
String name;
int speed;
Animal(String name, int speed){
this.name = name;
this.speed = speed;
}
abstract void run();
}
public class Turtle extends Animal {
Turtle(String name, int speed) {
super(name, speed);
}
@Override
void run() {
System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
}
}
public class Rabbit extends Animal {
Rabbit(String name, int speed) {
super(name, speed);
}
@Override
void run() {
System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
}
}
public static void main(String[] args) {
Rabbit rabbit = new Rabbit("토끼", 5);
Turtle turtle = new Turtle("거북이", 1);
rabbit.run();
turtle.run();
}
}
동물이라는 객체로 추상화해서 공통의 속성이나 메서드를 묶어서 이름을 붙여 놓았습니다. 속성들을 재정의 할 필요가 없어졌고 메서드도 통일감있게 작성할 수 있게 되었습니다.
(결론적으로 추상화를 하는 이유는 중복코드가 줄고 코드의 재사용성이 증가하기 때문에 추상화를 하는 목적이 됩니다.)
상속
- 상위(부모) 객체를 기반으로 하위(자식) 객체를 생성하는 관계를 말합니다.
- 상위 개념의 특징을 하위 개념이 물려받는 것이 상속이라고도 합니다.
- 위에 있는 코드로 비교를 하면
public class Rabbit extends Animal {
Rabbit(String name, int speed) {
super(name, speed);
}
@Override
void run() {
System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
}
}
Animal객체가 상위 개념이며 거북이와 토끼가 속성들을 물려 받은 것입니다. (extends)
캡슐화
- 실제로 구현되는 부분을 외부에 드러나지 않도록 캡슐로 감싸 이용방법만을 알려주는 것이며, 메서드(이용방법)만 접근 가능하고 속성값들은 접글할 수 없습니다.
- ex)를 들자면
public class Turtle extends Animal {
private int hungry = 10;
Turtle(String name, int speed) {
super(name, speed);
}
@Override
void run() {
System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
hungry(); // 달렸더니 허기가 지는군요.
}
public void hungry(){
hungry++;
System.out.println("열심히 달렸더니 허기가 지군요.");
}
public void eat(){
hungry--;
System.out.println(name+" 이(가) 밥을먹어서 배고픔 지수가 " +this.hungry+"가 되었습니다.");
}
public void status(){
System.out.println(name+"의 배고픔 지수는 " +this.hungry+" 입니다.");
}
}
public class Match {
public static void main(String[] args) {
Turtle turtle = new Turtle("거북이", 1);
turtle.status();
turtle.run();
turtle.status();
turtle.eat();
turtle.status();
}
}
//거북이의 배고픔 지수는 10 입니다.
//거북이는 1의 속도로 달린다.
//열심히 달렸더니 허기가 지군요.
//거북이의 배고픔 지수는 11 입니다.
//거북이 이(가) 밥을먹어서 배고픔 지수가 10가 되었습니다.
//거북이의 배고픔 지수는 10 입니다.
- 거북이의 배고픔은 거북이 스스로 관리하면 되는 것입니다. 다른 코드에서 거북이의 배고픔을 조절한다면 거북이 객체는 혼선이 올 것이며, 밥을 먹어야하는데 허기짐지수가 높아서 밥을 먹어야 하는 상황이 오지 않는 오류가 생깁니다.
- 정리를 해보자면 중심적인 구조적 프로그래밍 언어에서는 프로그램 내부에서 데이터가 어디서 어떻게 변경되는지 파악하기 어렵습니다 그로 인해 유지보수가 어려웠지만 객체지향에서는 캡슐화를 통해서 유지보수성이 향상된 것입니다.
Getter & Setter
- 클래스를 선언할 때 가능하다면 필드를 private로 선언해서 외부로부터 보호하고 필드에 대한 Setter/Getter 메서드를 작성해서 필드값을 안전하게 변경 / 사용하는 것이 좋습니다.
Setter
- 객체 지향 프로그래밍에서 객체의 데이터는 객체 외부에서 직접적으로 접근하는 것을 막습니다. 객체 데이터를 외부에서 읽고 변경 시 객체의 무결성이 깨질 수 있기 때문입니다. 따라서 객체 지향 프로그래밍에서는 메서드를 통해 데이터를 변경하는 방법을 선호합니다.
- 데이터는 외부에서 접근하지 않도록 막고, 메서드는 공개해서 외부에서 메서드를 통해 데이터에 접근하도록 유도합니다.(메서드는 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있기 때문입니다.)
Getter
- 외부에서 객체의 데이터를 읽을 때도 메서드를 사용하는 것이 좋습니다. 객체 외부에서 객체 필드값을 사용하기 부적절한 경우가 있으며, 이런 경우 메서드로 필드값을 가공 후, 외부로 전달합니다.
Getter & Setter 예시
private 타입 test; // 필드 접근 제한자 : private
// Getter
public 리턴타입 getTest(){
return test;
}
// Setter
public void SetTest(타입 test){
this.test = test;
}
Getter : getTest()
- 접근 제한자 : public
- 리턴 타입 : 필드의 리턴 타입
- 메서드 이름 : get + 필드 이름 (첫문자는 대문자)
- 리턴값 : 필드값
===========================================================
Setter : setTest(타입 test)
- 접근 제한자 : public
- 리턴 타입 : void
- 메서드 이름 : set + 필드 이름 (첫문자는 대문자)
- 매개 변수 타입 :필드 타입
(만약 필드 타입이 boolean일 경우, Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례입니다.)
private boolean stop; // 필드 접근 제한자 : private
// Getter
public boolean isStop() {
return stop;
}
// Setter
public void setStop(boolean stop) {
this.stop = stop;
}
Getter : isStop()
- 접근 제한자 : public
- 리턴 타입 : 필드 타입
- 메서드 이름 : is + 필드이름 (첫문자는 대문자)
- 리턴값 : 필드값
===========================================================
Setter : setStop(boolean stop)
- 접근 제한자 : public
- 리턴 타입 : void
- 메서드 이름 : is + 필드이름 (첫문자는 대문자)
- 매개 변수 타입 : 필드 타입
- 외부에서 필드값을 읽을 수만 있고, 변경하지 못하도록 하려면(읽기 전용) Getter 메서드만 선언하거나, Setter 메서드를 private 접근 제한을 갖도록 선언합니다.
클래스와 인스턴스
클래스
- 객체를 생성하기 위한 필드와 메서드가 정의된 것입니다.
- 간단한 객체를 만들기 위한 틀 또는 설계도라고 생각하면 쉽습니다.
인스턴스
- 클래스로부터 만들어진 객체입니다.
- 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체입니다.
다형성
- 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말하며, 코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입합으로써 다양한 기능을 이용할 수 있도록 해줍니다.
- 부모-자식 상속 관계에 있는 클래스에서 상위 클래스가 동일한 메시지로 하위 클래스들을 서로 다르게 동작시키는 객체 지향 원리입니다.
- 다향성을 활용하면 부모클래스가 자식클래스의 동작 방식을 알수없어도 오버라이딩을 통해 자식 클래스를 접근 할 수 있습니다.
다형성 장점
- 유지보수가 쉽다
- 개발자가 여러 객체를 하나의 타입으로 관리가 가능하기 때문에 코드 관리가 편리해 유지보수가 용이합니다.
- 재사용성 증가
- 다형성을 활용하면 객체를 재사용하기 쉬워지기 때문에 개발자의 코드 재사용성이 높아집니다.
- 느슨한 결합
- 다형성을 활용하면 클래스간 의존성이 줄어들며 확장성이 높고 결합도가 낮아져 안전성이 높아집니다.
다형성 단점
- 처리 속도가 상대적으로 느립니다.
- 객체가 많으면 용량이 커질 수 있습니다.
- 설계시 많은 시간과 노력이 필요합니다.
다형성 필수 조건
- 상속 관계
- 다형성을 활용하기 위해서는 필수로 부모-자식 간 클래스 상속이 이루어져야 합니다.
- 오버라이딩 필수 (자식 클래스에서 메서드 재정의)
- 다형성이 보장되기 위해서는 하위 클래스 메서드가 반드시 재정의되어 있어야 합니다.
- 업캐스팅 (자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것)
- 부모 타입으로 자식클래스를 업캐스팅하여 객체를 생성해야 합니다.
다형성 구현 방법
- 상속 클래스 구현 (부모 - 자식 클래스 구현)
- 메서드 오버라이딩
- 업캐스팅하여 객체 선언
- 부모 클래스 객체로 자식 메서드 호출
다형성 예시
- 상속 클래스 구현 (부모 - 자식 클래스 구현)
- 부모 자식간 상속 클래스를 구현합니다.
- 부모 클래스는 Book 클래스 이며 자식 클래스는 Novel(소설) 클래스입니다.
- 디폴트 생성자 외 인수 있는 생성자를 추가해 생성자를 중복 정의합니다.
class Book{
public String name;
public String publisher;
Book(){
this.name = "";
this.publisher = "";
}
Book(String name, String publisher){
this.name = name;
this.publisher = publisher;
}
void print(){
System.out.println("print : Book");
};
}
- 메서드 오버라이딩
- 자식 클래스인 Novel, SF 클래스에 부모 메서드를 오버라이딩하여 재정의합니다.
class Novel extends Book{
public String name;
public String publisher;
Novel(String name, String publisher){
super(name, publisher);
}
@Override
void print(){
System.out.println("print : Novel");
}
}
class SF extends Book{
public String name;
public String publisher;
SF(String name, String publisher){
super(name, publisher);
}
@Override
void print(){
System.out.println("print : SF");
}
}
- 업캐스팅하여 객체 선언
- 업캐스팅 (자식 클래스 객체를 부모 클래스로 형변환)하여 아래와 같이 객체를 선언합니다.
Book b = new Novel("메타버스 소설","출판사(IT)");
Book c = new SF("메타버스", "SF출판사");
- 부모 클래스 객체로 자식 메서드 호출
- 부모 클래스 Book으로 생성된 객체의 멤버 함수를 선언합니다.
Book b = new Novel("소설","소설출판사");
b.print();
Book c = new SF("메타버스", "SF출판사");
c.print();
- 결과적으로 위와 같이 다형성을 활용하면 부모 클래스로 객체를 선언했으나 실행시점에 동적 바인딩되어 자식클래스의 멤버함수가 호출되는 것을 볼 수 있습니다.
print : Novel
print : SF
- 객체 타입 확인 : instanceof
- instanceof는 객체 타입을 확인하는 연산자로, 객체의 실제 타입을 알아보기 위한 연산자입니다.
- 아래와 같이 "객체 instanceof 타입"과 같이 사용합니다.
객체 instanceof 타입
- instanceof 연산자는 해당 객체가 타입이 맞다면 true, 아니면 false를 반환합니다.
if(c instanceof SF){
c.print(); //true
}
if(b instanceof Novel){
b.print(); //true