[OOP] 말문부터 막히는 객체 지향 프로그래밍
zl존석동
·2021. 12. 27. 19:26
OOP, 누군가에게 설명할 수 있고 싶다.
객체 지향 프로그래밍에 대해 스스로 예시를 만들어보며 이해하고 공부해보자
OOP?
OOP특성
OOP 2분 요약 ver.
OOP?
객체 지향 프로그래밍(Object Oriented Programming) 은 컴퓨터 프로그래밍 패러다임 중 하나로 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 객체들의 모임으로 파악하고자 하는 것이다.
필요한 데이터를 추상화해 상태 와 행위 를 가진 객체 로 만들어 유기적으로 동작하는 프로그램의 주체 된다.
절차적 방식에서 기능을 중심으로 무엇을 어떤 절차로 처리할까? 라는 생각으로 프로그램을 처리했다면
객체지향 방식에서는 객체를 중심으로 누가 무엇을 수행하나 라는 것에 중점을 두어 프로그램을 처리해나가는 것이다.
장문주의!!!!
자판기 예시를 통해 절차적 프로그래밍과 객체지향 프로그래밍의 차이를 알아보자
간단함을 위해 구매자가 선택한 상품이 없는 경우는 없고 상품은 단 하나라고 가정하자.
절차적 방식 예시
지나가던 갑환이는 음료수 자판기 1을 보고 목이말라 오렌지주스를 구매하려고 한다.
자판기에 다가서면 자판기 정보가 보여진다.
갑환이는 돈을 넣어 오렌지주스 구매를 시도한다.
자판기가 들어온 돈을 검사해 갑환이의 돈을 자판기가 가져가며 갑환이는 음료수를 얻는다.
public static void main(String[] args) {
// 존재하는 음료 자판기 1
String machineName1 = "음료 자판기1";
String drink1 = "오렌지주스";
int price1 = 800;
int machineBalance = 3000;
// 구매자가 왔다.
String name = "김갑환";
int age = 17;
int balance = 1500;
List<String> pocket = new ArrayList<>();
// 자판기 1 앞에 다가선 갑환이
printMachine(machineName1);
// 돈을 넣어 오렌지주스를 구매하려는 갑환이.
// 돈이 충분해 구매가 가능하면
if (checkMoney(balance, price1)) {
// 음료를 구매해 갑환이의 돈은 소비되고 자판기에는 돈이 추가된다.
balance -= price1;
machineBalance += price1;
// 음료는 갑환이 주머니속으로
getDrink(pocket,drink1);
}
}
public static void printMachine(String name) {
System.out.println(name + " 자판기입니다.!");
}
public static boolean checkMoney(int money, int price) {
if (money >= price) {
return true;
}
return false;
}
public static void getDrink(List<String> pocket,String drink){
pocket.add(drink);
}
존재하는 데이터를 가지고 무엇을 해야 할지를 판단하고 기능을 만들어 기능을 통해서 요구사항에 부합하는 처리를 하고 있다.
실행부분을 보면 프로그램 동작을 위해 필요한 각각의 기능들이 순서대로 명시되어 있는 형태이다.
객체지향 방식
public class Customer {
private String name;
private int age;
private int balance;
private List<String> pocket;
public Customer(String name, int age, int balance){
this.name = name;
this.age = age;
this.balance = balance;
this.pocket = new ArrayList<>();
}
public int inputBalance(){
return balance;
}
public void spendMoney(int price){
this.balance -= price;
}
public void getDrink(String drink){
this.pocket.add(drink);
}
public List<String> getPocket(){
return pocket;
}
}
public class VendingMachine {
private String name;
private String drink;
private int price;
private int balance;
public VendingMachine(String name, String drink, int price, int balance) {
this.name = name;
this.drink = drink;
this.price = price;
this.balance = balance;
}
public String giveDrink() {
return drink;
}
public int getPrice(){
return price;
}
public void addBalance(int money){
this.balance += money;
}
public boolean checkMoney(int money) {
if (money >= price) {
return true;
}
return false;
}
public void printMachine(){
System.out.println(name + " 자판기 입니다.");
}
}
public class OopMain {
public static void main(String[] args){
VendingMachine machine = new VendingMachine("음료 자판기1","오렌지주스",800, 3000);
Customer customer = new Customer("김갑환",17,1500);
// 갑환이가 자판기 앞으로 접근했다.
machine.printMachine();
// 자판기의 오렌지주스 구매를 시도하는 갑환이.
// 갑환 : 돈 넣는다.
// 자판기: 돈 체크한다.
//machine.checkMoney(customer.inputBalance());
// 구매하기에 돈이 충분하다면?
if(machine.checkMoney(customer.inputBalance())){
// 음료의 가격은?
int price = machine.getPrice();
// 갑환이: 돈 소비
customer.spendMoney(price);
// 자판기: 갑환이의 돈 수령
machine.addBalance(price);
// 자판기: 음료수 준다. 갑환이: 음료수 받는다.
customer.getDrink(machine.giveDrink());
}
}
}
구매하는 사람인 Customer 클래스와 판매역할을 하는 VendingMachine 클래스를 만들고 각 클래스가 가질 것 같은 속성과 각 클래스가 할 것 같은 행위를 넣었다.
그리고 자판기인 음료자판기1 과 구매자인 깁갑환 이라는 객체를 생성하고 각 객체들이 지가 가진 데이터로 지가 할 수 있는 행동들을 하게끔 설계하였다.
프로그램 동작과정을 보면 필요한 객체가 생성되어 있고 각 객체가 뭘 하는지가 순서대로 명시되어 있는 형태이다.

