객체 지향 프로그래밍 (Object Oriented Programing) 이란?

프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법

 

- 장점

코드 재사용이 용이

남이 만든 클래스를 가져와서 이용할 수 있고 상속을 통해 확장해서 사용할 수 있다.

유지보수가 쉬움

절차 지향 프로그래밍에서는 코드를 수정해야할 때 일일이 찾아 수정해야하는 반면 객체 지향 프로그래밍에서는 수정해야 할 부분이 클래스 내부에 멤버 변수혹은 메서드로 존재하기 때문에 해당 부분만 수정하면 된다. 

대형 프로젝트에 적합

클래스 단위로 모듈화시켜서 개발할 수 있으므로 대형 프로젝트처럼 여러 명, 여러 회사에서 프로젝트를 개발할 때 업무 분담하기 쉽다.

 

- 단점

처리 속도가 상대적으로 느림

객체가 많으면 용량이 커질 수 있음

설계시 많은 시간과 노력이 필요

 

OOP의 4가지 특징

1. 캡슐화

2. 추상화

3. 다형성

4. 상속

 

오버라이딩 : 부모클래스의 메서드와 같은 이름, 매개변수를 재정의 하는것.

오버로딩 : 같은 이름의 함수를 여러개 정의하고, 매개변수의 타입과 개수를 다르게 하여 매개변수에 따라 다르게 호출할 수 있게 하는 것.


1. 캡슐화

원래의 목적은 객체의 속성(data fields)과 행위(methods)를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉한다.

즉, 사용하려는 메서드에 접근제어자를 활용하여 기존의 데이터에 손상이 가지 않도록(접근 못하도록) 하는것으로 주요목적은

1. 메서드를 통한 간접접근이 가능하도록 하는것

2. 내부적으로만 사용되는 부분을 감추기 위하여 사용

이것을 조금더 상세하게 설명하자면 

//접근 가능 코드
public class Time{
	public int hour = 12;		//0~23
	public int minute = 35;		//0~60
	public int second = 27;		//0~60
    //출력시 12:35:27
}

public class TimeTest{
	Time t = new Time(); 		//생성
	t.hour = 50;			//Time 맴버변수에 직접 접근
	System.out.println(t.hour);	//출력시 50:35:27이 출력된다...
	}
}

한가지 예시로 실시간 반영 서비스를 위한 코드를 작성하고 있다고 가정했을때 public으로 구현된 hour에 직접 접근하여 50을 넣는다면 코드상으론 문제가 없지만 실제 서비스를 구현한다고 했을때 사용자는 이로인해 여러 애로사항이 발생할것이다

 

여기서 발생하는 문제점은

문제점1. 접근제어자가 public이라 데이터의 원형이 바뀔 우려가 있다.

문제점2. 시간,분,초가 기준범위를 넘어선 값이 입력될 우려가 있다(현재시간 50시입니다..)

//수정한 코드

public class Time2{				//접근제어자 변경으로 문제점1 해결
	private int hour = 12;			//0~23
	private int minute = 35;		//0~60
	private int second = 27;		//0~60
    //출력시 12:35:27

    public int getHour() {return hour;}		//다른 메서드에서 호출은 가능해야 하기 때문에 public으로 선언
    public void setHour() (int hour) {		//조건문을 활용해 문제점 2 해결
	if (hour < 0 || hour >23) return;	
        this.hour = hour;
	}
}

public class TimeTest {
	public static void main(String[] args){
    	Time t - new Time();
        t.setHour (21);
        System.out.println(t.getHour));
	}	
}

이것이 캡슐화가 진행되도록 수정된 코드이다.

 

문제점1은 public에서 private로 접근제어자를 변경하여 데이터의 원형을 참조할수 없도록 하였다 

문제점2는 조건문을 활용하여 정해진 범위만 들어가도록 하였다

추가로 hour은 private로 선언하여 외부에선 접근이 불가능 한데 이를 활용하기 위하여 public을 사용하여 외부의 다른 클래스에서 사용이 가능하도록 하였다.

 

객체의 속성과 행위를 하나로 묶고 -> 시간과 관련된 객체, 행위(유효한지 확인)를 한곳에("Time2"메서드) 묶고

실체 구현 내용 일부를 외부에 감추어 은닉한다 -> private를 활용하여 외부에서 접근할수 없고 참조하는 형식으로만 진행된다.

 

접근제어자

public - 외부 클래스가 자유롭게 사용가능

protected - 같은 패키지, 자식클래스(상속)만 사용가능

default - 같은 패키지에 소속된 클래스만 사용가능

private - 외부에서 사용 불가(캡슐화!)

 


2. 추상화(Abstraciton)

공통된 속성, 기능을 묶어 이름을 붙이는것

 

좀더 자세히 설명하기 위해 코드를 작성하기로 하자

