[Design Pattern] 객체 지향 원리
Posted: Updated:
자바 디자인 패턴 스터디를 하며 ‘Java 객체 지향 디자인 패턴’ 교재를 정리한 글입니다.
추상화
- 속성이나 행동을 추출하는 작업
- 필요한 부분에 집중 가능
- 여러 개체들을 집합으로 파악
- 예시) 자동차
- 아우디의 자동차들
- 벤츠의 자동차들
- 아우디와 벤츠 두 곳의 자동차들을 ‘자동차’ 로 언급할 수 있음
- 자동차는 아우디와 벤츠의 추상적 개념
- 구체적 개념보다 추상적 개념에 의존해야 함
캡슐화
요구 사항
요구사항의 변경은 당연한 것이다.
- 응집도: 클래스나 모듈 안의 요소들이 얼마나 밀접하게 관련되어 있는지
- 결합도: 어떤 기능을 실행하는 데 다른 클래스나 모듈들에 얼마나 의존적인지
- 응집도는 높게, 결합도는 낮게
캡슐화란?
- 정보 은닉(information hiding)을 통한 높은 응집도, 낮은 결합도
- 알 필요가 없는 정보는 외부에서 접근하지 못하도록 제한함
- 예시) 자동차의 가속 페달을 밟았을 때 어떤 과정을 거쳐 속도가 올라가는 지 몰라도 됨
정보 은닉
- 멤버가 public 으로 외부에 공개되어 있는 Array Stack
public class ArrayStack {
public int top;
public int[] itemArray;
public int stackSize;
public ArrayStack(int stackSize) {
itemArray = new int [stackSize];
top = -1;
this.stackSize = stackSize;
}
public boolean isEmpty() { // 스택이 비어있는지 검사
return (top == -1);
}
public boolean isFull() { // 스택이 꽉 차 있는지 검사
return (top == this.stackSize -1);
}
public void push(int item) { // 스택에 아이템 추가
if(isFull()) {
System.out.println("Inserting fail! Array stack is full!");
}
else {
itemArray[++top] = item;
System.out.println("Inserted Item: "+item);
}
}
public int pop() { // 스택의 탑에 있는 아이템 반환
if(isEmpty()) {
System.out.println("Deleting fail! Array Stack is empty!");
return -1;
}
else {
return itemArray[top--];
}
}
public int peek() {
if (isEmpty()) {
System.out.println("Peeking fail! Array Stack is empty!");
return -1;
}
else {
return itemArray[top];
}
}
}
- Stack Client
public class StackClient {
public static void main(String[] args) {
ArrayStack st = new ArrayStack(10);
st.itemArray[++st.top] = 20;
System.out.println(st.itemArray[st.top]);
}
}
push 메서드, pop 메서드를 사용하지 않고 직접 배열에 값을 저장할 수 있음.
→ ArrayStack과 StackClient 클래스 간 강한 결합 발생
- 자료구조의 형태와 관련된 항목들을 private 로 은닉
private int top;
private int[] itemArray;
private int stackSize;
- 사용하는 코드를 수정
public class StackClient {
public static void main(String[] args) {
ArrayStack st = new ArrayStack(10);
st.push(20);
System.out.println(st.peek());
}
}
일반화 관계
일반화는 또 다른 캡슐화
- 객체 지향 프로그래밍 관점에서의 상속 관계
※ 속성이나 기능의 재사용으로 한정하면 안 됨 - 여러 개체들이 가진 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립시키는 과정
- 과일(바나나, 사과, 배, 오렌지)
- 변경사항에 유연성 있게 대처할 수 있음
- 새로운 종류의 과일이 추가되어도 코드를 수정할 필요가 없음
is a kind of
관계가 성립해야 함- 성립하지 않을 경우 상속을 사용하면 불필요한 속성, 연산도 물려받게 됨
- ArrayList 클래스를 상속받은 Stack의 경우 → Stack
is a kind of
ArrayList.(거짓)
위임
- 클래스 일부 기능만 재사용하고 싶을 경우 위임(delegation)을 사용
- 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 위임
- 일반화 관계: 클래스 사이의 관계
- 위임: 객체 사이의 관계
- 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 위임
- 위임을 사용한 일반화 대신 과정
ArrayList 클래스를 상속받은 Stack의 위임
public class MyStack<String> extends ArrayList<String> {
public void push(String element) {
add(element);
}
public String pop() {
return remove(size() - 1);
}
}
- 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만들고, this로 초기화
public class MyStack<String> extends ArrayList<String> {
private ArrayList<String> arList = this;
public void push(String element) {
add(element);
}
public String pop() {
return remove(size() - 1);
}
}
- 서브 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경
public class MyStack<String> extends ArrayList<String> {
private ArrayList<String> arList = this;
public void push(String element) {
arList.add(element);
}
public String pop() {
return arList.remove(arList.size() - 1);
}
}
- 서브 클래스에서 일반화 관계 선언 제거, 위임 속성 필드에 슈퍼 클래스의 객체를 생성해 대입
public class MyStack<String> {
private ArrayList<String> arList = new ArrayList<>();
public void push(String element) {
arList.add(element);
}
public String pop() {
return arList.remove(arList.size() - 1);
}
}
- 서브 클래스에서 사용된 슈퍼 클래스의 메서드에도 위임 메서드 추가
public class MyStackDelegation<String> {
private ArrayList<String> arList = new ArrayList<>();
public void push(String element) {
arList.add(element);
}
public String pop() {
return arList.remove(arList.size() - 1);
}
public boolean isEmpty() {
return arList.isEmpty();
}
public int size() {
return arList.size();
}
}
- 기능을 재사용할 때는 위임 사용하기
집합론 관점의 일반화 관계
- 일반화 관계는 수학의 집합론과 매우 밀접
- 부모 클래스 A는 전체 집합의 A, 자식 클래스 A1, A2, A3는 A의 부분 집합
- 연관 관계 단순화 가능
- 상호 배타적인 부분 집합으로 나누는 과정에 적용 가능
- 다중 분류: 한 인스턴스가 동시에 여러 클래스에 속할 수 있음
- 쇼핑몰의 회원 등급을 구매액에 따른 VIP 회원, 일반 회원으로 분류하고 동시에 쇼핑몰과 동일한 지역 주민인지 아닌지에 따라 분류하는 것
- 변별자: 집합을 분류하는 분류 기준. UML 에서는 일반화 관계 선 옆에 표시
- 변별자를 사용해 집합을 부분 집합으로 나눌 때
<<다중>>
스테레오 타입을 사용해야 함
- 변별자를 사용해 집합을 부분 집합으로 나눌 때
- 동적 분류: 한 클래스의 인스턴스가 다른 클래스의 인스턴스로 할당될 수 있음
<<동적>>
스테레오 타입 사용
- 일반화는 자식 클래스들의 적절한 합집합과 교집합으로 이루어짐
특수화
- 일반화의 역관계
- 부모 클래스에서 자식 클래스를 추출하는 과정
- 어떤 속성이나 연관 관계가 특정 자식 클래스에서만 관련 있고 다른 자식 클래스에서는 관련이 없는 경우
다형성
- 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력
- 자식 클래스를 한 번에 처리할 수 있게 함
- 애완동물 중 고양이, 강아지, 앵무새 등 각각의 우는 소리를
talk
연산으로 표현
- 애완동물 중 고양이, 강아지, 앵무새 등 각각의 우는 소리를
public abstract class Pet {
public abstract void talk();
}
public class Cat extends Pet {
@Override
public void talk() {
System.out.println("야옹");
}
}
public class Dog extends Pet {
@Override
public void talk() {
System.out.println("멍멍");
}
}
public class Parrot extends Pet {
@Override
public void talk() {
System.out.println("안녀엉");
}
}
public class Main {
public static void main(String[] args) {
Pet p = new Parrot();
p.talk();
}
}
실행 결과
안녀엉
피터 코드의 상속 규칙
- 상속의 오용을 막기 위해 상속의 사용을 엄격하게 제한하는 규칙
- 한 가지라도 만족하지 않으면 상속을 쓰면 안 됨
- 자식 클래스와 부모 클래스 사이는
역할 수행
관계가 아니어야 함- 사람과 운전자, 회사원에서 운전자는 사람이 수행하는 역할 중 하나이며 회사원도 어떤 순간에 수행하는 역할
- 한 클래스의 인스턴스는 다른 서브 클래스의 객체로 변환할 필요가 절대 없어야 함
- 사람이 운전자의 역할과 회사원의 역할을 수행할 때 객체 변환 작업이 필요
- 자식 클래스가 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행해야 함
- 자식 클래스가 단지 일부 기능을 재사용할 목적으로 유틸리티 역할을 수행하는 클래스를 상속하지 않아야 함
- 자식 클래스가 역할, 트랜잭션, 디바이스 등을 특수화해야 함
댓글남기기