객체지향 프로그래밍

.·2022년 5월 20일
0

객체지향 프로그래밍이란

  • 객체지향 프로그래밍이란 객체의 지역 보존과 캡슐화, 메세징, 동적 바인딩 3가지 특성을 지닌 프로그래밍 기법입니다.

객체의 지역 보존 및 캡슐화

  • 객체들은 상태와 행동을 지니고 있으며 내부의 상태는 숨기고 외부로 행동 즉 메서드를 노출시킵니다.
  • 객체들은 서로 메세지를 주고 받으며 프로그램을 실행시킵니다.
  • 이를 통해 각 객체들의 소스코드들은 다른 객체들의 소스 코드들과 무관하게 작성되고 유지될 수 있습니다.
  • 코드의 재사용성이 높아지고, 프로그램의 복잡성이 낮아집니다.

메세징

  • 메세지를 보낸다는 말은 메서드를 호출한다는 의미입니다.
  • Java에서의 메서드 호출은 static 메서드 호출, 생성자나 private 메서드 호출, 인스턴스 메서드 호출, 인터페이스 메서드 호출이 존재합니다.

동적 바인딩

  • 인스턴스 메서드 호출과 인터페이스 메서드 호출과 실제로 연결되는 메서드는 프로그램의 실행 시점에 결정 됩니다.
  • 이를 동적 바인딩이라고 합니다.
  • 어떤 객체가 해당 메세지를 받느냐에 따라 메세지를 처리하는 방식이 달라집니다.
  • 동적 바인딩을 통해 기존 클래스를 변경하지 않고 기능을 확장시킬 수 있습니다.

객체지향 프로그래밍의 키워드

객체, 클래스, 인스턴스

public class Point {
    public int x = 0;
    public int y = 0;
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

Point originOne = new Point(23, 94);
  • 현실 객체 : 모든 객체들은 상태와 행동을 지닙니다. 현실 세계의 존재하는 모든 개념, 물체들은 객체가 될 수 있습니다.
  • 소프트웨어 객체 : 객체는 상태를 필드에 저장하고, 행동을 메서드에 저장합니다. 내부의 상태를 숨기고 외부와의 소통을 메서드를 통해서만 하는것을 캡슐화 라고 합니다.
  • 클래스 : 소프트웨어 객체의 설계도
  • 인스턴스 : 메모리가 할당된 객체
  • 객체는 클래스의 인스턴스 입니다.
  • 클래스의 생성자 호출하면 메모리가 할당되고 해당 메모리의 주소가 반환됩니다.

상속, 다형성

  • 상속 : 코드 재사용 및 코드 조직화를 위해 기존 클래스를 기반으로 새롭게 정의하는 것
  • 다형성 : 같은 메세지더라도 메세지를 전달받는 객체에 따라 행동이 달라진다.

오버로딩, 오버라이딩

  • 오버라이딩 : 상위클래스에서 정의한 메서드를 하위 클래스에서 재정의 하는 것입니다.
  • 오버라이딩을 하기 위해서는 메서드의 리턴타입, 매개변수의 개수와 타입이 같아야 합니다.
  • 오버로딩 : 메소드의 이름이 같고, 메소드의 매개변수의 개수 또는 타입이 다른 함수를 정의하는 것 - 새로운 함수로 리턴 타입은 관계 없습니다.

추상 클래스 인터페이스

  • 추상 클래스 : 추상 클래스는 일종의 뼈대 입니다. 서브클래스들의 동일한 기능들은 추상 클래스에서 구현해두고 서브클래스에서 구현이 달라지는 부분들은 추상 메서드로 남겨 둡니다.
  • 인터 페이스 : 인터페이스는 메세지를 보내는 방법만 정의한 것입니다. 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 행동을 제공할 것을 약속 합니다.

공통점

  • 구현되지 않은 메서드를 가지고 있으며 이를 하위 클래스에서 구현하도록 합니다.
  • 인스턴스화 시킬 수 없습니다.

차이점

  • 추상클래스는 상속을 하고, 인터페이스는 구현을 합니다.
  • 다중 상속을 불가능하나 다중 구현은 가능합니다.
  • 추상클래스의 목적은 서로 관련 있는 하위 클래스의 공통된 메서드들은 추상클래스에서 구현하고, 하위 클래스에서 구현이 달라지는 메서드들은 하위 클래스에서 구현하도록 만드는 것입니다.
  • 인터페이스의 목적은 서로 관련없는 클래스에서 특정 메서드를 구현하도록 만들 때 사용합니다.
  • Java 8의 디폴트 메서드와 스태틱 메서드의 도입 이전에, 인터페이스의 필드는 public static final 변수만 선언할 수 있고, 모든 메소드들은 public합니다.

객체지향 설계의 5대 원칙 - SOLID

단일 책임 법칙