//추상클래스
abstract class Player{		//(A)
    abstract void Unit_attack (Sting Unitname);	//공격
    abstract void Unit_Idle();			//대기
    abstract void Unit_Run(Sting Unitname);	//후퇴
    
}

상단의 코드는 3개의 메서드가 있지만 어떠한 기능을 하는것이 아닌 그저 선언만 되있는 반쪽짜리 메서드이다.

선언만 해놨기에 작동하지는 않고 그저 사람이 어떤기능을 하겠거니 하고 추측할수 있는 추상화된 메서드를 작성한것일 뿐이다. (이하 추상메서드) 라고 칭한다.

그리고 이것을 실제로 사용하는 코드를 작성하였다.

public class Main extends Player{
	
    public static void main(String[] args){
    	String Player_name = "marine";
    	Main main = new Main();    
    	main.Unit_attack("Zergling");
    	main.Unit_Idle();
        main.Unit_Run("Zergling");
    }

    @Override
    void Unit_attack (Sting Unitname){	//공격
    System.out.println(Player_name+"이 "+ Unitname + "을 공격합니다"); 
    }
    
    @Override
    void Unit_Idle(){			//대기
    System.out.println(Player_name+"이 대기중입니다"); 
    }
    
    @Override
    void Unit_Run(Sting Unitname){	//후퇴
    System.out.println(Player_name+"이 "+ Unitname + " 에게서 달아납니다."); 
    
    }

상단의 코드는 추상메서드의 선언에 지나지 않은것을 Override하여 구체화한 코드이다.

공통된 속성, 기능을 묶어(플레이어가 갖고있는 기본적인 기능)이름을 붙이는것

 

추상화를 사용하는 이유가 무엇이냐?

설계자가 특정 메서드를 각 클래스 별로 재 구현을 원하지만 부모 클래스에서 일반 메서드로 구현하면 자식 클래스에서 구현을 하지 않는 경우가 발생할 수 있다. 이런 메서드를 추상 메서드로 선언하면 자식 클래스는 재구현을 강요받는다.

조금더 상세히 설명하자면 마린과 탱크가 있다고 가정을 해보자. 2개의 유닛은 기본적인 기능(A)외에 추가적인 기능(ex:스팀팩, 시즈모드) 등이 있을때 해당 기능을 넣지 않고 개발해버린다면 큰 오류이다. (시즈모드 못하는 탱크...)

이것을 추상메서드로 선언해놓고 구체화를 진행할경우 구체화된 메서드에서 빠진부분이 없는지 컴퓨터가 점검해주는것 + Override를 통해 부가기능을 추가하는것이 장점이다.


3. 상속(Inheritance)

 

부모클래스의 속성과 기능을 그대로 이어받아 사용할 수 있게하고 기능의 일부분을 변경해야 할 경우 상속받은 자식클래스에서 해당 기능만 추가로 작성하여 사용하면 되는것이다.

하단의 코드를 참조하길 바란다.

//부모클래스
class Tv{
    boolean power;
    boolean now_touch;
    
}

class Phone{
    boolean power;
    boolean now_touch;
}

class nintendo{
    boolean power;
    boolean now_touch;

}

간단한 예시로 TV, 핸드폰, 닌텐도 3기기 모두 공통적으로 power,touch 맴버를 선언하였다.

상단의 코드는 3개밖에 안되지만 예를들어 스타 유닛마다 체력,공격력,방어력을 저장할 변수를 만든다고 생각해봐라..... 굉장히 번거로운 작업일것이다.

이러한 문제점을 없애주는 기능이 상속이다.

쉽게 말해서 공통적으로 사용되는것을 만들어놓고(부모클래스) 필요할때마다 가져와서(상속해서) 사용하는것

//자식클래스 상속 예시
//class 자식클래스 extends 부모클래스{...}

//총 맴버는 power,now_touch,Tv_Channel 3개
class Tv_attribute extends Tv{		
    int Tv_Channel;
}

//총 맴버는 power,now_touch,app_name 3개
class Phone_attribute extends Phone{	
    string app_name;
}

//총 맴버는 power,now_touch,game_name 3개
class Nintendo_attribute extends Nintendo{	
    string game_name;
}

//총 맴버는 power,now_touch,game_name,stage_level 4개
class Nintendo_play_gmae extends Nintendo_attribute{	
    int stage_level;

}

상속의 특징

1. 자식클래스는 부모의 생성자, 초기화 블럭을 제외한 모든 맴버를 상속받는다. (부모가 아닌 부부모의 맴버도 상속이 된다)

2. 자바에서는 다중상속이 불가능 하다.(extends Nintendo,Phone 등은 불가능하다 -> 이것은 자바의 일반클래스가 부모 클래스를 1개만 가져야 하기 때문인데 클래스가 아닌 인터페이스를 이용한다면 다중 상속이 가능하긴 하다)

3. 접근제어자가 private을 갖는 필드나 메소드는 상속이 불가하고, 패키지가 다를 경우 접근제어자가 default인 경우도 상속이 불가하다.(접근제어자에 대한 내용은 별도 포스팅 예정)

4. 접근제한을 더 강하게 오버라이딩 할수없다.

5. 부모의 메서드와 동일한 시그너처(동일한 리턴타입, 동일한 메소드이름, 동일한 매개변수 리스트)를 가져야한다.

6. 상속으로 부모클래스의 메서드를 오버라이딩 해버린경우 부모클래스의 메서드는 숨겨지고 오버라이딩된 자식메서드만 사용되며 부모클래스의 메서드를 호출하려면 super키워드로 호출할수 있다..(만일 그렇지 않으면 ambiguous 오류 발생)

 


4. 다형성(polymorphism)

여러가지 형태를가질수 있는 능력 -> 가장 범용적인 예시로 오버로딩, 오버라이딩, 함수형 인터페이스 3가지 종류이다.

다형성을 한줄로 요약하자면 "조상타입 참조변수로 자손타입 객체를 다루는것"이다.

 

하단의 예시를 참조하길 바란다.

//다형성 설명용 코드
class Controller {	//인스턴스 5개 (power,channel,power(),channelUp(),channelDown())
    boolean power;
    int channel;

