객체지향 3요소

yanju·2022년 11월 29일
0
post-thumbnail

추상화

추상화란 어떤 영역에서 필요로 하는 속성이나 행동을 추출하는 작업이다.

추상화 덕분에 관심이 쏠리는 부분에 더욱 집중할 수 있다.

객체간에 공통적인 특성을 뽑아서 묶어내면 추상화를 할 수 있다.

커다란 운동장에 수많은 자동차가 주차되어 있다고 가정한다.

이 자동차들을 그룹화할 때 추상화를 이용할 수 있다.

어떤 사람은 탈 수 있는 승객의 수를 기준으로 승합차와 승용차로 그룹화할 수 있다.

어떤 사람은 문의 개수에 따라 세단과 쿠페로 그룹화하려고 할 것이다.

어떤 사람은 자동차 관리법에 명시된 대로 그룹화하려고 할 것이다.

이처럼 사물들의 공통적인 특징을 파악해서 이를 하나의 개념으로 다루는 수단이 추상화다.

추상화는 객체지향에서 중요하다.

추상화가 없다면 각각의 개체, 즉 각각의 자동차들을 구분해야한다.

추상화가 없다면 모든 자동차마다 엔진 오일을 교환하는 기능을 추가해야 한다.

switch (자동차 종류)
	case 아우디: break;
	case 벤츠: break;
	case BMW: break;
	...

추상화를 이용하면 다음과 같이 Car의 구현체만 바꾸면 된다.

어떤 새로운 자동차가 추가되어도 changeEngineOil 메소드의 코드는 바뀌지 않는다.

void changeEngineOil(Car c) {
	c.changeEngineOil();
}

changeEngineOil(new 아우디());
changeEngineOil(new 벤츠());
changeEngineOil(new BMW());

3요소

캡슐화, 상속, 다형성이 있다.

캡슐화 (Encapsulation)

캡슐화는 정보 은닉의 특징이 있다.

정보은닉을 통해 높은 응집도와 낮은 결합도를 갖도록 한다.

높은 응집도와 낮은 결합도를 유지할 수 있도록 설계해야 요구사항 변경에 유연하게 대처할 수 있다.

소프트웨어는 결합이 많을수록 문제가 많이 발생한다.

한 클래스가 변경이 발생하면 의존하는 다른 클래스도 변경해야 할 가능성이 높아진다.

public class ArrayStack {
	public int top;
	public int[] itemArray;
	public int stackSize;

	public void push(int item) {
		itemArray[++top] = item;
	}

}

public class StackClient {
	
	main() {
		ArrayStack st = new ArrayStack(10);
		st.itemArray[++st.top] = 20;
	}
}

위에서 ArrayStack 클래스는 내부가 모두 public 키워드를 붙여 외부에 공개되어 있다.

StackClient는 push 메서드를 사용하지 않고 직접 배열에 값을 저장할 수 있다.

이런 경우 ArrayStack과 StackClient 클래스는 강한 결합이 발생한다.

상속

상속은 속성이나 기능의 상속, 즉 재사용을 위해 존재한다고 오해하고 있다.

이는 사실이 아니다.

만약 ArrayList를 상속 받아 Stack 클래스를 만들었다고 가정을 하면, Stack은 ArrayList에 있는 isEmpty, size, add, remove 메서드를 그대로 사용하길 원한다.

기능의 재사용이라는 측면으로만 보면 성공적이라고 볼 수 있다.

그러나 ArrayList 클래스에 정의된 스택과 관련 없는 수많은 연산이나 속성도 같이 상속 받는다.

이런 불필요한 속성은 도움이 되기보다는 도움이 안 될 가능성이 크다.

class MyStack<String> extends ArrayList<String> {
		public void push(String e) {
			add(element);  // ArrayList의 메소드
		}

		public String pop() {
			return move(size() - 1);  // ArrayList의 메소드
		}
}

기본적으로 상속 관계는 ‘is a kind of 관계’가 성립되어야 한다.

다음 문장은 성립하지 않는다.

Stack is a kind of ArrayList

클래스의 일부 기능만 재사용하고 싶은 경우에는 위임(delegation)을 사용하면 된다.

위임은 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 하는 것이다.

상속 관계는 클래스 사이의 관계지만 위임은 객체 사이의 관계다.

다음은 위임을 사용해 상속을 대신하는 과정이다.

  1. 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만든다.
  2. 자식 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경한다.
  3. 자식 클래스에서 상속 관계를 제거하고, 위임 속성 필드에 부모 클래스의 객체를 생성해 대입한다.
  4. 자식 클래스에서 사용된 부모 클래스의 메소드에서도 위임 메서드를 추가한다.
  5. 컴파일하고 잘 동작하는지 확인한다.
class MyStack<String> {
	private ArrayList<String> arrList = new ArrayList<>();

	public void push(String e) {
		arrList.add(element);
	}

	public String pop() {
		return arrList.remove(arrList.size() - 1);
	}

}

다형성

다형성은 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력이다.

다형성은 일반화 관계와 함께 자식 클래스를 개별적으로 다룰 필요 없이 한 번에 처리할 수 있게 하는 수단을 제공한다.

우리가 과제로 했던 택시와 버스는 손님을 태우는 메소드인 boardingPassengers 메소드 호출은 같더라도 택시, 버스에 따라 로직이 다르다.

이것이 다형성 개념이다.


// 택시
class Taxi extends PublicTransport {
	
    @Override
	public void boardingPassengers(int passengers) {
		if (status != TaxiStatus.NORMAL) {
			System.out.println(Utils.convertRedErrorMsg("차량이 운행중이지 않습니다."));
			return;
		}

		if (numberOfPassengers + passengers > capacity) {
			System.out.println(Utils.convertRedErrorMsg("최대 승객 수 초과"));
			return;
		}

		changeState(TaxiStatus.RUN);
		numberOfPassengers += passengers;
		payAmount = calculatePay();
	}
}

// 버스
class Bus extends PublicTransport {

	@Override
	public void boardingPassengers(int passengers) {
		if (status != BusStatus.RUN) {
			System.out.println(Utils.convertRedErrorMsg("차량이 운행중이지 않습니다."));
			return;
		}

		if (numberOfPassengers + passengers > capacity) {
			System.out.println(Utils.convertRedErrorMsg("최대 승객 수 초과"));
			return;
		}

		numberOfPassengers += passengers;
	}
}

main {
	PublicTransportation car = new Bus();
	car.boardingPassengers(5);

	car = new Taxi();
	car.boardingPassengers(2);
}

다형성을 사용하지 않는 경우 클래스별로 다르게 처리해주어야 하지만 다형성을 사용하는 경우에는 구체적으로 현재 어떤 클래스 객체가 참조되는지와 무관하게 프로그래밍을 할 수 있다.

새로운 탈 것을 나타내는 클래스가 자식 클래스로 추가되더라도 main 코드는 영향을 받지 않는다.

0개의 댓글