내가 이해한 객체 지향 프로그래밍 (feat. 그래서 이걸로 어쩌라고,,!?)

노을·2022년 12월 3일
0

java

목록 보기
9/9
post-thumbnail




이 글은 예전에 쓴 OOP의 네가지 특징에 대한 글이 2300번이나 읽혀진 게 부끄러워서 작성하게 되었다.
그 글에는 오타도 있고 객체지향을 잘 정리해놓은 것도 아닌데 생각보다 많은 사람이 봐서 놀랐다.
이 글에서는 좀 더 객체지향을 잘 이해할 수 있도록 글을 작성할 예정이다. (물론 난 아직 객체를 100% 이해한 게 아니라 또 허접한 글일 수도 있다.)

내가 처음에 자바를 배웠을 때, 인터페이스랑 추상클래스의 차이가 궁금해서 찾아봤는데 인터페이스는 다중상속이 되고, 추상메서드만 가질 수 있고,,, 뭐 이런 것들이 나왔다.
그땐 인터페이스랑 추상클래스의 차이는 알겠는데...

그래서 뭐 어쩌라고?

라는 생각을 가졌던 것 같다. 그 어쩌라고를 설명해보겠다.






💡객체지향 프로그래밍 특징


객체지향 프로그래밍(OOP)이란?

상태와 행동을 가지는 객체들이 서로 협력하는 구조로 프로그래밍 하는 것.



☑️캡슐화 (데이터 보호)


캡슐화는 쉽게 말해서 하나의 클래스에 필드와 메서드를 넣는 거라고 할 수 있다. 그래서 클래스 안에 있는 필드!를 보호한다. (private로 해서)

예를 들어, 자동차 클래스 Car에 연료 fuel랑 연료탱크용량 fuelTankCapasity이라는 필드가 있다고 가정하자.

public class Car {
	public int fuel; //연료
	public int fuelTankCapasity; //연료탱크용량
}

여기서는 fuel, fuelTankCapasitypublic으로 설정해서 외부에서 존재도 알 수 없게 만들었다.
근데! 여기서 문제가 있다. 외부에서는 fuelfuelTankCapasity 값을 볼 수도 없고 수정할 방법도 없다.

방법1. getter와 setter 추가하기

public class Car {

	private int fuel;
	private int fuelTankCapasity;
    
	public int getFuel() {
		return fuel;
	}
    
	public void setFuel(int fuel) {
		this.fuel = fuel;
	}
    
	public int getFuelTankCapasity() {
		return fuelTankCapasity;
	}
    
	public void setFuelTankCapasity(int fuelTankCapasity) {
		this.fuelTankCapasity = fuelTankCapasity;
	}
	 
}

하지만 이 방법은 객체지향적이라고 할 수 없다.
이 클래스로 객체를 만들어서 사용할 때, gettersetter를 보고

아 이 클래스에는 fuelfuelTankCapasity라는 속성이 있군,,,

라고 생각을 하게 된다. 이렇게 열심히 private으로 설정해서 감추려고 했는데 gettersetter 때문에 다 들통난 것이다...


방법2. 추상적인 메서드로 조작하게 하기

이럴 때는 추상적인 메서드를 public으로 제공해서 사용자가 속성은 모른채 속성들을 조작할 수 있게 해야 한다.

public class Car {
	
	private int fuel;
	
	private int fuelTankCapasity;

	public int getPercentFualRemaining() {
		return (fuel/fuelTankCapasity)*100;
	}

}

남은 연료 퍼센트를 제공하는 메서드 getPercentFualRemaining()를 만들었다.
이렇게 하면 직접적으로 속성을 안보여주면서 핵심적인 부분을 보게 할 수 있다.
( ➡️ 추상적으로 보여준다.)



☑️상속 (코드 재사용)

클래스는 클래스를 상속 받을 수 있기 때문에 코드를 재사용 할 수 있다는 내용이다.

예를 들면, 고양이 Cat 클래스를 만들어야 한다고 생각해보자.

class Cat {
	
	private String name;
	
	public Cat(String name) {
		this.name = name;
	}

    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

이렇게 이름 속성을 넣고, 그걸 받아 객체를 생성하는 생성자를 만들고, cry() 메서드 하나를 만들 수 있을 것이다. 근데,,, 이 코드가 왠지 길다. 어떡하지?

찾아보니 이미 만들어져 있는 Animal 추상클래스가 있다!!
Animal 추상클래스를 상속받으면 코드를 재사용할 수 있다.

class Animal { 
	
	private String name;
	
	public Animal(String name) {
		this.name = name;
	}


	public abstract void cry(){
			System.out.println("ㅜㅜㅜ");
	}
}
class Cat extends Animal {

    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

이건 Animal 추상클래스를 상속받는 Cat 클래스이다.
Animal의 추상메서드인 cry() 메서드를 오버라이딩하였다.
이렇게 하면 Cat 클래스의 코드가 짧아진 걸 볼 수 있다.




근데 여기서 나는 인터페이스랑 추상클래스의 차이가 헷갈렸었다. 추상클래스도 상속,,, 인터페이스도 상속의 느낌 아닌가? 부모를 복제해서 자식이 생기는 느낌??

그럼 Animal 추상클래스 대신 Animal 인터페이스를 만들어보자.

interface Animal { 
	
	public void cry();
}

이런식으로 만들 수 있을 것이다.



class Cat implements Animal {

	private String name;
	
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

그럼 이렇게 인터페이스를 받은 클래스는 인터페이스의 추상메서드를 오버라이드 하면 된다.


근데 여기서 잠깐,,, 그럼 인터페이스를 안 받았았다면 Cat 클래스는 어떤 모습일까?


class Cat{