    void power()	{ power =!power;}
    void channelUp()	{ ++channel;	}
    void channelDown()  { --channel;	}

}

class SmartTv extends Controller{	//부모(Controller)가 자식(smarTv)
    String text;			//인스턴스 2개 (text,caption())
    void caption() {...}

}

// 기존 생성자를 활용한 코드
// 앞뒤의 타입이 일치함을 확인할수 있다.
Controller TvController = new Controller();
SmartTv STV = new SmartTv();	//(A)

//타입의 불일치 - 다형성 (B)
Controller TvController2 = new SmartTv();

 

Controller 타입(참조타입) TvController 변수는 Controller객체를 생성자로 받고

SmartTv 타입(참조타입) STV변수는 SmartTv생성자로 받고있다.

 

다형성이 적용된 코드(B)를 확인하면

Controller타입 TvController2 변수에 SmartTv를 생성자로 받고있는것이다.

두 코드의 차이점은 타입이 불일치 한다는것을 확인할 수 있는데 이것을 조금더 자세히 설명해보겠다.

 

상단에서 다형성이란 조상타입참조변수(Controller)가 자손타입 객체를 다루는것(SmartTv, TvController)이라고 설명을 해놨는데 일단 타입이 일치할때(A)와 일치하지 않을경우(B)의 차이부터 알아보자

 

(A)는 동일한 타입을 생성하기에(SmartTv-SmartTv) (A)가 사용가능한 맴버의 숫자는 7개이다. (Controller + SmartTv)

(B)의 타입이 다르기 때문에 맴버는 7개이나 사용가능한 맴버는 5개이다. (Controller)

 

그러면 제약이 더 많은것 같은데 다형성의 장점이 무엇이냐?

1. 유지보수가 쉽다. (상단 코드의 예시로 Controller만 건드리면 자식클래스도 모두 변경되기 때문이다.)

2. 느슨한 결합 (클래스간 의존성이 줄어들며 확장성이 늘어나고 결합도가 낮아 안정성이 높아진다. -> 참조타입이 달라도 사용이 가능하기 때문)

3. 재사용성 증가 (한번 선언해두면 필요할때마다 호출해서 사용이 가능하기때문 참조타입이 달라도 된다!)

 

조건

자손타입 참조변수로 조상타입 객체를 가리킬순 없다. (업캐스팅만 가능하다)

한가지 예시를 들면 갤럭시10(이하 "10v")에서 20(이하 "20v")으로 넘어갈때 터치기능 외에 스와이프 기능이 추가되었다 가정을해보자

그리고 10v에서 작성된 프로그램 (A)는 터치기능만 있으면 작동이 되지만

20v에서 작성된 프로그램이(B)는 터치기능과 스와이프가 필수로 필요하다고 가정을 했을때

10v에서 사용하던(A)는 20v에선 사용이 가능하다. -> 20v도 터치기능은 내장되있기 때문이다!

그러나 20v에서 사용하던(B)는 10v에선 사용이 불가능하다. -> 스와이프 기능이 없기 때문이다!

class phone10V{
	boolean touch;	//터치기능
}

class phone20V extends phone10V{
	boolean swape;	//스와이프기능
}


phone10V A = new phone20v();	//가능 - A는 터치만 있으면 사용가능
phone20V B = new phone10v();	//불가능 -	B는 스와이프까지 있어야 사용가능

'CS' 카테고리의 다른 글

프로세서, 프로세스  (0) 2023.03.21
주소 바인딩  (0) 2023.03.18
제네릭(Generic)  (0) 2023.03.14
객체 지향 프로그래밍 (OOP) - 2 (5대원칙)  (0) 2023.03.02
스택트레이스(Stack Trace)  (0) 2023.02.26

+ Recent posts