사실 음료라는 객체가 이름,가격을 가지고 있고 자판기가 그 음료라는 객체를 여러개 가지고 있는게 맞지만 우선 이렇게 생각해보기로 하자.
자판기 상품 구매 라는 프로그램을 두 객체가 지가 가진 데이터로 지가 할 수 있는 행위 를 호출해대며 상호작용하게 되고 결과를 만들게 되는 것이다.
이렇게 클래스를 정의하여 구매자와 기계라는 설계도를 만들어두고 각각 다른 속성정보를 가졌으나 같은 기능을 사용할 수 있는 여러 구매자들과 설계도로 객체화하여 또 사용할 수 있게 된다.
아니 둘 다 결국 순서대로 동작하는 것 아닌가요??
절차적 방식에서 우리가 구현한 기능(메소드) 들을 가지고 충분히 똑같은 자판기 구매 과정을 잘 만들어 낼 수 있을 것 같은데요?
결국 프로그램을 동작시킬때 정해진 순서대로 처리하는 것은 같다.
결국 메인이라는 큰 메소드 안에서 동작하기 때문에 어떻게 구현하던 적힌 순서대로 동작할 것이다.
하지만 이 세상에는 위와 같이 단 하나의 자판기만 존재할리 없고
음료 자판기라고 범위를 줄였다 쳐도 모두 같은 동작을 하는 자판기일 것이란 보장은 없을 것 같다.
다양한 종류의 자판기가 있고 프로그래밍으로 이 수많은 자판기들에 대처할 수 있어야 한다.
위의 음료수 자판기1 옆에 카드 결제만 가능하며 성인만 구매할 수 있는 성인음료 자판기가 생긴다고 가정하면 어떨까?
절차적 프로그램에서는 성인용품 자판기를 위해 처리를 어떻게 할 것이고
객체지향 프로그램에서는 어떻게 할까??
절차적 프로그래밍은 프로그램 동작을 위해 무엇을 어떻게 처리할 것인가 를 고려하는 방법이고
객체지향 프로그래밍은 프로그램 동작을 위해 누가 뭘 할 것인가 를 고려하는 방법이라는 것을 염두해보며 객체 지향의 특징에 대해 알아보자.
OOP 특성
Encapsulation - 캡슐화
데이터와 데이터를 처리하는 기능을 클래스로 묶어주는 것을 말한다.
- 동질한 것들에 대한 기능, 특성이 한 곳에 묶여 분류되기 때문에 같은 목적을 위한 재사용이 원활해진다.
정보 은닉
- 접근 제어자를 활용해 내부의 세부적인 내용들에 대해 외부에서 직접 접근하는 것을 방지해 불필요한 변경으로 발생할 예상치 못한 오류를 방지할 수 있다.
- 사용방법의 변경 없이 해당 객체에서 필요한 부분에 대한 수정을 하면 되기에 다른 객체나 프로그램의 동작에 독립적이여서 이식성이 좋다.
간단한 예시로 캡슐화를 알아보자
위의 Customer 클래스를보면 해당 객체를 사용하는 부분에서 private 제어자를 사용했기 때문에 나이라는 변수에 접근하거나 수정할 수 없다.
뭘 잘못먹어서 갑자기 늙었다고 억지부리고 싶어요!
'나이 변경' 이라는 기능이 필요하면 어떡하죠
객체의 속성에 직접 접근하는 방법은 치명적인 오류를 만들 수 있다.
나이를 public 이라고 변경하면 다음과 같이 수정할 수 있다.
Customer c = new Customer("김갑환",15, 1500);
c.age = 35;
할당하려는 값이 int 타입이고 문제가 없다고 생각할 수 있지만..
c.age = -25
분명히 -25도 int 타입이니 해당 코드는 아무런 예외도 발생하지 않는다.
하지만 상식적으로 나이는 마이너스일수가 없을 것이다.
이런식으로 객체의 속성에 직접 접근하게끔 하는 것은 프로그램에 예상치 못한 오류를 가져다 줄 수 있다.
바보~ if(인풋 > 0){c.age = 인풋;} 이라고 하면 되잖아요~~
물론 객체 사용 부분에서 조건문을 사용하여 마이너스 값이 들어오는 것을 방지할 수 있겠지만
고객의 나이를 변경하다 라는 기능이 필요한 것인데 조건을 달아서 사용해야 하고
나이변경이 필요할 때마다 조건을 넣어 사용해야 하며
일정 값 이상일 때 수정을 허용하겠다 했을 때 이 일정값에 대해 변경이 필요하면
나이 변경이 있는 모든 부분을 다 찾아서 변경해야 할 것이다.
Getter & Setter
객체의 속성값을 알아야 하거나 수정해야 할 때 get , set 메소드를 이용해 속성 자체에의 접근을 막을 수 있고 일관적으로 요구를 처리할 수 있게 된다.
private int age;
// 고객: 나이수정
public void setAge(int inputAge){
if(inputAge > 1){
this.age = inputAge;
}
else{
throw new XXXException("~~~")
}
}
고객 클래스에 다음과 같이 `setAge(int 변경하려는값)` 메소드를 설정하고 해당 메소드로 접근하게끔 하여 나이를 변경하게 한다면
고객 : 나이변경 이라는 정확한 기능을 외부에서 사용할 수 있게 되고 나이를 수정할 때 내부적으로 어떻게 처리되어 나이가 수정되는지 알 수 없다.
그리고 서비스 정책이 바뀌거나 요구사항이 변경되어서 5살 이상으로만 수정이 가능하다고 생각해보자
언제 어느 부분에서 고객의 나이를 수정한다 라는 기능을 사용하던 상관없이 우리는 이 클래스의 해당 메소드 부분에서만 1을 5로 바꿔주기만 하면 된다.
Inheritance - 상속
이미 정의된 클래스에 대한 변경 없이 해당 클래스의 것들을 물려받는 새로운 클래스를 생성하는 방법이다.
기존 클래스를 부모(Super) 클래스라고 한다면 물려받는 자식(Sub) 클래스는 부모의 속성과 기능을 모두 가진 것에 더해 자신만의 속성과 기능을 가질 수 있게 된다.
갑자기 라면이 먹고 싶으니 라면으로 예시를 들어보자
라면을 끓일때는 면,스프,물 이라는 속성이 필요하고 물넣기,스프넣기,면넣기,조리하기 라는 동작이 필요할 것이다.
그런데 계란라면이 먹고싶다면? 면,스프,물,계란이라는 속성이 필요하고 물넣기,스프넣기,면넣기,계란넣기,조리하기 라는 동작이 필요할 것이다.
상속없이 라면과 계란라면을 설계한다면 다음과 같을 것이다.
public class 라면 {
int 면;
int 스프;
int 물;
int 스프넣다(){
return 스프;
}
int 면넣다(){
return 면;
}
int 물부터지(){
return 물;
}
void 조리하다(){
물부터지();
스프넣다();
면넣다();
}
}
public class 계란라면 {
int 면;
int 스프;
int 물;
int 계란;
int 스프넣다(){
return 스프;
}
int 면넣다(){
return 면;
}
int 물부터지(){
return 물;
}
int 계란넣다(){
return 계란;
}
void 조리하다(){
물부터지();
스프넣다();
면넣다();
계란넣다();
}
}
두 라면사이에 공통적으로 필요한 속성과 동작이 무엇인지 명확하게 보일 것이다.
상속을 활용하면 계란라면은 다음과 같이 설계할 수 있다.
public class 계란라면 extends 라면{
int 계란;
int 계란넣다(){
return 계란;
}
@Override
void 조리하다(){
super.조리하다();
계란넣다();
}
}
상속을 통해 이렇게 부모클래스에서 만들어진 속성과 기능을 자식클래스가 물려받아 사용할 수 있게 해주어
코드의 중복 작성을 방지해주고 재사용성을 향상시켜줄 수 있다.
Polymorphism - 다형성
요구에 의해 객체가 연산할 때 상황에 따라 다른 방법으로 응답될 수 있음을 말한다.
프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수,변수,식, 오브젝트,함수,메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.
참조 변수의 다형성
자바에서는 부모 클래스 타입의 변수로 자식 클래스 인스턴스를 참조할 수 있다.
즉 하나의 객체에 여러 타입을 대입할 수 있다.
형~ 배고파 라면 좀 끓여줘~
동생은 라면을 요구했는데 형은 동생을 위해 계란라면을 끓여 줄 수도 있고, 만두라면을 끓여줄 수도 있다.
// 같은 요구에 대해 다른 결과를 만들어 낼 수 있는 능력이 있다.
라면 r1 = new 계란라면();
r1.조리하다();
라면 r2 = new 만두라면();
r2.조리하다();
동생 (객체를 사용해야 하는 부분) 은 조리된 '라면' 을 요구하고 있고
그 라면의 형태(인스턴스)는 계란이 있던 만두가 있던 선인장이 있던 알 바 아니고 그저 라면으로써 조리된 것을 먹기만 하면 된다.
형이 어제 끓여줬던 계란라면이 맛이 없었다는 소문을 듣게 되면 형은 오늘은 만두라면을 끓여볼 수 있는 것이다.
같은 형태의 코드로 다른 결과를 만들어내는 다형성의 개념이라고 할 수 있다.
Overriding
오버라이딩은 부모클래스를 상속 받은 자식 클래스에서 부모클래스에 존재하는 메소드를 재정의해서 자기만의 것으로 활용하는 것을 말한다.
부모클래스의 메소드와 같은 이름 , 같은 리턴, 같은 파라미터를 사용한다.
위에서 라면 타입으로 다양한 라면객체를 가질 수 있었던 것 처럼
메소드 오버라이딩을 통해 같은 메소드 이름으로 자식놈들에 따라 다른 결과를 만들어 낼 수 있는 것이다.
위의 라면-계란라면 예시의 @Override 부분이 재정의 메소드이다.
Overloading
오버로딩은 한 클래스 안에 이미 정의된 메소드라 하더라도 일정 규칙을 가지고 또 정의해 사용할 수 있는 것을 말한다.
조건
1. 메소드 이름이 같다.
2. 파라미터의 개수 또는 타입 또는 순서가 달라야 한다.
3. 리턴 타입이 다르고 파라미터가 같은 것은 안된다.

