1. 객체지향 프로그래밍이란?


  • 객체 지향프로그래밍을 알기 위해서는 먼저 객체에 관해 알고 있어야 한다.

객체 👻

  • 객체(Object)를 표현할 때, 흔히, 붕어빵 틀(클래스)에서 찍어낸 붕어빵이라고 표현한다.
  • 좀 더 쉽게 아래의 그림으로 보자.

  • 위는 강아지를 찍어낼 수 있는 강아지 클래스이다.
  • 강아지의 이름, 종, 생일, 좋아하는 간식을 넣을 수 있는 멤버 변수와 명령1, 명령2를 내리는 멤버함수(메서드)로 이루어져 있다.
  • 그리고 이 강아지 설계도(클래스)를 가지고 객체를 찍어낼 수 있다.

  • 위는 강아지 설계도(클래스)를 이용하여 쫑이를 만든 것이다.
  • 이를 코드로 구현하면 다음과 같다

class Dog {	//클래스
	String name; //이름					||
    String type; //종					|| -> 멤버 변수
	String birthday;//생일				||
    String favoriteFood; //좋아하는 간식	||
    
    void giveHand() { System.out.println("오른 손을 내민다.");}	//멤버함수(메서드)
    void walk() {System.out.println("목줄을 가지고 온다.");}		//
    
}
public class Main() {
	public static void(String[] args) {
    	Dog myDog = new Dog(); //myDog라는 객체를 만든다.
        myDog.name = "쫑"; //'.'을 이용하여 멤버 변수와 메서드에 접근한다. 
        myDog.type = "시츄";
        myDog.birthday = "3/4";
        myDog.favoriteFood = "개껌";
        
        myDog.giveHand();	//'오른 손을 내민다.'가 출력된다
        myDog.walk(); 		//'목줄을 가지고 온다.'가 출력된다
    }
}
  • 위와 같이 Dog 클래스를 이용하여 쫑이, 마리, 콩이와 같은 강아지들을 찍어낼 수 있다.

객체(Object) & 인스턴스(instance)

  • 객체와 인스턴스 단어의 혼용으로 인해 헷갈려하는 사람들이 많다.
    OOP에서 인스턴스는 해당 클래스의 구도로 컴퓨터 저장공간에 할당된 실체를 의미한다.
    즉, 객체가 메모리에 할당되어 실제 사용될 때 이를 인스턴스라고 부른다.
    다만, 객체지향 프로그래밍을 할 때 객체와 인스턴스 간의 차이를 명확하게 구분하지는 않는다.

절차 지향 vs 객체 지향

  • 절차 지향 프로그래밍: 코드를 순차적으로 짜는 것
  • 객체 지향 프로그래밍: 코드를 객체 단위로 나누어 구성하여 짜는 것.

절차 지향객체 지향
장점 빠르다생산성이 높다. 코드 재사용성이 높아진다.
객체를 만들지 않아 메모리 소비가 적다.유지보수가 쉽다.
코드의 흐름이 보여 매우 직관적이다.대형 프로젝트에 적합하다.
단점모든 코드 들이 유기적으로 연결 되어 있어절차지향에 비해 느리고, 코드를 작성하기 어렵다.
코드 수정 및 유지보수가 어렵다.객체가 많아지면 소요되는 비용이 커진다.



2. 객체 지향 프로그래밍의 4가지 특성


  • 캡슐화, 추상화, 상속, 다형성

1. 캡슐화 (Encapsulation)

  • 객체 내부의 어떤 동작에 대한 구현이 어떻게 되었는지 감추어 외부에서 객체를 손상시키는 일을 방지한다.
  • 높은 응집도와 낮은 결합도를 목표로 한다.
  • 외부에서 접근할 필요 없는 메서드나 변수의 접근 제어자를 private로 설정하여 객체 내부를 공개하지 않음으로써, 의도하지 않은 동작 오류를 최소화한다.
class Dog{
// 접근 제어자는 총 네 가지이다. public, default, private, protected
	public String  name; // 외부에서 접근 가능
    private int age;	// 외부에서 접근 불가능
    String favoriteFood; // 아무것도 안 지정되어 있을때 default, 동일 패키지 내에서 접근 가능하다.
   protected String type; // 동일 패키지이거나, 상속받은 다른 패키지의 클래스에서만 접근 가능하다.
   
}
public class Main{
	public static void main(String[] args) {
    	Dog myDog = new myDog();
        myDog.name = "쫑"; 
        // myDog.age = 10; 접근 제어자가 private이기 때문에 외부에서 접근이 불가능하다
    }
}

2. 추상화 (Abstraction)

  • 객체들이 공통적으로 필요로 하는 속성이나 동작을 하나로 추출해내는 작업
    예를 들어 위의 강아지 클래스를 만든 다고 했을때,
    강아지들의 공통적인 특징을 파악한 후 -> 이름, 종, 나이 . . .
    하나의 묶음(클래스)으로 만들어 내는 것이 추상화이다.

3. 상속 (Inheritance)

  • 여러 개체들이 지닌 공통된 특성을 하나의 법칙으로 일반화하는 과정이다.
  • 예를 들어 학생, 선생님, 학부모, 클래스가 있다고 하자 이들은 모두 사람이라는 공통점이 있다. 이에 따라 아래와 같이 Person이라는 클래스를 만들어 공통된 부분을 물려 받게 할 수 있다.
class Person {	// 부모 클래스
   String name;
   void eat() {System.out.println("밥을 먹는다.");}
   void sleep() {System.out.println("잠을 잔다.");}
}

class Student extends Person { // 자식클래스
   void eat() {System.out.println("학식을 먹는다.");}
}
class Teacher extends Person { // 자식클래스
   void study() {System.out.println("수업을 한다");}
}

