dev-sohee 님의 블로그
객체 지향 세계로의 초대(4대 특징, 클래스 5원칙, 디자인 패턴 3가지) 본문
1990년대 중반, 인터넷의 급격한 성장으로 다양한 하드웨어에서 동일하게 동작하는 소프트웨어가 필요했습니다.
당시 많은 프로그래밍 언어는 특정 플랫폼에 종속적이어서, 여러 기기에서 동일한 소프트웨어를 실행하는 것이 어려웠습니다. 이때 한 번 작성한 코드를 다양한 플랫폼에서 실행할 수 있 자바가 개발되었고 지속적인 발전을 거듭하며 다양한 분야에서 널리 사용되는 주요 프로그래밍 언어 중 하나가 되었습니다.
이 글에서는 객체지향의 4대 특징, 클래스 5원칙과 디자인 패턴을 알아보겠습니다.
* 객체 지향의 4대 특징
* 클래스의 5원칙
* GoF 디자인 패턴 3가지
# 클래스(Class)
객체 지향 프로그래밍의 핵심 개념으로, 객체를 생성하기 위한 규격을 뜻합니다. 클래스는 특정 타입의 객체를 만들어내는 설계 도면의 역할을 합니다.
ex) 사람, 동물, 자동차, 음식, ...
Class Person{
// 구성요소 1. 필드
string Name;
int age;
int weight;
int height;
// 구성요소 2. 생성자
public Person() {
this.Name = "Kim";
this.age = 30;
}
// 구성요소 3. 메서드
public void eat() {
// eat 메서드 구현
}
}
# 객체지향의 4대 특징
1. 캡슐화
2. 상속
3. 추상화
4. 다형성
1. 캡슐화
: 캡슐화는 접근 제어자를 활용하여 특정 객체와 메서드에 접근을 통제하여 정보를 은닉하고 변경을 못하게 막는 것입니다. 객체 내 정보손상, 오용을 방지하고, 확장시 오류를 최소화하기 위해 필요합니다.
* 접근 제어자
1) public : 모두가 접근 가능
2) protected : 상속/같은 패키지 내의 클래스에서 접근 가능
3) default(package-private) : 아무런 접근 제어자를 적어주지 않을 때, 같은 패키지 내의 클래스에서 접근 가능
4) private : 동일한 클래스 내에서만 접근 가능
2. 상속
: 상속은 상위 클래스의 특성을 하위 클래스에서 재사용하고 확장하는 것입니다.
* 상위 클래스의 변수와 메서드 재사용
Class Animal {
string Name;
int age;
int weight;
public void eat() {
//eat 메서드 정의
}
}
Class Zoo extends Animal {
Animal Monkey = new Animal();
Animal Lion = new Animal();
Animal Bear = new Animal();
Animal Fox = new Animal();
Monkey.age = 10;
Lion.weight = 100;
Fox.Name = "Judy";
Monkey.eat();
Lion.eat();
Bear.eat();
Fox.eat();
}
* 상위 클래스의 메서드 확장
Class Animal {
public void eat() {
System.out.println("맛있다!");
}
}
* 오버라이딩(Overriding)
: 하위 클래스에서 상위 클래스의 메서드와 같은 메서드 이름, 같은 인자 리스트를 사용하여 메서드 재정의
Class Monkey {
public void eat() {
System.out.println("바나나 맛있다!!!");
}
}
* 오버로딩(Overloading)
: 하위 클래스에서 상위 클래스의 메서드와 같은 메서드 이름, 다른 인자 리스트를 사용하여 메서드 중복 정의
Class Monkey {
public void eat(int num) {
System.out.println("바나나 %d 개 먹었어!", num);
}
}
3. 추상화
: 추상화는 구체적인 것을 분해해서 관심영역에 있는 특성만 가지고 재조합을 하는 것입니다. (=모델링)
* 병원 애플리케이션의 클래스 모델링
Class Person {
int weight;
int blood;
int age;
public void eat() {
//eat 메서드 정의
}
public void sleep() {
//sleep 메서드 정의
}
}
* 은행 애플리케이션의 클래스 모델링
Class Person {
int age;
int job;
int salary;
public void work() {
//work 메서드 정의
}
}
4. 다형성
: 다형성은 하나의 객체나 메서드가 여러가지 다른 형태를 가질 수 있는 것입니다. 다형성 덕분에 코드가 간결해지고 가독성 및 유지보수성이 향상됩니다.
* 다형성의 예시
1) 오버라이딩 : 하위 클래스에서 상위 클래스의 메서드와 같은 메서드 이름, 같은 인자 리스트를 사용하여 메서드를 재정의한 것
2) 오버로딩 : 하위 클래스에서 상위 클래스의 메서드와 같은 메서드 이름, 다른 인자 리스트를 사용하여 메서드를 중복 정의한 것
3) 업캐스팅 : 하위클래스 타입의 참조 변수를 상위클래스 타입으로 형변환 하는 것
4) 다운캐스팅 : 상위클래스 타입의 참조 변수를 하위클래스 타입으로 형변환 하는 것
객체지향의 구체적인 예시인 디자인패턴이 정상적으로 동작하려면 객체지향 5원칙이 지켜져야 합니다.
클래스 5원칙은 그에 대한 설명입니다.
# 클래스 5원칙(SOLID)
1. Single Responsibility Principle (단일 책임 원칙)
2. Open/Closed Principle (개방-폐쇄 원칙)
3. Liskov Substitution Principle (리스코프 치환 원칙)
4. Interface Segregation Principle (인터페이스 분리 원칙)
5. Dependency Inversion Principle (의존성 역전 원칙)
Single Responsibility Principle (단일 책임 원칙)
클래스는 단 하나의 책임만 가져야 한다는 원칙입니다. 이를 통해 클래스나 모듈이 변경되어야 하는 이유를 최소화하고, 코드의 가독성, 유지보수성, 테스트 용이성을 개선할 수 있습니다.
Open/Closed Principle (개방-폐쇄 원칙)
확장에는 열려 있어야 하고 변경에는 닫혀 있어야 합니다. 즉, 기존의 코드를 변경하지 않고도 기능을 확장할 수 있어야 한다는 원칙입니다. 이는 다형성과 추상화를 통해 구현됩니다.
Liskov Substitution Principle (리스코프 치환 원칙)
상속 관계에서 하위 클래스는 상위 클래스의 기능을 변경하지 않고 대체될 수 있어야 한다는 원칙입니다. 즉, 상위 클래스 타입으로 선언된 변수에 실제 하위 클래스 객체가 대입되어도 기대한 동작을 수행할 수 있어야 합니다.
Interface Segregation Principle (인터페이스 분리 원칙)
클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다. 즉, 인터페이스는 클라이언트에 특화된 작은 단위로 분리되어야 합니다. 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 하고 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있게 합니다.
Dependency Inversion Principle (의존성 역전 원칙)
고수준 모듈은 저수준 모듈에 의존해서는 안 된다는 원칙입니다. 어떤 클래스를 참조해서 사용해야하는 상황이 생긴다면, 그 클레스를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조해야 합니다. 즉, 상속 관계로 이루어진 모듈을 사용할때, 하위 모듈을 직접 인스턴스로 가져오면 안됩니다. 왜냐하면 그렇게 할 경우, 하위 모듈의 구체적인 내용에 의존하게 되어 하위 모듈에 변화가 있을 때마다 상위 모듈의 코드를 자주 수정해야 되기 때문입니다.
**의존이란? : 클래스 간의 의존 관계란 한 클래스가 어떤 기능을 수행하려고 할 때, 다른 클래스의 서비스가 필요한 경우
**고수준 모듈이란? : 어떤 의미 있는 단일 기능을 제공하는 모듈 (interface, 추상 클래스)
**저수준 모듈이란? : 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현 (메인 클래스, 객체)
객체 지향 프로그래밍이 각광받으면서 소프트웨어 설계와 개발에서
반복적으로 등장하는 문제들이 있었습니다.
이를 해결하기 위해 검증된 솔루션을 제공하는 GoF(Gang of Four) 디자인 패턴이 등장하게 되었습니다.
개발자는 문제 해결 능력을 향상시키고, 효율적인 소프트웨어 개발을 위해 이 패턴들을 잘 숙지해야 할 필요가 있습니다.
# GoF(Gang of Four) 디자인 패턴
1. 생성(Creational) 패턴
2. 구조(Structural) 패턴
3. 행위(Behavioral) 패턴
디자인 패턴이란 모듈의 세분화된 역할이나 모듈들 간의 인터페이스 구현 방식을 설계할때 참조할 수 있는 전형적인 해결 방식을 말합니다. 이를 통해 설계 문제, 해결 방법, 해결 방법을 언제 적용해야 할지, 그 결과는 무엇인지 등을 알 수 있습니다.
1995년 GoF(Gang of Four)라고 불리는 Erich Gamma, Richard Helm, Ralph Johnson, John Vissides가 처음으로 디자인 패턴을 구체화하였고 GoF의 디자인 패턴은 소프트웨어 공학에서 가장 많이 사용되는 디자인 패턴입니다. 목적에 따라 크게 생성, 구조, 행위 패턴으로 분류할 수 있으며 각각의 패턴들은 아래의 패턴들로 구성되어 있습니다.
생성(Creational) 패턴 | 구조(Structural) 패턴 | 행위(Behavioral) 패턴 |
추상 팩토리(Abstract Factory) 빌더(Builder) 팩토리 메서드(Factory Method) 프로토타입(Prototype) 싱글톤(Singleton) |
어댑터(Adapter) 브리지(Bridge) 컴포지트(Composite) 데코레이터(Decorator) 퍼싸드(Facade) 플라이웨이트(Flyweight) 프록시(Proxy) |
책임 연쇄(Chain of Responsibility) 커맨드(Command) 인터프리터(Interpreter) 반복자(Iterator) 중재자(Mediator) 메멘토(Memento) 옵서버(Observer) 상태(State) 전략(Strategy) 템플릿 메서드(Template Method) 방문자(Visitor) |
1. 생성(Creational) 패턴
이름 |
의도 |
추상 팩토리 | 구체적인 클래스를 지정하지 않고 인터페이스를 통해 서로 연관되는 객체들을 그룹으로 표현함 |
빌더 | 복합 객체의 생성과 표현을 분리하여 동일한 생성 절차에서도 다른 표현 결과를 만들어낼 수 있음 |
팩토리 메서드 | 객체 생성을 서브클래스로 위임하여 캡슐화함 |
프로토타입 | 원본 객체를 복사함으로써 객체를 생성함 |
싱글톤 | 어떤 클래스의 인스턴스는 하나임을 보장하고 어디서든 참조할 수 있도록 함 |
2. 구조(Structural) 패턴
이름 | 의도 |
어댑터 | 클래스의 인터페이스를 다른 인터페이스로 변환하여 다른 클래스가 이용할 수 있도록 함 |
브리지 | 구현부에서 추상층을 분리하여 각자 독립적으로 확장할 수 있게 함 |
컴포지트 | 객체들의 관계를 트리 구조로 구성하여 복합 객체와 단일 객체를 구분없이 다룸 |
데코레이터 | 주어진 상황 및 용도에 따라 어떤 객체에 다른 객체를 덧붙이는 방식 |
퍼싸드 | 서브시스템에 있는 인터페이스 집합에 대해 하나의 통합된 인터페이스(Wrapper) 제공 |
플라이웨이트 | 크기가 작은 여러 개의 객체를 매번 생성하지 않고 가능한 한 공유할 수 있도록 하여 메모리를 절약함 |
프록시 | 접근이 어려운 객체로의 접근을 제어하기 위해 객체의 Surrogate나 Placeholder를 제공 |
3. 행위(Behavioral) 패턴
이름 | 의도 |
책임 연쇄 | 요청을 받는 객체를 연쇄적으로 묶어 요청을 처리하는 객체를 만날 때까지 객체 Chain을 따라 요청을 전달함 |
커맨드 | 요청을 객체의 형태로 캡슐화하여 재사용하거나 취소할 수 있도록 저장함 |
인터프리터 | 특정 언어의 문법 표현을 정의함 |
반복자 | 내부를 노출하지 않고 접근이 잦은 어떤 객체의 원소를 순차적으로 접근할 수 있는 동일한 인터페이스 제공 |
중재자 | 한 집합에 속해있는 객체들의 상호작용을 캡슐화하여 새로운 객체로 정의 |
메멘토 | 객체가 특정 상태로 다시 되돌아올 수 있도록 내부 상태를 실체화 |
옵서버 | 객체 상태가 변할 때 관련 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 함 |
상태 | 객체의 상태에 따라 동일한 동작을 다르게 처리해야할 때 사용 |
전략 | 동일 계열의 알고리즘군을 정의하고 캡슐화하여 상호교환이 가능하도록 함 |
템플릿 메서드 | 상위클래스는 알고리즘의 골격만을 작성하고 구체적인 처리는 서브클래스로 위임함 |
방문자 |
객체의 원소에 대해 수행할 연산을 분리하여 별도의 클래스로 구성함 |
클래스에 대해 공부하면서 소프트웨어 설계의 중요성과 그 원리를 깊이 이해하게 되었습니다.
특히, 설계 단계에서 이러한 원칙과 패턴을 고려한다면 코드의 유연성과 유지보수성이 보장된,
변경과 확장에 강한 고품질의 소프트웨어를 개발할 수 있을 것입니다.