JUINTINATION

객체 지향 원리 본문

JAVA객체지향디자인패턴

객체 지향 원리

DEOKJAE KWON 2023. 7. 2. 17:41
반응형

자바 언어의 가장 큰 특징은 객체 지향 언어, 즉 객체 지향 프로그래밍에서 사용하는 언어라는 점이다.

  • 객체 지향 프로그래밍은 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다.
  • 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만든다.

객체 지향 언어의 특징

  • 추상화
    • 집합을 구성하는 개체들을 '일반화'하는 것
      • 어떤 영역에서 필요로 하는 속성이나 행위를 추출하는 작업
      • 관심 있는 부분에 더욱 집중할 수 있음
      switch(자동차 종류) {
          case 아우디:
              // 아우디 엔진 오일 교환 코드
          case 벤츠:
              // 벤츠 엔진 오일 교환 코드
      }
      
      // 자동차 종류가 추가될 때마다 위의 코드를 수정하지 않고 아래와 같이 해결
      
      void changeEngineOil(Car c) {}  
          c.changeEngineOil();
      }
  • 캡슐화
    • 객체의 필드과 메서드를 하나로 묶고 실제 구현 내용 일부를 내부에 감추어 은닉
      • 코드의 결합도를 낮추고 응집도를 높히며 설계
      • 요구사항 변경에 영향을 최소화하여 유연하게 대처가 가능하게 함
      • ex) getter&setter 메서드는 필드를 캡슐화한 것임
      public class ArrayStack {
          public int top;
          public int[] itemArray;
          public int stackSize;
          public ArrayStack(int n) {
              itemArray = new int[n];
              stackSize = 0;
              top = -1;
          }
      }
      
      public class StackClient {
          public static void main(String[] args) {
              ArrayStack st = new ArrayStack(10);
              st.itemArray[++st.top] = 20;
              System.out.print(st.itemArray[st.top]);
          }
      }
      
      // 위의 코드를 캡슐화한 코드는 아래와 같음
      
      public class ArrayStack {
          private int top;
          private int[] itemArray;
          private int stackSize;
          public ArrayStack(int n) {
              itemArray = new int[n];
              stackSize = 0;
              top = -1;
          }
          public void push(int i) {
              itemArray[++top] = i;
          }
          public int peek() {
              if (stackSize > 0) return itemArray[top];
              else System.exit(1);
          }
      }
      
      public class StackClient {
          public static void main(String[] args) {
              ArrayStack st = new ArrayStack(10);
              st.push(20);
              System.out.print(st.peek());
          }
      }
  • 일반화(상속)
    • 클래스 자체를 캡슐화
      • 변경에 대비할 수 있는 설계를 가능하게 함
      • 수퍼클래스, 또는 부모 클래스 등의 기존의 클래스로부터 속성과 동작을 상속
      • 새로운 클래스가 추가되더라도 클라이언트는 영향을 받지 않음
      while (장바구니에 과일이 있다) {
          switch (과일 종류) {
              case 사과:
                  가격 총합 += 사과 가격;
              case 배:
                  가격 총합 += 배 가격;
          }
      }
      
      // 과일 종류가 추가될 때마다 위의 코드를 수정하지 않고 아래와 같이 해결
      
      int computeTotalPrice(List<Fruit> fruits) {
          int total = 0;
          for (Fruit fruit : fruits) {
              total += fruit.price;
          }
          return total;
      }
  • 다형성
    • 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력
      • 일반화 관계와 함께 자식 클래스를 개별적으로 다룰 필요 없이 한 번에 처리할 수 있게 하는 수단을 제공
      • 부모 클래스의 참조 변수는 자식 클래스가 상속받은 부모 클래스의 변수와 메서드에 접근할 수 있음
      • 코드를 간결하게 할 뿐 아니라 변화에도 유연하게 대처할 수 있음
      public abstract class Pet {
          public abstract void talk();
      }
      public class Cat extends Pet {
          public void talk() {
              System.out.println("야옹");
          }
      }
      public class Dog extends Pet {
          public void talk() {
              System.out.println("멍멍");
          }
      }
      public class Main {
          public static void main(String[] args) {
              Pet[] p = {new Cat(), new Dog()};
              for (int i = 0; i < 2; i++) {
                  p[i].talk();
              }
          }
      }