😐주의 : 상속을 코드 재사용의 개념으로 이해하면 안 된다!
일반적인 개념을 구체화하는 상황에서 상속을 사용해야 한다.


4. 다형성(Polymorphism)

서로 다른 클래스의 객체가 같은 동작 수행 명령을 받았을 때, 각자의 특성에 맞는 방식으로 동작하는 것이다.

class Car {
	void horn() {System.out.println("빵빵");}
}

class 구급차 extends Car {
	void horn() {System.out.println("위용위용");}
}
class 경찰차 extends Car {
	void horn() {System.out.println("삐용삐용");}
}
class 자전거 extends Car{
 void horn() {System.out.println("따르릉"); }}
  • 위와 같이 Car클래스를 상속받는 구급차, 경찰차, 자전거가 있을때 아래의 결과가 나온다.
public class Main{
	public static void main(String[] args) {
    	Car car1 = new Car();
        구급차 car2 = new 구급차();
        경찰차 car3 = new 경찰차();
        자전거 car4 = new 자전거();
        car1.horn();	// 빵빵
        car2.horn();	// 위요위용
        car3.horn();	// 삐용삐용
        car4.horn();	// 따르릉
    }
}

3. SOLID

  • 객체지향 프로그래밍의 5대 원칙이다.
  • 단일 책임 원칙(SRP), 개방 폐쇄 원칙(OCP), 리스코프 치환 원칙(LSP), 인터페이스 분리 원칙(ISP), 의존관계 역전 원칙(DIP)가 있다.
  • 시간이 지나도 유지 보수와 확장이 쉬운 프로그램을 만들기 위해 고완되었다.

S (SRP: Single Responsibility Principle)

  • 단일 책임 원칙

    클래스는 단 한 개의 책임을 가져야 한다.

    • 한 클래스가 수행할 수 있는 기능이 여러 개라면, 클래스 내부의 함수끼리의 결합이 높아지게되는 문제가 발생한다.
    • 예를 들어 어떤 클래스 내에 A라는 메소드가 있고, 이 A메소드는 B메소드를 호출하고 B메소드는 C메소드를 호출한다고 하자. 이때 A의 동작이 일부 수정된다면 B, C메소드를 전부 바꿔야 할 상황이 생기게 되고 이는 매우 비효율적이다. 따라서 이를 모두 분리할 필요가 있다.
    • 이에 따라 응집도(cohesion)는 높이고 결합도(coupling)를 낮출 수 있다.

O (OCP: Open-Closed Principle)

  • 개방-폐쇄 원칙

    확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다

    • 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다.
    • 어떤 모듈을 수정한다고 하면 그 모듈을 사용하는 다른 모듈 역시 줄줄이 수정해야 할 것이다. 따라서 개방-폐쇄 원칙을 잘 적용하여 기존 코드를 변경하지 않고 새롭게 기능을 만들거나 변경할 수 있도록 해야한다.
    • OCP는 추상화(인터페이스)와 상속(다형성)을 통해 구현할 수 있다.

L (LSP: Liskov's Substitution Principle)

  • 리스코프 치환 원칙

    부모 객체를 호출하는 동작에서 자식 객체는 부모 객체의 행위를 수행할 수 있어야 한다.

    • 상속 관계가 아닌 클래스들을 상속관계로 설정하면 이 원칙이 위배된다.

  • upcasting & downcasting

    위의 Car 클래스를 상속받은 구급차, 경찰차, 자전거 예제를 다시 들고 오겠다.

class Car {void horn() {System.out.println("빵빵");}}
class 구급차 extends Car {void horn() {System.out.println("위용위용");}}

public class Main{
	public static void main(String[] args) {
    	Car car1 = new Car();
        구급차 car2 = new 구급차();
        car1.horn();//빵빵
        car2.horn(); //위요위용
        
    }
}

위는 문제 없이 돌아 간다.
또한 아래의 코드는 정상적으로 작동한다.

public class Main{
    public static void main(String[] args) {
        Car car3 = new 구급차();
        car3.horn();	// 위용위용
    }
}

이처럼 자식 객체가 부모 클래스의 타입으로 치환되는 것을 upcasting이라고 한다.
아래의 예제를 집중해서 보자

public class Main{
    public static void main(String[] args) { 
   		Car car3 = new 구급차();
        // 구급차 car4 = car3;	// 에러 발생 car3를 구급차로 형변환 시켜줘야함
        구급차 car4 = (구급차)car3;	// downcasting
    }
}

위에서 처럼, 부모 클래스의 타입을 갖고 있는 객체를 자식타입의 객체로 바꾸고 싶다면 명시적으로 형변환 해주어야 한다. 이를 downcasting이라고 한다.


I (ISP: Interface Segregation Principle)

  • 인터페이스 분리 원칙

    객체는 자신이 호출하지 않는 메소드에 의존하지 않아야 한다.

    • 반드시 필요한 메소드만을 상속/구현한다.
    • 상속할 객체의 규모가 너무 크다면 해당 객체의 메소드를 작은 인터페이스로 나눈다.
    • 이는 각 객체가 필요한 인터페이스만을 상속하여 구현하면 되므로 각자가 필요한 메소드만을 가지게 된다.

D (DIP: Dependency Inversion Principle)

  • 의존 역전 원칙

    객체는 저수준 모듈모다 고수준 모듈에 의존해야한다.

    • 저수준 모듈이 변경되어도 고수준 모듈은 변경이 필요없는 형태가 이상적이다.
    • 객체는 객체보다 인터페이스에 의존해야 한다. 즉, 객체의 상속은 인터페이스를 통해 이루어져야 한다.
    • 고수준 객체: 인터페이스 등의 추상적인 개념
      저수준 객체: 구현된 객체

0개의 댓글