▼ Why ?
흔히 "SOLID" 라고 불리는 객체지향 5원칙은 이상적인 객체지향 설계를 하려 한다면 반드시 알아야 하는 소프트웨어 개발 원칙이다. 전에 공부했던 '싱글톤 패턴' 이나 '프록시 패턴' 과 같은 소프트웨어 디자인 패턴들도 모두 이 객체지향 5원칙에 입각해서 설계된 패턴이다. 그리고 이 '객체지향 5원칙' 에 하루라도 빨리 익숙해지는 것이 내가 올바른 객체지향 설계 방식을 하는 데 도움이 될 것 같고, 이번에 하고 있는 북 스터디를 통해 "객체지향의 사실과 오해" 를 읽고 있는 지금 '객체지향 5원칙' 을 공부하면 개념들이 더 잘 와닿을 것 같아서 지금 이렇게 공부하고 정리까지 하게 되었다.
▼객체지향 5원칙 - S.O.L.I.D
객체지향 5원칙이란 ?
- 객체지향 설계에서 지켜야 하는 다음과 같은 5개의 소프트웨어 개발 원칙을 칭하는 말이다.
- SRP(Single Responsibility Principle) : 단일 책임 원칙
- OCP(Open Closed Principle) : 개방 폐쇄 원칙
- LSP(Listov Substitution Principle) : 리스코프 치환 원칙
- ISP(INterface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependancy Inversion Principle) : 의존 역전 원칙
- 객체지향 프로그래밍의 장점이자 중요한 4가지 개념인 '추상화(Abstraction)', '상속(Inheritance)', '다형성(Polymorphism)', '캡슐화(Encapsulation)' 과 함께 '싱글톤(Singleton) 패턴', '프록시(Proxy) 패턴' 같은 소프트웨어 디자인 패턴들의 근간이 되는 것이 '객체지향 5원칙(SOLID)' 이고, 이 점만으로도 객체지향 5원칙을 공부하고 알아둬야 하는 이유가 충분하다.
- 내가 생각하는 '이상적인 객체지향 설계' 는 유지 보수성이 높으며 불필요한 코드나 데이터를 제거해 코드의 복잡성을 낮출 수 있는, 그리고 새로운 요구사항 및 변경사항에 반응할 수 있는, 즉 확장성 코드를 작성하는 것이라 생각한다.
- 객체지향이란 어떤 것인지부터 먼저 이해하자 !
객체지향의 세계 (참고 자료) - GDSC 북 스터디 : 01_협력하는 객체들의 공동체 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 01_협력하는 객체들의 공동체
▼ What ? 이 "협력하는 객체들의 공동체" 라는 챕터에선 실세계를 모방하여 '객체 지향' 이라는 세계를 이해하도록 하는 내용들을 담고 있다. ▼ Summory & Comment 이 챕터를 읽고 나서 기억에 남는 말
ukym-tistory.tistory.com
단일 책임 원칙 (SRP: Single Responsibility Principle)
- '단일 책임 원칙(SRP)' 은
클래스는 단 하나의 책임만 가져야 한다는 원칙이라고 하는 경우도 있다.
➜ 하지만 "객체는 단 하나의 책임만을 가져야 한다" 라고 하는 것이 더 적절한 설명이라고 생각한다.
그 이유는 ?
➜ "객체지향의 사실과 오해" 라는 책에서 '클래스' 는 객체들의 '협력 관계' 를 코드로 옮기는 도구에 불과하며 객체지향에서 클래스가 아닌 '객체에 어떤 책임을 부여해야 할지', '객체 간의 협력이 어떻게 이뤄져야 할지' 등 객체를 중심으로 생각해야 유지 보수성에 유리하고 확장성이 높은 코드를 설계할 수 있다고 했다.
( 여기서 '협력 관계' 는 객체들끼리 요청하고 응답하는 관계를 의미. )
➜ 이는 곧 클래스가 아니라 객체는 단 하나의 책임만을 갖도록 해야 한다고 생각하는 것이 '단일 책임 원칙' 을 제대로 이해하고 있는 것이라고 생각한다.
- 커피 주문 과정을 예로 들어보면,
손님(객체)은 커피를 주문하기만, 캐시어(객체)는 주문을 받고 바리스타에 요청하기만, 바리스타(객체)는 커피를 완성하기만 하도록 한 가지 책임만 신경 쓰면 된다는 것이다.
- 커피 주문 과정을 예로 들어보면,
- 그렇다면, 객체가 단 하나의 책임만을 갖도록 하는 이유가 무엇일까 ?
➜ 앞서 클래스가 아닌 객체를 중심으로 설계해야 한다고 한 이유는, 결국 객체를 담은 것이 곧 클래스이며 그런 클래스 간의 결합도 · 의존성을 낮춰서 프로그램의 유지 보수성을 높여야 하기 때문이다.
➜ 따라서, 객체가 갖고 있는 책임이 많아진다는 것은 객체가 외부에 영향을 미치는 기능(행동)이 많아진다는 것이고, 이후에 객체의 어떤 기능(행동)을 고치게 되면 해당 객체와 협력 관계에 있거나 의존하고 있는 객체들, 즉 수정해야 할 코드들이 많아진다.
- 결론적으로, '단일 책임 원칙' 은 프로그램의 유지 보수성을 높이기 위해 지켜야 하는 원칙이다 !
➜ 하지만, 아래 자료의 내용들을 보면 알 수 있듯이 객체에게 어떤 책임을 부여할지는 개발자가 결정해야 할 사항이고, 따라서 어떻게 설계를 하냐에 따라 책임의 범위가 달라지기 때문에 '단일 책임 원칙' 을 따랐다는 기준은 모호하다고 봐야 한다.
커피숍 - 객체의 책임과 협력 (참고 자료) - GDSC 북 스터디 : 01_협력하는 객체들의 공동체 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 01_협력하는 객체들의 공동체
▼ What ? 이 "협력하는 객체들의 공동체" 라는 챕터에선 실세계를 모방하여 '객체 지향' 이라는 세계를 이해하도록 하는 내용들을 담고 있다. ▼ Summory & Comment 이 챕터를 읽고 나서 기억에 남는 말
ukym-tistory.tistory.com
책임과 행동 (참고 자료) - GDSC 북 스터디 : 02_이상한 나라의 객체 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 02_이상한 나라의 객체
▼ What ? 이 "이상한 나라의 객체" 라는 챕터에선 '객체의 특성' 과 '캡슐화' 를 "이상한 나라의 앨리스" 라는 이야기에 빗대어 설명하고, 객체를 생성할 때 어떤 점을 유의해야 하는지, 그리고 객
ukym-tistory.tistory.com
개방 폐쇄 원칙 (OCP: Open Closed Principle)
- '개방 폐쇄 원칙' 도 클래스가 아닌 "객체는 확장에 개방적(open)이어야 하며, 수정에는 폐쇄적(closed)이어야 한다" 라는 원칙이라 하는 것이 적절하다.
➜ 쉽게 말하면, 감출 것은 감추고 보여줄 것은 보여주자는 말이다. - 이 원칙도 앞서 있었던 '단일 책임 원칙(SRP)' 과 비슷하게, 객체 하나를 수정할 때 해당 객체에 의존하는 다른 객체들도 줄줄이 수정해야 하는 번거로움을 막기 위해 생겨난 원칙이다.
- 코드를 추가하는 작업(확장)은 유연하게 하며, 단 객체를 직접적으로 '수정'하는 일은 발생하지 않도록 추상화를 잘 활용해서 설계를 하게 되면, 이 원칙을 만족시켰다라고 할 수 있는 것이다.
( 물론, 추후에 객체를 직접적으로 수정하는 일이 없도록 설계하는 것은 개발자의 역량에 달렸다고 생각한다. )
➜ 캡슐화, 다형성, 모듈화를 가능하게 하는 설계 원칙 !
캡슐화 (참고 자료) - GDSC 북 스터디 : 02_이상한 나라의 객체 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 02_이상한 나라의 객체
▼ What ? 이 "이상한 나라의 객체" 라는 챕터에선 '객체의 특성' 과 '캡슐화' 를 "이상한 나라의 앨리스" 라는 이야기에 빗대어 설명하고, 객체를 생성할 때 어떤 점을 유의해야 하는지, 그리고 객
ukym-tistory.tistory.com
타입과 추상화 (참고 자료) - GDSC 북 스터디 : 03_타입과 추상화 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 03_타입과 추상화
▼ What ? 이번 챕터에서도 "이상한 나라 앨리스" 에 나오는 장면을 가지고 '타입' 과 '추상화' 라는 개념에 대해 다룬다. '타입' 과 '추상화' 가 왜 필요하며 두 개념 사이에 어떤 관계가 있는지, 그
ukym-tistory.tistory.com
리스코프 치환 원칙 (LSP: Listov Substitution Principle)
- '리스코프 치환 원칙' 이란, "부모 객체는 자식 객체로 언제나 치환 가능해야 한다." 를 의미한다.
➜ 즉, '서브 타입' 은 항상 '슈퍼 타입' 을 대체 가능하다는 말이다. - '다형성' 원리를 이용하기 위해 만들어진 법칙이라 생각하면 편할 것 같다.
➜ '슈퍼 타입' 인 Animal의 자식 객체 즉 '서브 타입' 인 Dog, Cat, Duck이 있을 때, '슈퍼 타입' 으로 객체(animal)을 선언했는데 '서브 타입' 의 인스턴스(dog)를 받으면, 즉 업캐스팅(Up-casting)된 상태의 인스턴스로 '슈퍼 타입' 의 메서드를 호출해도 의도대로 동작이 수행되는 것 !
/*
Dog dog = new Dog();
Animal animal = (Animal)dog;
*/
Animal animal = new Dog(); // Up-casting
Dog dog = new Dog();
animal.sound(); // 두 경우 모두, 오버라이딩 된 Dog 클래스의 move 호출
dog.sound(); // @Override
// move() { System.out.println("Bow! Bow!"); }
- 다형성의 장점은 '슈퍼 타입을 이용해서 다양한 타입의 객체를 동일한 타입의 참조변수로 참조할 수 있다' 는 점이다.
➜ '서브 타입' 의 인스턴스를 매개변수에 제공하는 경우가 있다.
- 유연성 ⬆
➜ 객체 배열에 여러 타입(Tv, Computer)의 객체를 저장하고 일관된 타입(Product)로 다룬다. - 가독성 · 유지 보수성 ⬆, 중복성 ⬇
➜ 동일한 타입의 참조변수(b)로 다양한 객체(new Tv(), ne wComputer())들을 다룰 수 있기 때문에 ! - 확장성 ⬆
➜ 기존 클래스를 변경하지 않고도 새로운 클래스나 새로운 객체(new Phone())를추가 가능 !
- 유연성 ⬆
public class PolymorphismExample {
public static void main(String[] args) {
Buyer b = new Buyer();
// Tv t = new Tv();
// b.buy(t);
b.buy(new Tv());
b.buy(new Computer());
System.out.println("현재 " + b.money + "원이 남아있습니다.");
b.printHistory();
}
}
class Buyer {
int money = 1000;
// Product p1 = new Tv();
// Product p2 = new Computer();
// 객체 배열에 여러 타입(Tv, Computer)의 객체를 저장하고 일관된 타입(Product)로 다룬다.
Product[] item = new Product[10];
int i = 0;
void buy(Product p) {
if(money < p.price) { // 구매 불가
return;
}
// 제품 구입
money -= p.price;
// 구매한 제품 목록에 추가
item[i++] = p;
}
// 구한 제품 목록 출력
void printHistory() {
int sumPrice = 0;
String itemList = "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < item.length; i++) {
if(item[i] == null) break;
sum += item[i].price;
sb.append(item[i]);
}
itemList = sb.toString();
System.out.println(sum);
System.out.println(itemList);
}
}
class Product {
int price;
Product(int price) {
this.price = price;
}
}
class Tv extends Product {
Tv() {
super(100);
}
@Override
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() {
super(200);
}
@Override
public String toString() { return "Computer"; }
}
- LSP 원칙을 따르는 대표적인 케이스가 Collection 인터페이스가 있다.
➜ Collection 타입의 참조변수에 'LinkedList' 타입의 인스턴스를 할당했다가, 다른 자료형인 'HashSet' 타입의 인스턴스를 할당해도 'add()' 같은 메서드를 호출할 때 원래의 의도대로 작동한다.
타입과 다형성 (참고 자료) - GDSC 북 스터디 : 03_타입과 추상화 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 03_타입과 추상화
▼ What ? 이번 챕터에서도 "이상한 나라 앨리스" 에 나오는 장면을 가지고 '타입' 과 '추상화' 라는 개념에 대해 다룬다. '타입' 과 '추상화' 가 왜 필요하며 두 개념 사이에 어떤 관계가 있는지, 그
ukym-tistory.tistory.com
다형성 (참고 자료) - '[JAVA] 객체지향 프로그래밍 II (OOP : Object-Oriented Programming) (+ 싱글톤(Singleton))' 수정하기 (tistory.com)
https://ukym-tistory.tistory.com/manage/newpost/10?returnURL=https%3A%2F%2Fukym-tistory.tistory.com%2Fentry%2F%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-II-OOP-Object-Oriented-Programming&type=post
ukym-tistory.tistory.com
List와 ArrayList (참고 자료) - [PCCP] 스택 · 큐 · 정렬 · 그리디 알고리즘 [10] — Uykm_Note (tistory.com)
[PCCP] 스택 · 큐 · 정렬 · 그리디 알고리즘 [10]
▼ Why ? 이번 문제는 전에 배웠던 해시함수와 정렬을 활용하여 해결하는 문제이기 때문에 한 번 풀어보았다. ▼ 과일 장수 문제 정보 경화는 과수원에서 귤을 수확했습니다. 경화는 수확한 귤 중
ukym-tistory.tistory.com
인터페이스 분리 원칙 (ISP: INterface Segregation Principle)
- 앞서 나왔던 '단일 책임 원칙(SRP)' 는 '객체' 의 단일 책임을 강조했다면, '인터페이스 분리 원칙(ISP)' 은 '인터페이스' 의 단일 챔임을 강조하는 원칙이라고 생각하면 될 것 같다.
➜ 즉, '커피 주문' 이라는 작업을 위해 생성한 인터페이스는 '커피 주문' 이라는 작업을 수행하는데 필요한 것만 담은 인터페이스만을 제공하도록 해서 인터페이스의 크기가 지나치게 커지지 않도록 주의하자는 원칙이다.
- 쉽게 말하면, 추상화를 하면서 공통적인 부분은 하나의 인터페이스로 묶어주고 다른 부분은 따로 인터페이스를 분리해주자는 것이다.
➜ 예를 들어, 최신 스마트폰들은 웬만해선 동일한 기능을 갖고 있기 때문에 인터페이스를 분리해줄 필요가 없다.
➜ 하지만, 구형 핸드폰들까지 다뤄야 하는 경우라면, 모두 같은 핸드폰이라고 하나의 인터페이스로 다룰 것이 아니라 최신 스마트폰에는 있지만 구형 핸드폰엔 없는 기능들은 인터페이스를 따로 빼주자는 것이다.
- 단, SRP 원칙과 달리 'ISP 원칙' 에 따라 인터페이스를 분리시켰다면 나중에 뭔가 수정할 것이 생기더라도, 수정해성 안된다는 인터페이스의 취지에 따라 그렇게 분리한 인터페이스를 또다시 분리하는 행위를 해선 안된다는 점을 주의해야 한다 !
추상화 (참고 자료) - GDSC 북 스터디 : 03_타입과 추상화 — Uykm_Note (tistory.com)
GDSC 북 스터디 : 03_타입과 추상화
▼ What ? 이번 챕터에서도 "이상한 나라 앨리스" 에 나오는 장면을 가지고 '타입' 과 '추상화' 라는 개념에 대해 다룬다. '타입' 과 '추상화' 가 왜 필요하며 두 개념 사이에 어떤 관계가 있는지, 그
ukym-tistory.tistory.com
의존 역전 원칙 (DIP: Dependancy Inversion Principle)
- '의존 역전 원칙(DIP)' 이란, 객체에서 어떤 클래스를 참조해서 사용해야 하는 경우에, 그 클래스를 직접 참조하지 말고 해당 클래스의 '슈퍼 타입' 클래스 혹은 '인터페이스' 로 참조하라는 원칙이다.
➜ 쉽게 말하면, 구현 클래스가 아니라 인터페이스에 의존하라는 의미이다 !
➜ 클래스 간의 결합도를 낮추기 위함 !
( 이 원칙이 왜 중요한지는 앞선 원칙들을 이해하면 충분히 알 수 있는 내용인 것 같다. ) - 이 원칙은 인터페이스가 왜 필요한지를 원칙으로 만든 느낌이기도 하고, 우리가 평소에 많이 듣던 그런 주의사항에 해당하는 이야기라 객체지향 5원칙 중에 가장 익숙하게 다강는 원칙인 것 같다.
프록시 패턴 & DIP (참고 자료) - [Software Design Pattern] 프록시 패턴 (Proxy Pattern) — Uykm_Note (tistory.com)
[Software Design Pattern] 프록시 패턴 (Proxy Pattern)
▼ Why ? What ? GDSC - Web 커리큘럼에서 빈(Bean)을 등록하는 과정에서 빈(Bean)을 수동으로 등록하는 것과 자동으로 등록하는 것의 차이를 잘 모르는 것 같아 다시 공부를 했다. 빈을 수동으로 등록할
ukym-tistory.tistory.com