[TIL] 210803
1. 객체지향 프로그래밍 2. 객체지향의 특성 3. UML 4. 객체지향 설계의 5가지원칙 |
1. 객체지향 프로그래밍
객체지향 프로그래밍 : 프로그램을 객체로 구성하는 것
- 프로그램이 거대화 되면서 등장함
- 큰 프로그램을 만드는 방법으로 작게 나눠서 만든 후 합치는 방법을 택한 것
객체란?
- 개념적 용어 : 객체
- 기술적 용어 : class, instance
- 객체는 각각 작은 기능을 수행하고 객체가 모여서 큰 프로그램이 됨
즉, 객체지향을 잘 설계하는 법은 일(기능)을 잘게 쪼갠 후 객체에게 위임하고, 서로 협력하게 만드는 것이다.
- 객체를 서로 구분할 필요가 있다.
- 구분하는 기준 : Type
2. 객체지향의 특성
(1) 캡슐화
- 캡슐화는 2가지의 의미가 있다.
- 1. 완성도가 있다.
- 객체는 기능을 수행하는 최소 단위로서 완전함을 갖는다.
- 외부에 의존하는 것이 없도록 해야 함. 있더라도 느슨하게 하는 게 핵심
- 2. 정보가 은닉되어 있다.
- 객체의 정보가 밖으로 전달되거나, 밖에서 객체 내의 정보에 접근하지 못하게 한다.
--> 객체는 스스로 동작할 수 있는 환경을 갖고 있어야 한다.
외부에 의존하거나, 외부의 침략을 제한하여야 한다.
* 접근 지정자
- 종류
- private : 객체 소유.
- protected : 상속된 객체에서도 접근 가능.
- (friendly) : 같은 패키지 내에서 접근 가능. (패키지의 가시성을 높여줌)
- c.p.girl,
- c.p.boy
- 두 가지 패키지가 있을 때 gir, boy마다 공유되는 비밀이 있다.
외부 패키지에서는 비밀에 접근하지 못한다.
girl은 내부에서만 공유, boy도 내부에 서면 공유됨
- public : 모두 다 접근 가능.
- 접근 지정자는 class 객체 자체 선언, 변수 선언에서 사용
- public 객체는 한 파일에 한 개여야 하고 파일 이름과 객체 이름이 같아야 함
(2) 상속
- 상속을 해주는 상위 객체. 상위 / 부모 / super / [추상]
- 상속을 받는 하위 객체. 하위 / 자식 / this / [구체]
- 보통 상속을 공통된 기능을 여러 객체에게 전달하고 싶을 때 쓴다고 생각하는데, 잘못된 개념이다.
- 추상과 구체의 관계에서 상속 관계를 맺는 것이 올바른 상속관계를 포함하는 방법이다.
- 공통된 기능을 제공해주기 위해서 상속을 제공하는 게 잘못된 방법이다!
(3) 추상화
- 상속에서 살펴봤듯이 상위로 갈수록 추상적이고 하위로 갈 수록 구체적이다.
- 추상화된 객체 : 추상체
- 구체적인 객체 : 구상체
- 객체 간의 관계에서 상위에 있는 것이 항상 하위보다 추상적이어야 한다.
ex) 의미적인 추상체
//추상체
class Buy { //일반적인(추상적인) 구매
void buy() {};
}
//구상체
class BuyCard extends Buy { //구체적인 카드 구매
void buy() {};
}
추상 기능을 가진 객체
//추상 기능을 가진 객체
abstract class Buy { // abstract를 하나라도 가진 객체 -> abstract Class
abstract void buy() {}; //내용을 가지지 않고 정의만 가지고 있는 메서드 -> abstract Method
}
//구상체
class BuyCard extends Buy {
@Override
void buy() {}; //구체화 시켜줘야하는 의무를 가짐. 그래서 override해야함
}
객체 자체가 추상체
//모든것이 추상으로만 이루어진 객체 객체
interface class Buy {
void buy() {};
}
//구상체
class BuyCard implements Buy {
@Override
void buy() {}; //구체화 시켜줘야하는 의무를 가짐.
}
*** abstract과 interface의 차이점 ***
abstract는 추상 클래스로 클래스 구현 내부에 추상 메서드가 하나 이상 포함되거나 abstract로 정의된 경우를 말한다.
추상 클래스는 extends만 가능하기 때문에 단일 상속이 목적이다.
상속받은 자식 클래스는 abstract 메서드를 반드시 구현해야 한다는 특징이 있다.
동일한 부모를 가지는 클래스를 묶는 개념으로 상속을 받아서 기능을 확장시키는 것이 목적
interface는 모든 메서드가 추상 메서드인 경우를 말한다. abstract보다 한 단계 더 추상화된 클래스.
interface내의 모든 메서드가 abstract라고 생각하면 됨
인터페이스는 implements를 통해 다중 상속이 가능하다.
구현체가 추상체와 같은 동작을 한다는 것을 보장하는 것이 목적
- 추상 클래스의 목적은 상속을 받아서 기능을 확장시키는 것(부모의 유전자를 물려받는다)
- 인터페이스의 목적은 구현하는 모든 클래스에 대해 특정한 메서드가 반드시 존재하도록 강제하는 역할(부모로부터 유전자를 물려받는 것이 아니라 사교적으로 필요에 따라 결합하는 관계) 즉, 구현 객체가 같은 동작을 한다는 것을 보장하기 위함
(출처 : https://cbw1030.tistory.com/47 )
그렇다면 추상체를 가져야 하는 이유는 무엇일까???
다형성을 위해서이다.
(4) 다형성
추상화를 통해서 다형성을 보장할 수 있다.
interface class Buy {
void buy() {};
}
interface class Refund {
void refund() {};
}
class BuyCard implements Buy,Refund {
void buyCard() {};
@Override void buy() {};
@Override void refund() {};
}
class BuyCash implements Buy, Refund {
void buyCash() {};
@Override void buy() {};
@Override void refund() {};
}
BuyCard bc = new BuyCard(); //BuyCard 타입인 변수 bc에 BuyCard 생성해서 주입한다.
Buy b = new BuyCard(); // Buy를 상속받기 때문에 Buy 추상체로 표현할 수도 있다.
- 이처럼 똑같은 인스턴스지만 BuyCard, Buy와 같이 Type을 여러 가지로 표현할 수 있다.
- 이 다형성을 사용해서 추상 클래스로 표현할 수 있다. (추상화랑 이어지는 부분)
Buy buy = new Buy();
buy.buy();
Buy buyCard = new BuyCard();
BuyCard.buy(); //buycard() 메서드를 수행할 수 없고 buy()만 수행 가능
Buy buyCash = new BuyCash();
Refund cardRefund = new BuyCard();
cardRefund.refund(); //타입이 Refund 이므로 refund() 메서드만 호출 가능
3가지 모두 구체 클래스가 다른 인스턴스이지만 상위 클래스인 Buy로 표현할 수 있음
그렇기 때문에 buy.buy을 수행할 수 있다.
-프로그램은 객체들의 구성이다.
- 쇼핑몰 시스템이 있을 때 구매자가 접근할 때는 쇼핑몰, 판매자가 접근하면 판매채널, 운영자가 접근하면 관리하는 입장에서 시스템을 바라보게 된다.
- 즉, 접근하는 사람에 따라 관점이 달라진다. 이미 시스템 안에 있는 기능이지만 다른 사용자에게 필요 없는 정보를 제공해서는 안된다.
- 카드결제, 현금결제 기능이 있으면, Buy 인터페이스를 통해서 접근하면 buy() 기능밖에 접근할 수 없다.
- card, cash 클래스에 몇 가지 기능이 더 있어도 Buy 인터페이스를 통할 경우 제공해 주지 않음!!
- 만약 refund로 접근하게 되면 buyCard.refund();밖에 접근할 수 없음
- 즉, 필터링의 역할을 수행할 수 있다!!!
- 객체를 구성할 때는 독립적인 기능을 가진 객체를 만들고 상호작용 하도록 해야 하는데, 이렇게 될 경우 상호작용 하는 과정에서 접근하면 안 되는 기능에 접근할 수 있게 되는 경우가 생긴다.
이럴 때 어떤 추상체로서 객체를 호출하게 되면 접근 기능을 제한할 수 있다.(캡슐화)
3. UML
- 객체지향 프로그래밍은 기능을 객체에게 나눠서 수행시킨다는 의미를 가진다.
어떻게 객체지향을 만들 건지를 표현하기 위해서는 아래 2가지를 기억해야 한다.
=> 객체를 어떻게 구분했다. (즉 일을 어떻게 분할했다.)
=> 객체 간의 관계가 어떻게 구성되었다. / 객체 간의 연관관계가 어떠하다.
이러한 내용을 설명하기 위한 도구로는 UML이 있다.
UML이란 Unified Modeling Language의 약자로 통합 모델링 언어이다.
- UML 다이어그램의 종류
- Usecase Diagram
- Sequence Diagram
- Package Diagram
- Class Diagram
- 등등..
- 클래스 다이어그램
- 클래스 다이어그램을 그릴 줄 알고 읽을 줄 아는 것이 중요
- Tool : draw.io / starUML / PowerPoint / Visio / 그림판
- 클래스 다이어그램
(출처 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sule47&logNo=220857776925 )
- 이름 영역, 필드 영역, 메서드 영역으로 나뉜다.
- 각 객체를 나타낸 후 객채들 간의 관계를 나타내야 한다.
A - [관계] -> B 일 때,
일반화 : class B extends A
B가 A를 상속(일반화, 추상적이 됐다)했다. A가 B의 부모이다. B는 A의 구상체이다.
실체화 : class B implements A
implement. B가 A를 implement 한다.
의존 : class A directly accesses(uses) class B’s field or method
클래스 A가 B를 참조한다. A의 클래스에서 B 객체 생성, 사용, 메서드 호출 등을 수행
해당 객체의 참조를 계속 유지하지는 않음
연관 : 연관이 있긴 한데 뭔진 모르겠다!
직접 연관 : A가 B를 사용한다. 소유한다. (A가 주체, B가 대상이 됨)
- 소유하기는 하지만 B가 없어도 지장이 없는 경우 : 집합(빈 마름모)
- 소유하기는 하는데 B가 존재하지 않으면 A가 존재할 수 없는 필수 불가결 : 합성 (꽉 찬 마름모)
(출처 : https://www.nextree.co.kr/p6753/ )
4. 객체지향 설계의 5가지 원칙
객체지향 설계를 하는 5가지 원칙
- (1) S : SRP : 수정이 필요할 경우 수정되는 이유는 하나 때문이어야 한다. 단일 책임원칙.
(객체가 책임을 가지고 있으니까!! 수정할 일이 있을 때 다른 객체의 수정에 영향을 받은 것이 아니어야 한다.)
- (2) O : OCP : 수정에는 닫히고, 확장에는 열어라.
처음부터 객체를 만들 때 수정할 수 있는 형태 말고 기능을 확장하는 형태로 만들어라
- (3) L : LSP : 추상 객체로 사용되는 부분에 구상 객체가 들어가도 아무 문제없어야 한다.
공통기능의 확대로 설계했다면 이 원칙이 지켜지지 않는다
- (4) I : ISP : 위에서 봤던 Buy, Refund의 경우처럼 인터페이스를 사용해서 원하는 기능을 묶어서 하도록 나누어 써라
- (5) D : DIP : 의존 역전 부분(Dependenty Injection)
위의 원식에 대한 설명이 너무 추상체다!!! 구체적인 구상체 예시를 달라..!
- 원칙에 따라서 설계를 해본 결과 공통점이 생김
- 공통점들을 모아둔 것이 디자인 패턴의 23가지 패턴
- 참고자료
- 책 (리팩토링, 리팩토링2탄, 디자인패턴)에 대한 내용을 담은 사이트 : https://refactoring.guru/
- GoF의 Design Patterns: Elements of Reusable Object-Oriented Software (1994)
위의 SOLID 원칙을 지키며 구현해나가는 거보다 패턴을 학습해가면서 원칙을 이해하는 것이 좀 더 효율적