[Java] 객체 지향 프로그래밍

4 분 소요

Object-Oriented Programming

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

객체

  • 모든 실재하는 대상
  • 무형의 대상을 포함
  • 보고 느끼고 인지할 수 있는 모든 것

꼬리질문

  • 모든 객체는 추상화가 가능한가?
    • 공통적인 속성과 기능이 있는 객체라면 모두 추상화가 가능하다.
    • 다만, 물리적 속성이 없는 추상적인 개념, “사랑”이나 “정의” 같은 개념의 경우 속성과 기능을 정의하기 어렵기 때문에 추상화하기 한계가 있다.
    • 또한 이질적인 사물이나 개념, 매우 복잡하고 다양한 상태와 행위를 가진 객체를 추상화했을 때 원래 의도와 동떨어진 비효율적인 결과를 낼 수 있다.

장점

  • 유연하다
  • 변경이 용이하다 ⇒ 유지 보수성이 좋다.

꼬리질문

  • 단점은 무엇인가?
    • 절차 지향 프로그래밍에 비해 더 복잡하며 설계와 구현이 어렵다.
    • 절차 지향 프로그래밍에 비해 실행속도가 느리다.
    • 객체 간의 관계가 복잡해지면 메모리 비용이 높다.
    • 다형성으로 인해 디버깅성능 최적화가 까다로울 수 있다.

4가지 특성

  1. 추상화 (Abstraction)

    공통성과 본질을 모아 추출한다.

    객체의 공통적인 속성(state)과 기능(behavior)을 추출하여 정의하는 것

    자바에서 추상화 실현

    • 추상 클래스 (abstract class) : 공통 기능을 상속받는 클래스들 간에 구현을 공유
    • 인터페이스 (interface) : 공통된 규약을 정의하여 클래스 간의 일관성을 유지

    image

  2. 상속 (Inheritance)

    기존의 클래스를 재활용해서 새로운 클래스를 작성하는 자바의 문법 요소

    추상화의 연장선 : 공통된 속성과 기능을 상위 클래스에서 추상화시켜 상위 클래스로부터 확장된 여러 개의 하위 클래스들이 상위 클래스의 속성과 기능을 간편하게 사용한다.

    장점

    • 간편하게 재사용
    • 반복적인 코드를 최소화
    • 공유하는 속성과 기능에 간편하게 접근


    자바에서 상속 실현

    • extends


    상속과 인터페이스를 통한 구현의 차이

    • 상속 : 상위 클래스의 속성과 기능들을 하위 클래스에서 그대로 받아 사용하거나, 오버라이딩을 통해 선택적으로 재정의해서 사용
      • 인터페이스를 사용하는 구현에 비해 추상화의 정도가 낮다.
    • 인터페이스를 통한 구현 : 반드시 인터페이스에 정의된 추상 메서드의 내용이 하위 클래스에서 정의
  3. 다형성 (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;

4. 캡슐화 (Encapsulation)

클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것

장점

  • Data Protection : 외부로부터 클래스에 정의된 속성과 기능들을 보호
  • Data Hiding : 내부의 동작을 감추고 외부에는 필요한 부분만 노출


자바에서 구현 방법

  • 접근 제어자(access modifiers)를 활용
    • 클래스 또는 클래스의 내부의 멤버들에 사용되어 해당 클래스나 멤버들을 외부에서 접근하지 못하도록 접근을 제한

    image 1

  • 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

  1. 단일 책임 원칙 - SRP (Single Responsibility Principle)
    • 클래스(객체)는 단 하나의 책임(기능 담당)만 가져야 한다.
    • 한 클래스에 기능이 여러개라면 기능 변경이 있을 경우 코드 수정이 많아진다.
    • 목적 : 프로그램의 유지보수성을 높이기 위한 설계 기법
  2. 개방 폐쇄 원칙 - OCP (Open Closed Principle)
    • 클래스는 확장에 열려있어야 하고 수정에는 닫혀있어야 한다.
    • 기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하고, 확장에 따른 클래스 수정은 최소화하도록 프로그램을 작성해야 하는 설계 기법
    • 추상화 사용을 통한 클래스 관계 구축을 권장
  3. 리스코프 치환 원칙 - LSP (Liskov Substitution Principle)
    • 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다.
    • 다형성 원리를 이용하기 위한 원칙
    • 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미
  4. 인터페이스 분리 원칙 - ISP (Interface Segregation Principle)
    • 인터페이스를 각각 사용에 맞게 끔 잘게 분리해야 한다는 설계 원칙
    • 인터페이스의 단일 책임
    • 목적 : 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것
    • 주의 : 한 번 인터페이스를 분리하여 구성 해놓고 나중에 수정사항이 생겨서 또 인터페이스를 분리하는 행위를 지양한다.
  5. 의존 역전 원칙 - DIP (Dependency Inversion Principle)
    • Class를 참조해야 하는 상황에서 직접 Class를 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙
    • 구현 클래스(변화하기 쉬운것)에 의존하지 말고 인터페이스(변화하기 어려운 것)에 의존
    • 각 클래스간의 결합도(coupling)를 낮추는 것

참고자료

객체 지향 프로그래밍의 4가지 특징ㅣ추상화, 상속, 다형성, 캡슐화

💠 객체 지향 설계의 5가지 원칙 - S.O.L.I.D

카테고리:

업데이트: