[Java] 객체 지향 프로그래밍
Object-Oriented Programming
프로그래밍에서 필요한 데이터를 추상화시켜 상태(field)와 행위(method)를 가진 객체로 만들고 객체들 간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법.
객체
- 모든 실재하는 대상
- 무형의 대상을 포함
- 보고 느끼고 인지할 수 있는 모든 것
꼬리질문
- 모든 객체는 추상화가 가능한가?
- 공통적인 속성과 기능이 있는 객체라면 모두 추상화가 가능하다.
- 다만, 물리적 속성이 없는 추상적인 개념, “사랑”이나 “정의” 같은 개념의 경우 속성과 기능을 정의하기 어렵기 때문에 추상화하기 한계가 있다.
- 또한 이질적인 사물이나 개념, 매우 복잡하고 다양한 상태와 행위를 가진 객체를 추상화했을 때 원래 의도와 동떨어진 비효율적인 결과를 낼 수 있다.
장점
- 유연하다
- 변경이 용이하다 ⇒ 유지 보수성이 좋다.
꼬리질문
- 단점은 무엇인가?
- 절차 지향 프로그래밍에 비해 더 복잡하며 설계와 구현이 어렵다.
- 절차 지향 프로그래밍에 비해 실행속도가 느리다.
- 객체 간의 관계가 복잡해지면 메모리 비용이 높다.
- 다형성으로 인해 디버깅과 성능 최적화가 까다로울 수 있다.
4가지 특성
-
추상화 (Abstraction)
공통성과 본질을 모아 추출한다.
객체의 공통적인 속성(state)과 기능(behavior)을 추출하여 정의하는 것
자바에서 추상화 실현
- 추상 클래스 (abstract class) : 공통 기능을 상속받는 클래스들 간에 구현을 공유
- 인터페이스 (interface) : 공통된 규약을 정의하여 클래스 간의 일관성을 유지
-
상속 (Inheritance)
기존의 클래스를 재활용해서 새로운 클래스를 작성하는 자바의 문법 요소
추상화의 연장선 : 공통된 속성과 기능을 상위 클래스에서 추상화시켜 상위 클래스로부터 확장된 여러 개의 하위 클래스들이 상위 클래스의 속성과 기능을 간편하게 사용한다.
장점
- 간편하게 재사용
- 반복적인 코드를 최소화
- 공유하는 속성과 기능에 간편하게 접근
자바에서 상속 실현
- extends
상속과 인터페이스를 통한 구현의 차이
- 상속 : 상위 클래스의 속성과 기능들을 하위 클래스에서 그대로 받아 사용하거나, 오버라이딩을 통해 선택적으로 재정의해서 사용
- 인터페이스를 사용하는 구현에 비해 추상화의 정도가 낮다.
- 인터페이스를 통한 구현 : 반드시 인터페이스에 정의된 추상 메서드의 내용이 하위 클래스에서 정의
-
다형성 (Polymorphism)
어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
예시) Overriding, Overloading
객체 지향 프로그래밍에서 다형성이란 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미. 좀 더 구체적으로, 상위 클래스 타입의 참조변수(car2)로 하위 클래스의 객체를 참조할 수 있도록 하는 것
ex)
Vehicle car2 = new Car();
(Upcasting)Vehicle
: class - 메모리 상에 실제로 존재하지 않음car2
: 객체 (instance) - 실제 메모리에 생성
장점
-
여러 종류의 객체를 배열로 다루는 일이 가능하다
Car 클래스와 MotorBike 클래스는 Vehicle interface를 추상화한다.Vehicle vehicles[] = new Vehicle[2]; vehicles[0] = new Car(); vehicles[1] = new MotorBike();
-
객체들 간의 직접적인 결합을 피하고, 느슨한 관계 설정을 통해 보다 유연하고 변경이 용이한 프로그램 설계가 가능하다.
public class Driver { void drive(Car car) { car.moveForward(); car.moveBackward(); } void drive(MotorBike motorBike) { motorBike.moveForward(); motorBike.moveBackward(); } }
- Driver 클래스는 Car 클래스와 MotorBike 클래스에 의존한다
- 객체들 간의 결합도가 높다. ⇒ 객체 지향적인 설계에 불리
public class Driver { void drive(Vehicle vehicle) { vehicle.moveForward(); vehicle.moveBackward(); } }
꼬리질문
- upcasting 과 downcasting
- Upcasting : 하위 클래스 객체를 상위 클래스 타입으로 참조 (다형성) 암시적
Animal animal = new Dog();
- Downcasting : 상위 클래스 타입을 하위 클래스 타입으로 명시적으로 변환할 때 사용
- 런타임 오류의 위험을 대비하여 인스턴스 타입 확인 (
instanceof
)을 통해 안정성을 확인한다. Dog dog = (Dog) animal;
- 런타임 오류의 위험을 대비하여 인스턴스 타입 확인 (
- Upcasting : 하위 클래스 객체를 상위 클래스 타입으로 참조 (다형성) 암시적
4. 캡슐화 (Encapsulation)
클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것
장점
- Data Protection : 외부로부터 클래스에 정의된 속성과 기능들을 보호
- Data Hiding : 내부의 동작을 감추고 외부에는 필요한 부분만 노출
자바에서 구현 방법
- 접근 제어자(access modifiers)를 활용
- 클래스 또는 클래스의 내부의 멤버들에 사용되어 해당 클래스나 멤버들을 외부에서 접근하지 못하도록 접근을 제한
- getter/setter 메서드 사용
- 모든 속성값들을 private 접근 제어자로 선언
public class Car { public void startEngine() {} public void moveForward() {} public void openWindow() {} } public class Driver { private Car car; public void drive() { car.startEngine(); car.moveForward(); car.openWindow(); } }
- 객체 간의 결합도가 높다
- Car 클래스 내 메서드 수정이 필요하다면 Driver 클래스의 drive 메서드도 수정이 필요하다.
public class Car { private void startEngine() {} private void moveForward() {} private void openWindow() {} public void operate() { startEngine(); moveForward(); openWindw(); } } public class Driver { private Car car; public void drive() { car.operate(); } }
꼬리질문
- 데이터 무결성
- 데이터의 정확성과 일관성
- 외부에서 직접적인 조작을 막아 데이터의 일관성을 유지한다.
객체 지향 설계의 5원칙
S.O.L.I.D
- 단일 책임 원칙 - SRP (Single Responsibility Principle)
- 클래스(객체)는 단 하나의 책임(기능 담당)만 가져야 한다.
- 한 클래스에 기능이 여러개라면 기능 변경이 있을 경우 코드 수정이 많아진다.
- 목적 : 프로그램의 유지보수성을 높이기 위한 설계 기법
- 개방 폐쇄 원칙 - OCP (Open Closed Principle)
- 클래스는 확장에 열려있어야 하고 수정에는 닫혀있어야 한다.
- 기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하고, 확장에 따른 클래스 수정은 최소화하도록 프로그램을 작성해야 하는 설계 기법
- 추상화 사용을 통한 클래스 관계 구축을 권장
- 리스코프 치환 원칙 - LSP (Liskov Substitution Principle)
- 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다.
- 다형성 원리를 이용하기 위한 원칙
- 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미
- 인터페이스 분리 원칙 - ISP (Interface Segregation Principle)
- 인터페이스를 각각 사용에 맞게 끔 잘게 분리해야 한다는 설계 원칙
- 인터페이스의 단일 책임
- 목적 : 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것
- 주의 : 한 번 인터페이스를 분리하여 구성 해놓고 나중에 수정사항이 생겨서 또 인터페이스를 분리하는 행위를 지양한다.
- 의존 역전 원칙 - DIP (Dependency Inversion Principle)
- Class를 참조해야 하는 상황에서 직접 Class를 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙
- 구현 클래스(변화하기 쉬운것)에 의존하지 말고 인터페이스(변화하기 어려운 것)에 의존
- 각 클래스간의 결합도(coupling)를 낮추는 것
참고자료