Abstraction - 추상화
공통의 속성과 기능을 묶어 이름을 붙이는 것으로 객체 지향 관점에서 클래스를 정의하는 것을 추상화라고 할 수 있다.
* 필수 속성만 "보여주고" 불필요한 정보는 "숨긴다".
* 구체적인 구현을 숨기고 요구되는 특성만 드러내는 것
클래스의 기능을 사용할 때 내부의 동작 원리에 대해서는 알 수 없지만 무엇을 하는지는 알 수 있고 알 수 있게 해야 한다. (이름만으로 무엇을 하는지 알 수 있어야 추상화가 잘 된 것.)
끓여주는 형의 입장에서 라면 끓여주는 기계를 이용해 라면을 끓인다는 설정으로 변경해 예시로 들어보자.
실제로는 물만 기계가 조절해주고 면, 스프는 내가 넣어야 되지만 기계에서 조절해 알아서 넣어준다고 가정하자
동생이 라면을 요구해 (라면 클래스 변수 선언) 계란 라면을 끓인다면
형은 '계란 선택' 버튼을 누르고 (계란라면으로 인스턴스 생성)
'조리하기' 버튼 (조리하다() 메소드 실행) 을 누르기만 하면 된다.
내부에서 물과 스프, 계란은 어느정도로 넣을 것이며 어느 순서대로 언제 넣을지 판단하지만
형은 그 동작에 대해 알 필요가 없고 `조리하기` 만 찾아서 누르면 된다.
또한 클래스 내부에서도 조리하다() 라는 메소드 관점에서 보면 물 넣기() , 스프넣기(), 면 넣기() 등
조리를 위해 필요한 동작들의 로직 없이 각각 동작만 할 수 있도록 추상화 되어 있다.
물량에 대한 로직은 물 넣기에서 해결되고 변경될 수 있고 스프에 대한 로직은 스프넣기에서 해결되고 변경된다.
조리하다() 라는 과정에서는 해당 동작들의 세부적인 로직 변경과 독립적이며 해당 동작들을 사용만 할 뿐인 것이다.
-> 사용자에게 영향을 끼치지 않은 채로 독립적으로 클래스의 내부 구현 변경 가능
스프넣기() , 물넣기() , 면 넣기() 동작을 private 으로 설정하고 조리하다() 만 public 으로 설정한다고 치자.
-> 추상화를 위해 접근제어자를 활용한 캡슐화는 도움이 될 수 있다.
조금이나마 이 라면기계를 현실에 가깝게 대입해보면 사용자 입장에서는 조리하기 버튼만 보이며
조리 도중에는 어떤 유리막에 라면이 갇혀있어 조리 과정에 관여 할 수 없는 것이라고 볼 수 있다.
사용자는 조리라는 동작만 사용할 수 있고 사용하면 된다.
-> 중요하고 필요한 정보만 알맞게 제공해 프로그램의 보안이 향상되고 생산성, 가독성이 좋아진다.
좀 억지지만 어? 스프 왜 안나와? 언제나와? 안나와? 하고 스프를 더 때려부어 라면이 심각하게 짜질일도 없을 것이다.
-> 코드 중복 및 추상화 수준이 낮은 코드 사용 방지, 의도치 않은 결과 도출 방지
자바에서의 추상화
Abstarct Class 와 Interface 를 활용해 인스턴스화 불가능한 개체에 대한 명세만을 보여줄 수 있다.
현실에서의 라면을 생각해볼 때 그냥 '라면' 이라는게 있을까?
신라면, 너구리, 진짬뽕, 삼양라면, 참깨라면 등등 다양한 라면들이 있지 그냥 라면같은 것 따위는 없다.
다양한 종류의 실세계에 존재하는 라면들을 '라면' 이라는 추상적인 클래스를 통해
공통적인 부분만 추려 묶을 수 있는 것을 추상화라고도 할 수 있다.
라면을 추상클래스로 만들고 계란에 대한 부분을 인터페이스로 하여 신라면, 진라면, 너구리를 구현해보았다.
절대 생성자를 만들기 귀찮아서 그런건 아니고 이 세상 모든 라면의 물, 스프, 면에 대한 Default 양은 모두 같다고 가정하자
라면 추상 클래스
public abstract class 라면 {
protected int 물;
protected int 스프;
protected int 면;
protected 라면() {
물 = 500;
스프 = 80;
면 = 150;
}
protected abstract int 물넣기();
protected abstract int 스프넣기();
protected abstract int 면넣기();
public abstract void 조리하기();
}
계란 인터페이스
public interface 계라너블 {
int 계란넣기();
}
신라면 구현 클래스
public class 신라면 extends 라면 implements 계라너블 {
private boolean requested계란;
public 신라면(boolean requested계란) {
super();
this.requested계란 = requested계란;
}
@Override
protected int 물넣기() {
return 물;
}
// 신라면은 매워서 스프를 다 안 넣기
@Override
protected int 스프넣기() {
return 스프 - 30;
}
@Override
protected int 면넣기() {
return 면;
}
@Override
public void 조리하기() {
물넣기();
스프넣기();
면넣기();
System.out.print("[넘무 맛있는 신라면 완성!");
if (requested계란) {
계란넣기();
}
System.out.println("]");
}
// 신라면은 매우니까 계란 두개를 넣자
@Override
public int 계란넣기() {
System.out.print("계란 2개를 모두 풀어서 함유!");
return 2;
}
}
너구리 구현 클래스
// 너구리에 계란 넣는 사람이 있을까?
public class 너구리 extends 라면 {
private int 다시마;
public 너구리() {
this.다시마 = 1;
}
// 랜덤으로 다시마 2개 이벤트 발생
// 먹는 놈은 다시마가 1개인지 2개인지 알 길이 없다
private int 다시마넣기() {
return (int) Math.round(Math.random() + 1);
}
@Override
protected int 물넣기() {
return 물;
}
@Override
protected int 스프넣기() {
return 0;
}
@Override
protected int 면넣기() {
return 0;
}
// 너구리는 면이 잘 안 익어서 스프전에 면부터 넣으면 맛있다더라
@Override
public void 조리하기() {
물넣기();
면넣기();
스프넣기();
int dasima = 다시마넣기();
System.out.println("[너무 맛있는 너구리 완성! - 다시마(" + dasima + ")개 함유");
}
}
실행부분
public class 라면기계 {
public static void main(String[] args) {
// 라면 ramen = new 라면();
라면 requestedRamen1 = new 너구리();
requestedRamen1.조리하기();
라면 requestedRamen2 = new 진라면(false);
requestedRamen2.조리하기();
라면 requestedRamen3 = new 진라면(true);
requestedRamen3.조리하기();
라면 requestedRamen4 = new 신라면(true);
requestedRamen4.조리하기();
// 계라너블 너구리에_누가계란넣어 = new 너구리();
계라너블 계란넣을수있는라면이_먹고싶어요 = new 신라면(true);
라면 requestedEggableRamen1 = (라면) 계란넣을수있는라면이_먹고싶어요;
requestedEggableRamen1.조리하기();
}
}

사실 상속, 다형성 등 모든 개념이 다 들어있는 것 만 같은 기분이다.
라면이라는 추상 클래스는 공통된 데이터와 공통된 추상 메소드들을 가지며
각각의 진짜 라면들에서 이 메소드들을 강제로 구현해 사용한다.
라면기계로 라면을 끓이는 사람 입장에서는 어떤 라면을 조리한다 라는 것에만 관심이 있고 그것밖에 알 수 없다.
너구리라 다시마가 추가로 들어가고 가끔 두개가 나올 수도 있으며
신라면이라 매워서 스프가 덜 들어가는 등의 로직에는 관심이 없고 알 수도 없다.
추상 클래스는 직접 인스턴스화 시킬 수도 없으며 추상 클래스 타입의 변수로 선언해 인스턴스화된 자식들은
같은 부모의 메소드 이름으로 각각 다른 구현의 결과를 보여줄 수 있다.
인스턴스 사용관점 (라면 기계를 누르는 사람) 에서는 별도의 다양한 클래스, 메소드들에 대해 확인할 필요도 알 필요도 없이
해당 추상 클래스(라면)를 상속받는 라면이 뭐가 있는지와 추상 클래스의 조리하다() 메소드의 존재만 보고
어떠어떠한 라면을 조리할 수 있겠다 라고 알 수 있다.
계라너블 인터페이스 같은 경우 계란넣다() 라는 추상 메소드만 존재하는데
위에서처럼 어디에는 계란을 다 풀고 어디에는 반만 풀고 하지말고 어차피 계란을 넣으려면 손질을 해야되는데
인터페이스에 계란손질하기() 라는 추상메소드를 만들어 넣으면 더 완벽한 추상화가 되지 않을까 생각한다.
라면 뿐 아니라 다른 음식 클래스를 만들 때도 계란이 들어갈 수 있다면 이 계라너블 인터페이스를 구현하도록 강제시킬 수 있다.
이 계라너블 인터페이스의 존재로 인해 앞으로 다른 계란이 들어간 음식을 설계할 때 계란에 대한 기능을 또 만들다가 중복이나 오류를 발생시킬 일은 없을 것이다.
계란이 들어가는 음식이 먹고 싶어~
이 때 계라너블 타입으로 인스턴스화를 시도하면 계라너블을 구현한 다른 클래스들을 인스턴스화 시킬 수 있다.
=> 관계가 없는 클래스들 간에 관계를 만들어 준다.
위의 억지 예시를 바탕으로 추상화가 잘 되어 있으면 코드 재사용성이 높고 생산성이 높아지며 예측 안되는 상황 발생 확률을 낮춰줄 수 있다는 것을 알 수 있다!
위의 긴 글은 공부하고 난 뒤 얻은 주관과 더러운 예시가 상당히 가미되어있어 잘못된 부분이 많을 수 있습니다.
읽으셨다면 지적 부탁합니다!
OOP 2분 요약 Ver.
장황하고 길었던 윗소리들은 모두 접어두고
팩트 위주로 객체지향 프로그래밍에 대해 간단하게 요약해보며 마무리해보았다!!
OOP란?
프로그램을 위해 필요한 데이터들을 추상화하여 속성과 행위를 가진 객체 를 만들고
객체들 간의 유연한 상호작용을 통해 로직을 구성하며 프로그래밍 하는 것
OOP 키워드
클래스, 객체, 인스턴스는 위에서 다루지 않았고 위의 글에서 객체와 인스턴스를 혼용해서 사용했다.
정확하게 한번 더 정리해보고 넘어가자!
클래스
- 메타데이터(설계도): 정의한 연관된 속성과 행위
객체
- 클래스로 진짜 구현해야 되는 대상이 되는 것(Object)
인스턴스
- 구현해야 되는 실세계의 객체를 소프트웨어 세계에 구현한 실체
- 객체가 실제 메모리에 할당되어 프로그램에서 사용되는 것을 말함
- 클래스를 통해 생성된 객체 하나하나를 인스턴스라고 할 수 있다.
캡슐화
- 데이터와 데이터를 처리하는 기능을 클래스로 묶어주는 것 - 재사용
- 접근 제어자로 불필요한 정보 노출을 외부로부터 숨기는 것 - 정보은닉
상속
- 상위 클래스의 속성과 기능을 이어 받아 하위 클래스에서 재사용하거나 일부 기능을 재정의하여 사용하는 것
다형성
- 요구에 따라 객체가 다른 응답을 할 수 있음을 말함.
- 같은 이름이라 하더라도 다르게 작용할 수 있음.
- Overriding : 부모 메소드를 재정의하여 사용하는 것
- Overloading : 같은 이름의 메소드를 다른 파라미터를 통해 다양하게 동작시키는 것
추상화
- 클래스 설계 그 자체: 공통된 기능과 속성을 묶어 네이밍 하는 것 자체
- 일반화 : 공통된 속성과 기능을 취해 슈퍼 클래스화
- 이름으로 말해요: 원치 않는 정보(구현)를 숨기고 중요 정보만 표시
장점
- 생산성 향상: 구현된 클래스 사용, 상속 등으로 인한 재사용성 증가
- 유지보수가 쉬움 : 추상화를 통해 나의 똥(추가,수정)이 주는 영향이 적어짐.
- 실세계스러운 모델링: 좀 더 자연스러운 구현이 가능
- 보안성 향상: 캡슐화, 정보은닉
단점
- 자원소모: 실행속도가 느리고, 메모리와 연산 비용이 더 크다.
- 어렵고 느린 개발속도: 높은 이해도와 설계를 위한 고뇌가 필요할 것이다.
객체 지향에 대해서 공부하고 다양한 글들을 읽어보고 길게, 짧게 글을 쓰면서도 참 어려운 개념 같이 느껴진다.
'이해도가 좀 더 높아져가고 있다' 라고 느낄만한 시점에
객체 지향의 5원칙에 대해서도 공부해보고 정리해봐야겠다는 생각이 들었다!
Ref
Java의 정석 기초편
교육현장에서 뒤쳐지는 학생들을 위해 쓰고, 학생들에게 직접 검증받고 호평받은 책. 코딩을 처음 배우는 사람도 자바를 쉽게 배울수 있게 도와준다.
book.naver.com
객체 지향 프로그래밍이 뭔가요? (꼬리에 꼬리를 무는 질문 1순위, 그놈의 OOP)
객체 지향 프로그래밍(Object Oriented Programming) 여러 소프트웨어 관련 IT기업 신입사원 기술면접에서 면접자들 긴장을 풀어줄 겸 워밍업으로 자주 나오는 질문이다. "객체 지향 프로그래밍에 대해
jeong-pro.tistory.com
Java에서 추상화 란 무엇인가-예제로 배우기 - 다른
이 튜토리얼에서는 코드 예제와 함께 Java의 추상화가 무엇인지 설명합니다. 또한 추상 클래스가 무엇이며 왜 사용되는지 배우게됩니다.
ko.myservername.com
객체 지향 프로그래밍이란?
객체 지향 프로그래밍에 대해 알아보자!
velog.io
What is Abstraction in OOPs? Java Abstract Class & Method
Abstraction is selecting data from a larger pool to show only the relevant details to the object. In Java, abstraction is accomplished using Abstract classes and interfaces. It is one of the most important concepts of OOPs.
www.guru99.com