	private String name;
	
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}

아마 이런 모양일 것이다.
인터페이스를 받든 안받든 코드의 양이 줄어들지 않았다.
코드 재사용이 되었다고 말할 수 없다!


그럼 추상클래스는 코드 재사용을 하는 거고, 인터페이스는 무슨 역할을 하는 거지? 라는 생각이 들 것이다. 그건 추상화/다형성에서 설명하겠다.



☑️ 추상화(핵심적인 코드만 보여주기) / 다형성 (객체 갈아끼우기)

추상화랑 다형성은 같이 이해하는 게 좋은 것 같다.

추상화 : 객체의 공통되는 부분을 추출해서 불필요한 부분은 감추자.
다형성 : 분명 똑같은 객체를 사용했는데 다르게 행동한다.

보통 이렇게 많이 정의가 되어있는데 이런 말은 30번 들어도 이해가 안될 수도 있다.
코드로 다형성이랑 추상화를 이해하는 게 좋다.


추상화

일단 추상화를 한 예시를 살펴보자.
이렇게 멤버와 가격을 받아 할인 가격을 return하는 할인 정책 인터페이스를 만들고,
이 인터페이스를 implements 하는 클래스를 만들어보자.

public interface DiscountPolicy {
	 int discount(Member member, int price);
}
public class FixDiscountPolicy implements DiscountPolicy {
	
	private int discountFixAmount = 1000; //1000원 할인
	
	@Override
	public int discount(Member member, int price) {
		if (member.getGrade() == Grade.VIP) {
			return discountFixAmount;
		} else {
			return 0;
		}
	}
}

member의 등급이 VIP이면 1000원을 할인해준다.

이렇게 할인정책이라는 것의 공통 속성을 빼내어 DiscountPolicy라는 인터페이스를 만들어냈다. 이게 추상화!

근데 추상화를 해서 뭐가 좋은 걸까? 이건 다형성을 이해하면 알게 된다.



다형성

위 코드로 내가 작성했는데
갑자기 기획자가 와서

??? : 우리는 1000원 할인이 아니라 10% 할인으로 바꿀 겁니다~^^

라고 했다면 어떻게 해야 할까?


만약 추상화를 안해놨다면, DiscountPolicy만 존재할 것이다.

public class DiscountPolicy {
	
	private int discountFixAmount = 1000; //1000원 할인
	
	public int discount(Member member, int price) {
		if (member.getGrade() == Grade.VIP) {
			return discountFixAmount;
		} else {
			return 0;
		}
	}
}

그럼 결국 DiscountPolicy의 코드를 수정해야 한다.
하지만 추상화를 잘 해놓았다면,,,?

public interface DiscountPolicy {
	 int discount(Member member, int price);
}
public class FixDiscountPolicy implements DiscountPolicy {
	
	private int discountFixAmount = 1000; //1000원 할인
	
	@Override
	public int discount(Member member, int price) {
		if (member.getGrade() == Grade.VIP) {
			return discountFixAmount;
		} else {
			return 0;
		}
	}
}

public class RateDiscountPolicy implements DiscountPolicy {
	 private int discountPercent = 10; //10% 할인
	 
	 @Override
	 public int discount(Member member, int price) {
		 if (member.getGrade() == Grade.VIP) {
			 return price * discountPercent / 100;
		 } else {
			 return 0;
		 }
	 }
	}
}

이런 식으로 RateDiscountPolicy를 추가하면 될 것이다.

그러면 다형성을 이용해 객체를 사용해보자.

DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 기존 1000원 할인정책
DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 바꾼 10% 할인정책

이렇게 상위(인터페이스) 타입으로 바꿔서 사용한다면 원래 1000원 할인을 할 때에서 10% 할인으로 바꿀 때 코드 수정을 최소화할 수 있다.

왜나하면 똑같은 타입의 객체지만 다르게 동작하기 때문이다. (이게 바로 다형성 !)

클래스와 다르게 인터페이스는 코드 중복을 없애는 게 아니라, 변경에 용이한 코드를 만들 수 있게 해준다.



그리고 이 DiscountPolicy 객체를 사용하는 입장에서는

인터페이스 타입 객체를 쓰고 있기 때문에 메서드가 어떻게 구현된 것인지는 알 수 없다. (이게 바로 추상화 !)









객체지향을 알게 되면 사실 상속, 추상화, 다형성, 캡슐화 이 네 가지가 서로 연결되어서 구분지을 수 없다는 생각을 하게 된다.
결국 객체지향적인 설계객체가 서로 영향을 받지 않아 내용을 변경했을 때 코드의 변경은 이루어지지 않고, 객체를 사용하는 사람(혹은 객체)에게는 내부 구현을 숨기면서 자료의 핵심을 보작할 수 있어야 한다는 것이다.

난 처음에 다형성을 이해해서 객체지향적인 설계가 무엇인지 조금이나마 알게 되었을 때, 정말 너무 신기하고 행복했다. 아 이래서 인터페이스가 존재하고,,, 클래스는 이런 거고 결국 좋은 설계는 객체들 간의 협력이구나라고 생각했는데 그게 생각을 하는 시간이 기뻤다.

누군가 이 글을 보고 단순히 추상클래스는 다중상속이 안되기 때문에 인터페이스가 만들어졌다~~~ 이런 걸 암기하는 게 아니라 객체지향을 "이해"하게 돼서 나와 같은 기쁨을 느낀다면 정말 좋을 거 같다.


0개의 댓글