  • SRP : 클래스는 한가지 이유만으로 변경을 해야합니다.
  • List의 인터페이스의 구현체는 각각 AbstractList와 AbstractSequentialList로 구현됩니다.
  • AbstractList : RandomAccess를 통해 데이터에 접근하는 리스트
  • AbstractSequentialList : Sequential Access를 통해 데이터에 접근하는 리스트
  • AbstractList는 ArrayList로 구현되며 AbstractSequentialList는 LinkedList로 구현 됩니다.
// AbstractList
public E get(int index) {
            Objects.checkIndex(index, size);
            checkForComodification();
            return root.get(offset + index);
        }

// AbstractSequentialList
public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
  • 2가지 리스트는 데이터에 접근하는 방법을 다르게 구현합니다.
  • 만약 2가지 클래스를 나누지 않고 하나의 클래스로 모든것을 구현했다면 RandomAccess의 세부 구현 사항이 바뀌거나, Sequential Access의 세부 구현 사항이 바뀔 때마다 해당 클래스를 변경해야 합니다.
  • 2가지 클래스를 나눔으로써 RandomAccess의 세부 구현 사항을 바꿀 때는 AbstractList를 Sequential Access의 세부 사항을 바꿀 때는 AbstractSequentailList의 코드에 접근하면 됩니다.
  • 이렇게 많은 책임을 지니고 있는 클래스는 프로그램의 복잡성을 증가시킵니다.
  • 같은 이유에 의해 변경되는 것들은 모으고, 다른 이유에 의해 변경되는 것들은 분리해야 합니다.

개방 폐쇄 법칙

  • OCP : 클래스를 수정하지 않고 기능을 확장할 수 있어야 합니다.
  • Collections.sort() 함수는 컬렉션을 정렬시킵니다.
  • Collections.sort()는 Comparable 인터페이스를 상속받은 객체들로 이루어진 리스트를 인자로 받습니다.
  • 정렬을 수행하면서 최종적으로 binarySort()를 호출하고 이 때 리스트 안에 있는 객체들을 Comparable 인터페이스로 변환시키고 compareTo 함수를 호출합니다.
public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;
...
}
  • 기존 Collections class를 수정하지 않고도 Collections.sort()의 기능을 확장시킬 수 있습니다.
  • Collections.sort()에게 전달되는 List 안에 있는 객체들의 종류에 따라 Collection.sort() 함수의 기능은 달라집니다.
  • Integer 객체들이 주어지면 숫자들을 기준으로 비교하고 String 객체들이 주어지면 문자열을 기준으로 비교합니다.
  • Comparable 인터페이스를 구현한 새로운 객체를 전달하면 사용자가 새롭게 정의한 compareTo() 함수를 호출 합니다.
  • OCP 법칙이 가능한 것은 DIP 법칙이 지켜지고 있기 때문입니다.

리스코프 치환 법칙

  • LSP : 하위클래스는 상위 클래스로 대체할 수 있어야 합니다.
  • ArrayList는 AbstractList를 상속받고 있습니다.
  • AbstractList는 데이터에 임의 접근을 하는 리스트입니다.
  • 만약에 ArrayList에서 데이터에 접근하는 get()함수를 순차 접근으로 구현했다면 이는 리스코프 치환 법칙을 위반합니다.
  • 왜냐하면 ArrayList는 AbstractList로 대체할 수 없기 때문입니다.

인터페이스 분리 법칙

  • ISP : 구체적인 기능을 가진 여러가지 인터페이스를 만들어야 합니다.
  • Collection 인터페이스는 List 인터페이스, Set 인터페이스, Queue 인터페이스로 분리됩니다.
  • 이를 통해 각각의 인터페이스는 독립적으로 유지보수 될 수 있습니다.
  • List : 순서가 존재하는 컬렉션
  • Set : 중복되지 않는 원소들로 구성된 컬렉션
  • Queue : FIFO 구조로 이루어진 컬렉션

의존관계 역전 법칙

  • DIP : 구현체에 의존하는 것이 아니라 추상체에 의존해야합니다.
  • Collections.sort() 함수에서 compareTo 메서드를 호출할 때 Comparable 인터페이스에 의존하고 있습니다.
  • 이 때문에 Comparable 인터페이스를 상속받은 객체라면 자유롭게 해당 메세지를 받을 수 있습니다.
  • 의존관계 역전 법칙으로 인해 개방 폐쇄 법칙을 실현시킬 수 있습니다.

Reference

profile
지금부터 공부하고 개발한것들을 꾸준하게 기록하자.

0개의 댓글