일반화의 위임

어떤 클래스의 일부 기능만을 사용하고 싶을 경우에는 위임을 사용해야 한다.

두 자식 클래스 사이에 ‘is a kind of 관계’가 성립되지 않을 때 상속을 사용하면 불필요한 속성이나 연산(빚이라 해도 될 것이다)도 물려받게 된다.

예를 들어 다음과 같이 stack을 만들었다고 했을 때 push, pop 메서드를 통하지 않고 ArrayList에 직접 접근하여 값을 얻을 수 있기 때문에 스택의 무결성 조건인 LIFO를 위배한다.

class MyStack<E> extends ArrayList<E> {
    public void push(E e) {
        add(e);
    }
    public E pop() {
    	return remove(size() - 1);
    }
}

일반화를 위임으로 변환하는 프로세스

  1. 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만든다. 이 속성 필드를 this로 초기화한다.
class MyStack<E> extends ArrayList<E> {
    private ArrayList<E> aList = this;
    public void push(E e) {
        add(e);
    }
    public E pop() {
        return remove(size() - 1);
    }
}
  1. 서브 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경한다.
class MyStack<E> extends ArrayList<E> {
    private ArrayList<E> aList = this;
    public void push(E e) {
        aList.add(e);
    }
    public E pop() {
        return aList.remove(aList.size() - 1);
    }
}
  1. 서브 클래스에서 일반화 관계 선언을 제거하고 위임 속성 필드에 슈퍼 클래스의 객체를 생성해 대입한다.
class MyStack<E> {
    private ArrayList<E> aList = new ArrayList<>();
    public void push(E e) {
        aList.add(e);
    }
    public E pop() {
        return aList.remove(aList.size() - 1);
    }
}
  1. 서브 클래스에서 사용된 슈퍼 클래스의 메서드에도 위임 메서드를 추가한다.
class MyStack<E> {
    private ArrayList<E> aList = new ArrayList<>();
    public void push(E e) {
        aList.add(e);
    }
    public E pop() {
        return aList.remove(aList.size() - 1);
    }
    public boolean isEmpty() {
        return aList.isEmpty();
    }
    public int size() {
        return aList.size();
    }
}
  1. 컴파일하고 잘 동작하는지 확인한다.

특수화

  • 일반화의 역관계, 즉 부모 클래스에서 자식 클래스를 추출하는 과정
  • 특수화가 필요한 경우

어떤 속성이나 연관 관계가 특정 자식 클래스에서만 관련이 있고, 다른 자식 클래스에서는 관련이 없는 경우

  • ex) VIP 멤버들에게만 할인 쿠폰이 발행된다.


피터 코드의 상속 규칙

  1. 자식 클래스와 부모 클래스 사이는 "역할 수행" 관계가 아니어야 한다.
  2. 한 클래스의 인스턴스는 다른 서브 클래스의 객체로 변환할 필요가 절대 없어야 한다.
  3. 자식 클래스가 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행해야 한다.
  4. 자식 클래스가 단지 외부 기능을 재사용할 목적으로 유틸리티 역할의 클래스를 상속하지 않아야 한다.
  5. 자식 클래스가 역할(Role), 트랜잭션(Transaction), 디바이스(Device) 등을 특수화(specialization)해야 한다.

 

1번 규칙 위배

2번 규칙 위배

3번 규칙 점검 불가

4번 규칙 준수

5번 규칙 위배

 

 

위의 클래스 다이어그램을 다음과 같이 수정할 수 있다.

집약(혹은 연관) 관계를 사용하여 클래스 사이의 관계를 표현하여

어느 순간에는 두 역할 모두 수행하지 않을 수 있다는 다중성 또한 표현할 수 있다.

기존의 코드(사람)에 영향을 주지 않고 새로운 역할을 추가할 수 있다.

이는 SOLID 원칙에서 OCP를 준수한 것이다.

728x90
Comments