-
Object-oriented programming paradigmOnline study/OOP 2023. 3. 8. 12:00
# 객체지향프로그래밍의 특성에 대하여...
--> 총 5가지 특성 정리
#1 Encapsulation
보안성 향상?
개인적으로는...
현실세계의 특성이 프로그램에 반영된 것!
예를 들어
Human 이란 클래스를 만들고
member field 로
int physical_strength = 1 로 정한다.
physical_strength 를 향상시키기 위해선
몇 가지 방법이 있는데
1) gym 에 가서 빡세게 운동을 한다 (goToGym {...})
2) 잘먹는다 (eatWell {...})
3) 잘 쉰다 (restWell {...})
등의 메소드를 human 객체가 호출해야만 한다.
그런데...
외부에서 physical_strength = 100;
이렇게 갑자기 상승시켜 버리면
프로그램에서는 가능한 일이지만
현실 세계에서는
신체적 능력이 이렇게 갑자기 바뀌지 않는다.
시간, 돈, 피와 땀 등이 있어야만
점점 신체적 능력이 올라간다...
그런 현실 세계의 특성이 나름 반영되도록
private int physical_strength = 1; 로 만들면
외부에서 physical_strength = 100;
이렇게 함부로 바꿀 수 없고
바꾸고 싶다면 외부에서
setter, goToGym, eatWell, restWell 등의
메소드를 호출해야만 바꿀 수 있다.
그러면 나름 현실세계의 특성을 반영하는
패러다임이 되는 것 같다.
#2 Abstraction
추상화는 캡슐화와 긴밀하게 연관(closely connected)되어 있다.
추상화를 하는 방법은 여러가지가 있겠지만
2가지 정도만 이해해도
실무에서 어느 정도 응용할 수 있다고 생각한다.
첫 번째는 예를 들어
CoffeeMachine 이란 클래스를 만들고
수많은 기능(메소드)을 넣는다.
1) 커피콩 분쇄 크기를 세팅한다
grindSet( ) {...}
2) 커피콩을 세팅된 분쇄 크기에 맞게 간다
grindCoffeeBeans( ) {...}
3) 간 커피가루를 압축기에 넣어 압축시킨다
compressCoffeePowder( ) {...}
4) 압축기에 고온, 고압으로 물을 통과시킨다
waterCoffeePowder( ) {...}
5) 에스프레소 커피를 10~15초간 추출한다
getEspresso( ) {...}
6) 추출된 커피가루는 별도의 통에 배출한다
dischargeCoffeePowder( ) {...}
7) 다음 커피를 추출할 수 있도록 모든 부품이 준비한다
getReadyForNext( ) {...}
이 수많은 메소드가 보일 때
객체를 사용하는 사용자는 뭐부터 써야하나?
벌써 머리가 아파오기 시작한다.
이 모든 메소드를
getEspressoCoffee( ) {...}
란 메소드 하나에 모두 집어넣고
다른 메소드들은
외부에서 접근할 수 없게
private 으로 제한해 버리면(캡슐화)
객체를 사용하는 사용자는
getEspressoCoffee( ) 메소드 하나면
커피가 만들어지구나! 하면서
좀 더 직관적이고 쉽게
CoffeeMachine 클래스를 활용할 수 있다.(추상화 성공!)
두 번째는
interface 를 사용하여 추상화를 코드로 표현할 수 있다.
interface 를 만들어 해당 interface 에
원하는 행동을 추상 메소드로 넣으면
interface 를 구현한 클래스는
추상 메소드를 강제로 override 해야 하기 때문에
내부의 복잡한 프로세스를 구체적으로 몰라도
강제로 구현해야 하는 메소드를 오버라이드 하면서
추상화를 이루어 갈 수 있다.
#3 Inheritance
상속은 자식(클래스)이
상속받고 싶은 부모(클래스)를
선택해서 물려받는 특성이다.
자식클래스는
부모의 메소드와 필드를 물려받아 사용한다.
특징은
중복된 코드를 줄일 수 있고,
유지 보수가 편리하며,
통일성이 있고
다형성과 연결 된다.
언제 상속이 필요할까?
클래스 관계에서
B가 클래스 A와 is-a 관계일때 사용을 권한다.
반드시 하위 클래스가
상위 클래스의 진짜 하위 타입인 상황에서만
쓰는 것이 좋다고 한다.
예를 들어,
클래스 A를 상속하는
클래스 B를 만드려고 한다면,
“B가 정말 A인가?” 를 생각해봐야 한다.
와인 클래스를 상속하는
레드 와인 클래스가 있을 때
"레드 와인은 와인이다." 가 성립된다.
그 조건이 아니라면
A를 클래스 B의 private 인스턴스로 두면 된다.
즉,
"A는 B의 필수 구성요소가 아니라
구현(implements)하는 방법 중 하나다." 라고 말이다.
#4 Polymorphism
상속을 통한 다형성
다형성을 이용하면
한 가지 클래스나 또는 한 가지 인터페이스를 통해
다른 방식으로 구현(동작)한
클래스를 만들 수 있다.
* 다형성의 최고의 장점 *
sample code 넣을 예정입니다...(정리 중)
하나의 클래스 혹은 인터페이스를 만들고
위의 클래스 혹은 인터페이스를 상속, 구현한
각각 다른 클래스를 만들고 배열에 넣는다.
내부적으로 구현된
다양한 클래스들이
하나의 인터페이스를 구현하거나
또는 동일한 부모 클래스를 상속했을 때
동일한 함수를 어떤 클래스인지 구분하지 않고
공통된 API 로써 호출할 수 있다는 것이 큰 장점이다.
#5 Composition
Favor Composition over inheritance !
=> 필요한 것은 가져와서 조립해라 !
상속에는 문제점이 있다.
1. 족보가 꼬인다.
=> 상속의 깊이가 점점 길어질수록
서로의 관계가 점점 복잡해진다.
상속은 수직적인 관계가 형성된다.
어떤 부모 클래스의 행동을 수정하게 되면
그것을 상속한 모든 자식 클래스에도 영향을 미친다.
새로운 기능을 도입하고자 할 때도
어떻게 상속의 구조를 가져가야 하는지
머리가 복잡해지기 시작함...
또, java나 javascript, typescript 등
여러 개의 부모를 상속할 수 없는 언어들이 한국에서는 대세다.
그럼 어쩌라고?
Dependency injection 을 활용해보자.
클래스의 생성자에
private member field 로 클래스를 전달한다.
(아래 코드에서 sugar 로 전달되는 아이가 클래스 객체임)
이 클래스의 기능을 사용하기 위해서 말이다!
constructor(private beans: number, private sugar: AutomaticSugarMixer) { super(beans); }
하지만 치명적인 단점... ㅜ_ㅜ
클래스와 클래스가 서로 너무 긴밀하게 연결되면 좋지 않다.
다른 클래스로 대체하고 싶으면?
클래스가 업데이트 되면? 등등
클래스 간 커플링은
예상치 못한 문제점을 발생시킨다.
그래서 결국...
확장가능하고
유연한 방법이 필요한데
그것을 디커플링이라 한다.
클래스들 사이에
서로 긴밀히 상호작용하는 경우
클래스 자신을 직접 노출하지 않고
계약서(interface)에 의거해서 의사소통한다.
클래스의 생성자에
private member field 로 interface를 전달한다.
interface의 기능을 사용하기 위해서 말이다!
다른 interface로 대체하고 싶으면?
interface가 업데이트 되면?
interface는 나름 유연한 방법으로 대응이 가능하다...
단, 상속이 필요한 곳에는 상속을 사용하고
composition(조립)이 필요한 곳은
최대한 composition을 활용하는
경험적인 지혜가 필요한 것!