OOP란
- 객체 지향 프로그래밍(Object-Oriented Programming)은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, '객체'들의 상호작용으로 프로그램 로직을 구성하는 프로그래밍 패러다임
- 프로그램을 보다 유연하고 변경이 용이하게 만들 수 있다는 점
- 코드의 변경을 최소화하고 유지보수를 하는 데 유리
- 코드의 재사용을 통해 반복적인 코드를 최소화하고, 코드를 최대한 간결하게 표현
프로그래밍 패러다임
프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를 어떻게 작성할지 결정하는 역할을 한다. 새로운 프로그래밍 패러다임을 통해서는 새로운 방식으로 생각하는 법을 배우게 되고, 이를 바탕으로 코드를 작성하게 된다.
1. 추상화(Abstraction)
- 공통의 속성이나 기능을 묶어 이름을 붙이는것이다
- 보통 객체 지향 프로그래밍에서 클래스를 정의하는 걸 추상화라고 생각하면 된다
- 자바에서 추상화를 구현할 수 있는 문법 요소로는 추상 클래스(abstract class) 와 인터페이스(inferface) 있습니다.
예시
- 위 예시를 보면, 자동차 와 오토바이는 모두 이동 수단이며 모든 이동 수단은 전진과 후진을 할 수 있다는 공통점을 가집니다.
- 이것을 자바 문법 요소를 사용하여 표현하면, 자동차와 오토바이라는 하위 클래스(sub-class) 들의 공통적인 기능(전진과 후진)을 추출하여 이동 수단 이라는 상의 클래스(super class)에 정의했습니다. 위 예제에서는 편의상 공통적인 기능(메서드)만 추출 했지만, 공통적인 속성(변수)도 추출하여 선언하는 것이 가능합니다.
- 위에서 본 살펴본 내용을 코드로 표현해보면 다음과 같습니다.
public interface Vehicle {
public abstract void start()
void moveForward(); //public abstract 키워드 생략 가능
void moveBackward();
}
- 자동차와 오토바이의 공통적인 기능 추출하여 이동 수단(Vehicle) 인터페이스에 정의합니다.
- 인터페이스란 "서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어주는 부분 또는 그런 접속 장치" 이라 정의 할 수 있는데, 객체 지향적 설계에 있어서 인터페이스는 어떤 객체의 역할만을 정의하여 객체들 간의 관계를 보다 유연하게 연결하는 역할을 담당합니다.
다른 말로 표현하면, 인터페이스에는 추상 메서드나 상수를 통해서 어떤 객체가 수행해야 하는 핵심적인 역할만을 규정 해두고, 실제적인 구현은 해당 인터페이스를 구현하는 각각의 객체들에서 하도록 프로그램을 설계하는 것을 의미합니다.
public class Car implements Vehicle {//이동 수단을 구체화한 자동차 클래스
@Override
public void moveForward() {
System.out.println("자동차가 앞으로 전진합니다.")
}
@Override
public void moveBackward() {
System.out.println("자동차가 뒤로 후진합니다.")
}
}
public class MotorBike implements Vehicle{
@Override
public void moveForward() {
System.out.print("자전거가 앞으로 전진 합니다.");
}
@Override
public void moveBackward() {
System.out.print("자전거가 뒤로 후진 합니다.");
}
}
- Vehicle 인터페이스를 구현한 구현체, Car 와 MotorBike 클래스에서 앞서 우리가 인터페이스에 정의한 역할을 각각의 클래스의 맥락에 맞게 구현하고 있습니다.
- 즉, 각각 클래스 모두 전진 과 후진의 기능을 공통적으로 가지지만, 차는 차의 시동을 걸어야 한고, 오토바이는 오토바이의 시동을 걸어야 하기 때문에 그 구현은 각 클래스에 따라 달라야 할 것 입니다.
- 이것을 객체 지향 프로그래밍에서 역할과 구현의 분리라고 하여, 이 부분이 아래에서 살펴볼 다형성과 함께 유연하고 변경이 용이한 프로그램을 설께하는 데 가장 핵심적인 부분이라 할 수 있습니다.
- 정리하면, 객체 지향 프로그래밍에서는 보다 유연하고 변경에 열려 있는 프로그램을 설계하기 위해 역할과 구현을 분리 하는데, 여기서 역할에 해당하는 부분이 인터페이스를 통해 추상화 될 수 있습니다.
https://lavender1122.tistory.com/80
2. 상속(Inheritance)
- 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법 요소
- 추상화의 연장선에서, 상속은 클래스 간 고유될 수 있는 속성과 기능들을 상위 클래스로 추상화 시켜 해당 상위 클래스로 부터 확장된 여러 개의 하위 클래스들이 모두 상위 클래스의 속성과 기능들을 간편하게 사용할 수 있도록 합니다.
- 즉, 클래스들 간 공유하는 속성과 기능들을 반복적으로 정의할 필요 없이 딱 한 번만 정의해두고 간편하게 재사용할 수 있어 반복적인 코드를 최소화하고 공유하는 속성과 기능에 간편하게 접근하여 사용할 수 있도록 합니다.
예시
public class Car {
String model;
String color;
int wheels;
// Car 클래스 고유의 속성
boolean isConvertible;
void moveForward() {
System.out.println("앞으로 전진합니다.");
}
void moveBackward() {
System.out.print("뒤로 후진 합니다.");
}
// Car 클래스 고유의 기능
void openWindow() {
System.out.print("모든 창문을 엽니다.");
}
}
public class MoteorBike {
String model;
String color;
int wheels;
// MotorBike 클래스 고유의 속성
boolean isRaceable;
void moveForward() {
System.out.print("앞으로 전진합니다.");
}
void moveBackward() {
System.out.print("뒤로 후진합니다.");
}
//MotorBike 클래스 고유의 기능
public void stunt() {
System.out.print("오토바이로 묘기를 부립니다.");
}
}
- 각각의 클래스 마다 속성으로 model, color, wheels 기능으로 moveFordward() 와 moveBackward 가 완전히 동일한 코드임에도 불구하고 계속 반복되고 있다는 점을 확인 할 수 있습니다. 또한 하나의 코드에서 변경 사항이 일어나면, 해당 코드 변경사항을 다른 클래스에서도 일일이 수정해줘야 하는 어려움이 있습니다.
- 그러면 이제 앞서 설명했던 추상화와 상속을 활용하여 앞선 코드를 재정의 해보도록 하겠습니다.
public class Vehicle { //추상화를 통한 상의 클래스 정의
String model;
String color;
int wheels;
void moveFordward() {
System.out.print("전진합니다.");
}
void moveBackward() {
System.out.print("후진합니다.");
}
}
public class Car extends Vehicle {
boolean isConvertible;
void openWindow() {
System.out.print("모든 창문을 엽니다.");
}
}
public class MotorBike extends Vehicle {
boolean isRaceable;
//메서드 오버라이딩 => 기능 재정의
@Override
void moveFordward() {
System.out.print("오토바이가 앞으로 전진합니다.");
}
public void stunt() {
System.out.print("오토바이가 묘기를 부립니다..");
}
}
public class Main {
public static void main(String[] args) {
//객체 생성
Car car = new Car();
MotorBike motorBike = new MotorBike();
// car 객체의 속성 정의
car.model = "테슬라";
car.color = "빨간색";
System.out.print("나의 자동차는" + car.color + " " + car.model + "입니다.");
// 객체들의 기능 실행
car.moveFordward();
motorBike.moveForward();
motorBike.moveBackward();
}
}
//출력 값
나의 자동차는 빨간색 테슬라입니다.
전진합니다.
오토바이가 앞으로 전진합니다.
후진합니다.
- Car와 MotorBike 클래스의 공통적인 속성과 기능들을 추출(추상화) 하여 Vehicle클래스 (상위클래스)에 정의하였고, extends 키워드를 통해 각각의 하위 클래스로 확장하여 해당 기능과 속성들을 매번 반복적으로 정의해야 하는 번거로움을 제거 했습니다.
- 또한 공통적인 코드의 변경이 있는 경우, 상위 클래스에서 단 한번의 수정으로 모든 클래스에 변경 사항이 반영될 수 있도록 만들었습니다.
- 참고로, MotorBike 클래스에서 확인할 수 있듯이, 상위 클래스의 기능과 속성들을 그대로 사용할 수도 있지만, 각각의 클래스의 맥락에 맞게 메서드 오버라이딩(method overriding) 을 사용하여 내용을 재정의 할 수도 있습니다.
- 추상화에서 봤었던 인터페이스를 통한 구현과 상속을 구분하는 핵심적인 차이 중에 하나라 할 수 있습니다.
- 즉, 양자 모두 상위 클래스-하위 클래스의 관계를 전제하면서 공통적인 속성과 기능을 공유할 수 있지만, 상속의 경우 상위 클래스의 속성과 기능들을 하위 클래스에서 그대로 받아 사용하거나 오버라이딩을 통해 선택적으로 재정의하여 사용할 수 있는 반면, 인터페이스를 통한 구현은 반드시 인터페이스에 정의된 추상 메서드의 내용이 하위 클래스에서 정의되어야 합니다.
- 결론적으로, 상속 관계의 경우 인터페이스를 사용하는 구현에 비해 추상화의 정도가 낮다고 할 수 있습니다. 인터페이스가 역할에 해당하는 껍데기만 정의해두고, 하위 클래스에서 구체적인 구현을 하도록 강제하는 것에 비해, 상속 관계의 경우 상황에 따라 모든 구체적인 내용들을 정의해두고 하위 클래스에서는 그것을 단순히 가져다가 재사용할 수 있습니다.
3. 다형성(Polymorphism)
- 하나의 변수명이 상황에 따라 다른 의미로 해석될 수 있다는것을 뜻합니다.
- 일반적으로 오버라이딩 혹은 오버로딩 의미
public interface Vehicle {
public abstract void start()
void moveForward(); //public abstract 키워드 생략 가능
void moveBackward();
}
public class Car implements Vehicle {//이동 수단을 구체화한 자동차 클래스
@Override
public void moveForward() {
System.out.println("자동차가 앞으로 전진합니다.")
}
@Override
public void moveBackward() {
System.out.println("자동차가 뒤로 후진합니다.")
}
}
- 메서드 오버라이딩을 사용하면 같은 이름의 moveFordward() 와 moveBackward()를 각각의 클래스의 맥락에 맞게 재정의하여 사용할 수 있습니다.
- 즉, 같은 이름의 메서드가 상황에 다른 역할을 수행하는 것입니다.
- 또한, 하나의 클래스 내에서 같은 이름의 메서드를 여러 개 중복하여 정의하는 것을 의미하는 메서드 오버로딩도 이와 같은 맥락이라 할 수 있습니다.
- 객체 지향 프로그래밍에서 다형성이란 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만드는 것을 의미합니다. 좀 더 구체적으로, 상위 클래스 타입의 참조변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것입니다.
- 예를 들어
- 사람이 음식을 먹습니다.
- 여기서 음식은 상황에 따라서 피자가 될 수도, 치킨이 될 수도, 짜장면이 될 수도, 탕수육이 될 수도 있습니다. 또 다른 한편으로, 사람은 철수가 될 수도 ,영희가 될수도 있습니다. 정의하면, 앞서 쭉 설명한 다형석의 맥락과 마찬가지로 음식과 사람은 그 상황과 맥락에 따라서 모습을 바꿀 수 있습니다.
- 객체 지향 프로그래밍에서 다형성이란 앞서 설명한 이동 수단과 같은 넓은 범위의 타입, 즉 상위 클래스 타입의 참조 변수로 그것과 관계있는 하위 클래스들을 참조할 수 있는 능력입니다.
public class Main{
public static void main(String[] args){
//원래 사용했던 객체 생성 방식
Car car = new Car();
MotorBike motorBike = new MotorBike();
// 다형성을 활요한 객체 생성 방식
Vehicle car2 = new Car();
}
}
- 상위클래스 타입의 참조변수로 하위클래스 객체를 참조하는 것의 의미를 조금 더 구체적으로 이해할 수 있습니다. 원래 우리가 사용했던 방식은 하위 클래스의 객체를 생성하여 하위 클래스 타입의 참조변수에 할당해주었지만,
- 다형성을 활용한 객체 생성방식에서는 하위 클래스의 객체를 생성하여 상위 클래스 타입의 참조변수 car2에 할당해주고 있습니다.
- 그렇다면 왜 이렇게 다형성을 활용한 방식이 유용할까요?
- 지금부터 코드로 하나씩 살펴보도록 하겠습니다. 먼저는 다형성을 활용하면 여러 종류의 객체를 배열로 다루는 일이 가능해집니다.
public class Main{
public static void main(String[] args){
//상위 클래스 타입의 객체 배열 생성
Vehicle vehicles[] = new Vehicle[2];
vehicles[0] = new Car();
vehicles[1] = new MotorBike();
for(Vehicle vehicle : vehicles){
System.out.println(vehicle.gietClass()); // 각각의 클래스를 호출해주는 메서드
}
}
}
---------------
//출력값
class Car
class MotorBike
- 상위 클래스 Vehicle 타입의 객체 배열을 생성해주면, 이제 해당 타입의 참조 변수는 Vehicle 클래스와 상속 관계에 있는 모든 하위 클래스들을 그 안에 담아줄 수 있습니다.
- 원래 자바에서 배열의 개념이 하나의 같은 타입으로 이뤄져 있는 자료구조라는 사실을 기억할때, 이렇게 다형성을 활용하면 하나의 타입만으로 여러 가지 타입의 객체를 참조할 수 있어 보다 간편하고 유연하게 코드를 작성하는 것이 가능해집니다.
또 다른 예제
public class Driver{
void drive(Car car){
car.moveForward();
car.moveBackward();
}
void drive(MotorBike motorBike){
motorBike.moveForward();
motorBike.moveBackward();
}
}
public class Main{
public static void main(String[] args){
Car car= new Car();
MotorBike motorBike = new MotorBike();
Driver driver = new Driver();
driver.drive(car);
driver.drive(motorBike);
}
}
//-----------------------------------------
//출력값
전진합니다.
후진합니다
오토바이가 앞으로 전진합니다.
후진합니다.
- 위의 예제에서 확인할 수 있듯이, Driver 클래스의 코드는 매우 간단합니다.
즉, 매개변수로 자동차나 오토바이 객체를 전달받아 운전합니다. 이렇게 하나의 객체가 다른 객체의 속성과 기능에 접근하여 어떤 기능을 사용할 때, 우리는 'A클래스는 B클래스에 의존한다' 라고 표현합니다. - 같은 맥락에서, 위의 코드 예제를 도식을 사용하여 표현하면 아래와 같이 그려볼 수 있을 것이고, 우리는 Driver 클래스가 Car 클래스와 MotorBike 클래스에 의존하고 있다라고 설명할 수 있습니다.
- 즉, Driver 클래스와 다른 두 개의 클래스가 서로 직접적인 관계를 가지고 있는데, 이러한 상황을 조금 어려운 말로는 '객체들 간의 결합도가 높다'고 표현합니다.
- 하지만 이렇게 결합도가 높은 상태는 객체 지향적인 설계를 하는데 매우 불리합니다.
- 만약 지금처럼 이동 수단이 자동차와 오토바이 단 2개가 아니라 수 십, 수 백개라면 어떨까요? 그러면 똑같은 코드를 수십, 수백번 작성해야 할 것입니다.
- 또 만약 어떤 새로운 상황이 발생해서, MotorBike 클래스가 다른 클래스 MotorCycle 클래스로 변경되어야 하는 경우 어떨까요?
public class Driver {
void drive(Car car) {
car.moveForward();
car.moveBackward();
}
void drive(MotorCycle motorCycle) {
motorCycle.moveForward();
motorCycle.moveBackward();
}
}
- Driver 클래스 안에 매개변수로 전달되는 참조변수의 타입과 참조변수를 수정할 수 밖에 없는 상황이 발생합니다.
- 이런 맥락에서, 객체 지향 프로그래밍은 지금까지 학습한 추상화, 상속, 다형성의 특성을 활용하여 프로그래밍을 설계할 때 역할과 구현을 구분하여 객체들 간의 직접적인 결합을 피하고, 느슨한 관계 설정을 통해 보다 유연하고 변경이 용이한 프로그램 설계를 가능하게 만들었습니다.
public interface Vehicle { // 이동수단의 역할 정의
void moveForward();
void moveBackward();
}
public class Car implements Vehicle {//이동 수단을 구체화한 자동차 클래스
@Override
public void moveFordward() {
System.out.println("자동차가 앞으로 전진합니다.")
}
@Override
public void moveBackward() {
System.out.println("자동차가 뒤로 후진합니다.")
}
}
public class MotorBike implements Vehicle{
@Override
public void moveFordward() {
System.out.print("자전거가 앞으로 전진 합니다.");
}
@Override
public void moveBackward() {
System.out.print("자전거가 뒤로 후진 합니다.");
}
}
public class Driver{
void drive(Vehicle vehicle){ //매개변수로 인터페이스 타입의 참조변수를 전달
vehicle.moveForward();
vehicle.moveBackward();
}
}
public class Main{
public static void main(String[] args) {
Car car= new Car();
MotorBike motorBike = new MotorBike();
Driver driver = new Driver();
driver.drive(car);
driver.drive(motorBike);
}
}
//----------------------------------------------------------
//출력갑
자동차가 앞으로 전진합니다.
자동차가 뒤로 후진합니다.
오토바이가 앞으로 전진합니다.
오토바이가 위로 후진합니다.
- 추상화에서 봤었던 것처럼 Vehicle 인터페이스를 통해 이동 수단의 역할을 추상화하고, 각각 Car 클래스와 MotorBike 클래스에서 기능들을 구현하고 있습니다. 여기까지 앞서 추상화 파트에서 봤던 것과 동일 합니다.
- 가장 핵심은 Driver 클래스 입니다. 비교를 위해 인터페이스르 작성하지 않았던 코드를 함께 가져와서 살펴보도록 하겠습니다.
Vehicle 인터페이스 적용전
public class Driver {
void drive(Car car) {
car.moveForward();
car.moveBackward();
}
void drive(MotorCycle motorCycle) {
motorCycle.moveForward();
motorCycle.moveBackward();
}
void drive(Bus bus) {
bus.moveForward();
bus.moveBackward();
}
void drive(Train train) {
train.moveForward();
train.moveBackward();
}
// 이하 생략
}
Vehicle 인터페이스 적용 후
public class Driver{
void drive(Vehicle vehicle){ //매개변수로 인터페이스 타입의 참조변수를 전달
vehicle.moveForward();
vehicle.moveBackward();
}
}
- 중복이 사라지고, 코드가 휠씬 간결해졌다는 사실을 알 수 있습니다.
- 핵심은 drive() 메서드로 전달되는 매개변수의 타입을 상위 클래스인 인터페이스 타입 Vehicle 클래스로 변경한 것입니다. 이제 다형성의 세례를 받은 drive() 메서드의 매개변수로 인터페이스를 구현한 객체라면 무엇이든 전달이 될 수 있게 되었습니다. 마찬가지로 지금은 편의상 하나의 메서드만 살펴봤지만 만약 메서드의 수가 많아지고 코드 라인이 길어지면 그 효과는 더 강력해 집니다.
- Driver 클래스가 Car클래스 와 MotorBike 클래스 각각과 직접적으로 연결되어 강한 결합도를 보였지만, 이제는 Vehicle 인터페이스를 통해 간접적으로 연결되어 결합도가 낮아졌습니다.
- 따라서, 이제 Driver 클래스는 더 이상 각각의 클래스 내부의 변경이나 다른 객체가 새롭게 교체되는 것을 신경 쓰지 않아도 인터페이스에만 의존하여 수정이 있을 때 마다 코드 변경을 하지 않아도 됩니다.
- 비유로 표현하면, 운전자가 운저하는 법을 매번 새롭게 배우지 않아도 변경된 이동 수단 이용하는데 아무런 문제가 발생하지 않습니다. 이것이 바로 지금까지 학습한 추상화와 다형성의 특성을 활용한 역할과 구현의 구분이자, 보다 유연하고 변경이 용이한 소프트웨어 설계를 가능하게 하는 객체 지향 프로그래밍의 꽃이라 할 수 있습니다.
public class Main{
public static void main(String[] args) {
Vehicle car = new Car(); //높은 결합도
Vehicle motorBike = new MotorBike(); //높은 결합도
Driver driver = new Driver();
driver.driver(car);
driver.driver(motorBike);
}
}
//------------------------------------------------------------------
//출력값
자동차가 앞으로 전진합니다.
자동차가 뒤로 후진합니다.
오토바이가 앞으로 전진합니다.
오토바이가 뒤로 후진합니다.
- 실행 클래스의 코드에서 객체를 생성할 때 ,new Car() 와 new MotorBike() 처럼 객체에 직접적으로 의존하고 있어서, 해당 객체를 다른 객체로 변경이 불가피합니다.
- 즉, 다시 객체 간 높은 결합도를 보이는 상황이 초래되었습니다.
- 이 문제를 해결하기 위해 등장한 것이 바로 의존관계 주입(dependency Injection)이라 부르는 스프링 프레임워크의 핵심적인 개념입니다.
https://lavender1122.tistory.com/318
- 객체 지향 프로그래밍은 객체 간 관계와 협력을 설계하는 것인데, 다형성은 그 관계를 보다 유연하고 확장이 용이한 설계가 가능하도록 하는데 핵심적인 역할을 한다는 사실이 중요합니다.
- 또한, 다형성을 제대로 활용하기 위해서 앞서 배웠던 추상화와 상속에 대한 내용들이 함께 존재해야 한다는 사실도 기억해야 합니다.
- 즉, 추상화가 있어야 각 객체들의 역할 정의가 가능하고, 인터페이스는 상위클래스 - 하위 클래스를 전체하기 때문에 상속에서 배웠던 개념들이 함께 필요합니다.
4. 캡술화(Encapsulation)
- 한 객체가 특정한 하나의 목적을 위해 필요한 데이터나 메소드를 하나로 묶는 것 의미
- 데이터는 외부에서 직접 접근하면 안되고 함수를 통해서만 접근
- 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것을 말합니다.
- 데이터 보호(data protection) - 외부로부터 클래스에 정의된 속성과 기능들을 보호
- 데이터 은닉(data hiding) - 내부의 동작을 감추고 외부에는 필요한 부분만 노출
- 바깥 간섭으로 인해 발생하는 오류 방지 (내부의 구현은 감추고 모듈 내에서의 응집도를 높이며, 외부로의 노출의 최소화 하여 모듈 간의 결합도를 떨어뜨려 유연함과 유지 보수성을 높힘)
- 다른 사람의 클래스 객체를 다 뜯어볼 필요가 없고 제공되는 기능 그대로 사용
- 외부로 부터 클래스에 정의된 속성과 기능들을 보호하고, 필요한 부분만 외부로 노출될 수 있도록 하여 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 하는 목적이 있습니다.
- 캡슐화를 구현하기 위한 방법
- 접근제어자(access modifiers)를 활용하는 것 입니다.
- getter/setter 메소드 활용
접근 제어자(access modifiers)
- 접근 제어자 : 클래스 또는 클래스의 내부의 멤버들에 사용되어 해당 클래스나 멤버들을 외부에서 접근하지 못하도록 접근을 제한하는 역할
- 접근 제어자를 활용하여 어떤 클래스나 그 멤버에 대한 접근 범위를 설정하여 데이터를 효과적으로 보호할 수 있습니다.
접근제어자 | 클래스 내 | 패키지 내 | 다른 패키지의 하위클래스 | 패키지 외 | 설명 |
private | O | X | X | X | 동일 클래스 내에서만 접근 가능 |
default | O | O | X | X | 동일 패키지 내에서만 접근 가능 |
protected | O | O | O | X | 동일 패키지 + 다른 패키지의 하위클래스에서 접근 가능 |
public | O | O | O | O | 접근 제한 없음 |
예제
package package1; // 패키지명 package1
class Test{ //Test 클래스의 접근 제어자는 default
public static void main(String[] args){
SuperClass superClass = new SuperClass();
//System.out.print(parent.a); // 동일 클래스가 아니기 때문에 에러발생
System.out.print(superClass.b);
System.out.print(superClass.c);
System.out.print(superClass.d);
}
}
public class SuperClass { // SuperClass 클래스의 접근 제어자는 public
private int a = 1; // a,b,c,d에 각각 다른 접근 제어자 정의
int b = 2;
protected int c = 3;
public int d = 4;
public void printEach() { //동일 클래스이기 때문에 에러발생하지 않음
System.out.print(a);
System.out.print(b);
System.out.print(c);
System.out.print(d);
}
}
//---------------------------------------------------------------------------------------
//출력값
2
3
4
package package2;
//파일명 Test2.java
import package1.SuperClass;
class subClass extends SuperClass { //package1으로부터 SuperClass 클래스를 상송
public void printEach() {
//System.out.print(a);// private 의해서 에러 발생
//System.out.print(b);// default 의해서 에러 발생
System.out.print(c);// 다른 패키지의 하위 클래스 출력 가능
System.out.print(d);
}
}
pulic class Test2 {
public static void main(String[] args) {
SuperClass parent = new SuperClass();
//System.out.print(parent.a);// public을 제외한 모든 호출 에러!
//System.out.print(parent.b);//default 동일한 패키지에 없어서 에러 발생
//System.out.print(parent.c);//public을 제외한 모든 호출 에러!
System.out.print(parent.d);
}
}
getter/setter
예제
public class Car {
private String model;
private String color;
private int wheels;
public String getModel() {
return model;
}
public void setModel(String modle) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getWheels() {
return wheels;
}
public void setWheels(String wheels) {
this.wheels = wheels;
}
}
- 위의 예제를 보면, 모든 속성값들이 private 접근 제어자로 선언되어 있고, getter/setter 메서드의 접근제어자만이 public 으로 열려있습니다. 따라서 선택적으로 외부에 접근을 허용할 속성과 그렇지 않을 속성을 getter/setter 메서드를 통해 설정해 줄 수 있습니다.
public class Car {
private String model;
private String color;
public Car(String model, String color){ //생성자
this.model = model;
this.color = color;
}
public void startEngine() {
System.out.print("시동을 겁니다.");
}
public void moveForward() {
System.out.print("자동차가 앞으로 전진합니다.");
}
public void openWindow() {
System.out.print("모든 창문을 엽니다.");
}
}
public class Driver {
private String name;
private Car car;
public Driver(String name, Car car) {
this.name = name;
this.car = car;
}
public void driver(){
car.startEngine();
car.moveForward();
car.openWindow();
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("테슬라", "레드");
Driver driver = new Driver("김코딩", car);
driver.drive();
}
}
//------------------------------------------------------
//출력값
시동을 겁니다.
자동차가 앞으로 전진합니다.
모든 창문을 엽니다.
- 만약에 Car 클래스의 3가지 메서드들에 어떤 변경이 생겼다고 가정해봅니다.
- 그러면 해당 메서드들을 사용하고 있는 Driver 클래스의 drive() 메서드의 수정이 불가피합니다.
- 다른 말로, Driver클래스가 Car클래스의 세보적인 내부 로직을 속속히 너무 잘 알고 있고 ,이것은 앞서 우리가 계속 피하고자 했던 객체간의 결합도가 높은 상태를 의미합니다.
- 이럴 때 우리는 캡슐화를 활용하여 객체의 자율성, 즉 하나의 객체가 해당 객체의 속성과 기능에 대한 독점적인 책임을 담당하도록 만들고, 이를 통해 객체 간의 결합도를 낮게 유지할 수 있습니다.
public class Car {
private String model;
private String color;
public Car(String model, String color){ //생성자
this.model = model;
this.color = color;
}
public void startEngine() {
System.out.print("시동을 겁니다.");
}
public void moveForward() {
System.out.print("자동차가 앞으로 전진합니다.");
}
public void openWindow() {
System.out.print("모든 창문을 엽니다.");
}
public void operate() { //앞서 Driver 클래스에 정의된 메서드들 이동하여 메서드 추출
startEngine();
moveForward();
openWindow();
}
}
public class Driver {
private String name;
private Car car;
public Driver(String name, Car car) {
this.name = name;
this.car = car;
}
public void drive(){
car.operate(); //Car 클래스에 있는 메서드를 단순하게 호출
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("테슬라", "레드");
Driver driver = new Driver("김코딩", car);
driver.drive();
}
}
//------------------------------------------------------
//출력값
시동을 겁니다.
자동차가 앞으로 전진합니다.
모든 창문을 엽니다.
- 아까와 출력값은 동일하지만, 기존의 Driver 클래스가 하나하나 호출해줬던 메서드들을 모두 operate() 메서드로 묶어 Car 클래스로 옮겨두고, Driver 클래스에서는 내부 동작을 전혀 신경쓰지 않아도 단순히 operate() 메서드를 호출하고 있습니다.
- 또한, operate() 메서드 내부의 메서드들은 외부에서 호출되어 사용할 일이 없으므로 접근제어자를 모두 private으로 변경 해주었습니다.
- 정리하면, Car 클래스와 관련된 기능들은 온전히 Car에서만 관리 되도록 하였고, 불필요한 내부 동작의 노출을 최소화 하였습니다.
- 이제 Driver 클래스의 입장에서는 더 이상 Car 클래스의 내부 로직을 알지 못하고, 알 필요도 없어졌습니다.
- 이렇게 캡슐화를 활용하면, 객체 내부의 동작의 외부로의 노출을 최소하하여 각 객체의 자율성을 높이고, 이를 통해 객체 간 결합도를 낮추어 앞서 설명한 객체 지향의 핵심적인 이점을 잘 살리는 방법으로 프로그램을 설계하는 일이 가능합니다.
동적 바인딩
- runtime에 값에 따라 변수 데이터 타입, 호출될 함수가 결정된다.
- 대부분 객체 지향 언어가 동적 바이딩을 지원하지만 에이다 같은 예외도 있다
출저
https://computasha.github.io/CS-OOP/#2-%EC%9D%80%EB%8B%89%ED%99%94-information-hiding-
https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95
'JAVA' 카테고리의 다른 글
[Java&JS]WebSocket 채팅 (0) | 2024.09.03 |
---|---|
Data 값 숫자로 변경된 경우 ex)1713925050000 수정 (0) | 2024.05.22 |
java Date 객체 이용한 당일 연월일 생성 (0) | 2024.05.11 |
SVN 설치 & 명령어 (0) | 2024.03.04 |
이클립스 export (내보내기),import(가져오기) (0) | 2024.02.17 |