프로그래밍 패러다임이란?
말그대로 프로그래밍의 패러다임 형태입니다. 프로그래머에게 프로그래밍의 관점을 갖게 도와주는 개발 방법론입니다. 크게 선언형의 함수형, 명령형의 객체지향형과 절차지향형으로 나뉩니다. 각각 살펴봅시다.
1) 선언형과 함수형 프로그래밍
선언형 프로그래밍은 프로그램이 어떤(무엇을) 동작을 하는지에 중점을 두고 풀어내는지에 집중하는 패러다임이고 그 일종에 함수형 프로그래밍이 있습니다. 함수형 프로그래밍은 입력에 의해서만 출력이 결정되고 외부요인에 영향을 받지않게 순수 함수들로만 쌓아 로직을 구현하는 패러다임입니다. 또한 함수를 함수에서 반환이 가능하고, 변수나 메소드에도 할당 될 수 있는 일급 객체로 취급하는 고차함수를 활용해서 재사용성을 높이는 프로그래밍 방법입니다.
일급 함수(First-class Functions)
: 함수를 변수에 할당하고, 함수의 매개변수로 전달하거나 함수의 반환값으로 사용할 수 있는 특성을 말합니다.
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// 함수를 변수에 할당
Function<Integer, Integer> square = x -> x * x;
// 함수를 매개변수로 전달
System.out.println(applyFunction(square, 5));
}
// 고차 함수 - 함수를 매개변수로 받는 함수
public static int applyFunction(Function<Integer, Integer> function, int value) {
return function.apply(value);
}
}
불변성(Immutability)
: 객체의 상태가 변하지 않는 것을 의미합니다. 자바에서는 'final'키워드를 사용하여 변수를 불변으로 만들 수 있습니다.
public class Main {
public static void main(String[] args) {
final int x = 10; // 불변 변수 선언
// x = 20; // 컴파일 에러 - 변수 값 변경 불가능
}
}
고차 함수(Higher-order Functions)
: 함수를 다루는 함수를 의미합니다. 예를 들어, 함수를 반환하거나 함수를 인자로 받는 함수 등을 말합니다.
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// 고차 함수 - 함수를 반환
Function<Integer, Function<Integer, Integer>> add = x -> y -> x + y;
Function<Integer, Integer> addTwo = add.apply(2);
System.out.println(addTwo.apply(3)); // 출력: 5
}
}
이렇게 자바에서도 함수형 프로그래밍의 개념을 활용하여 코드를 작성할 수 있습니다. 함수형 프로그래밍을 더 깊게 이해하고 싶다면, 자바의 java.util.function 패키지에 있는 함수형 인터페이스들을 살펴보시는 것이 도움이 될 것입니다.
2) 객체지향 프로그래밍
객체지향 프로그래밍(OOP)은 소프트웨어를 설계하고 구현하는 방법론 중 하나로, 현실 세계의 객체들을 소프트웨어로 모델링하는 것을 중심으로 합니다. 이러한 객체들은 속성과 행위를 가지며, 상호작용하면서 프로그램이 동작합니다. OOP의 주요 특징으로는 추상화, 캡슐화, 상속, 다형성이 있습니다.
추상화
: 복잡한 시스템으로부터 핵심적인 개념 또는 기능을 간추려내는 것을 의미합니다.
캡슐화
: 객체의 상태(데이터)와 행위(메서드)를 하나로 묶고, 외부에서 접근을 제한하는 것을 의미합니다. 이를 통해 데이터를 보호하고 객체 간의 결합도를 낮출 수 있습니다.
상속
: 부모 클래스의 속성과 메서드를 자식 클래스가 상속받아 재사용할 수 있는 메커니즘입니다. 이를 통해 코드 재사용성과 계층 구조를 만들어 소프트웨어의 설계와 구현을 용이하게 합니다.
다형성
: 하나의 인터페이스나 추상 클래스를 통해 여러 객체를 동일하게 다루는 기능을 말합니다. 메서드 오버로딩(Overloading)과 메서드 오버라이딩(Overriding)을 통해 구현됩니다.
이제 자바로 객체지향 프로그래밍의 예시를 살펴보겠습니다.
// 동물 클래스
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println("동물이 소리를 내고 있습니다.");
}
}
// 개 클래스 - 동물을 상속받음
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void speak() {
System.out.println(name + "이(가) 멍멍 짖고 있습니다.");
}
}
// 고양이 클래스 - 동물을 상속받음
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void speak() {
System.out.println(name + "이(가) 야옹하고 있습니다.");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("멍멍이");
Animal cat = new Cat("야옹이");
dog.speak(); // 멍멍이이(가) 멍멍 짖고 있습니다.
cat.speak(); // 야옹이이(가) 야옹하고 있습니다.
}
}
위의 예시에서는 동물을 나타내는 Animal 클래스를 상속받아 개와 고양이 클래스를 구현하였습니다. 이를 통해 상속을 이용한 코드의 재사용성과 다형성을 확인할 수 있습니다.
설계 원칙
SOLID는 객체지향 프로그래밍에서 유지보수성, 확장성, 재사용성 등을 향상시키기 위한 다섯 가지 설계 원칙을 나타냅니다. SOLID는 다음의 다섯 가지 원칙으로 구성됩니다.
- 단일 책임 원칙 (Single Responsibility Principle, SRP): 하나의 클래스는 하나의 책임만 가져야 합니다. 클래스가 여러 책임을 가지게 되면 변경이 발생했을 때 다른 책임에 영향을 줄 수 있습니다.
- 개방-폐쇄 원칙 (Open-Closed Principle, OCP): 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 합니다. 즉, 기존의 코드를 변경하지 않고 새로운 기능을 추가할 수 있어야 합니다.
- 리스코프 치환 원칙 (Liskov Substitution Principle, LSP): 서브 타입은 언제나 자신의 베이스 타입으로 대체될 수 있어야 합니다. 즉, 상속 관계에 있는 클래스들은 부모 클래스의 기능을 필요에 따라 대체할 수 있어야 합니다.
- 인터페이스 분리 원칙 (Interface Segregation Principle, ISP): 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다. 따라서 하나의 큰 인터페이스보다는 작고 구체적인 여러 개의 인터페이스가 있어야 합니다.
- 의존 역전 원칙 (Dependency Inversion Principle, DIP): 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 추상화에 의존해야 합니다. 또한, 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항은 추상화에 의존해야 합니다.
3) 절차형 프로그래밍
절차형 프로그래밍은 컴퓨터 프로그램을 단계적인 절차로 구성하는 프로그래밍 패러다임입니다. 이러한 프로그램은 일련의 연속적인 절차나 명령어들의 집합으로 이루어져 있으며, 데이터와 절차(알고리즘)를 분리하여 생각합니다. 주로 순차적으로 실행되는 명령어들로 구성되며, 명령어의 실행 순서가 결과에 영향을 미칩니다.
절차형 프로그래밍은 주로 다음과 같은 특징을 갖습니다:
- 순차적인 실행: 프로그램은 주로 순차적으로 명령어를 실행합니다. 한 번에 하나의 명령어가 실행되며, 다음 명령어는 이전 명령어가 완료된 후에 실행됩니다.
- 절차적 분해: 프로그램은 보통 작업을 작은 단위로 나누어 절차(서브루틴 또는 함수)로 구성됩니다. 이러한 절차는 보통 하위 태스크를 수행하는 데 사용됩니다.
- 전역 데이터: 데이터는 주로 전역 변수로 사용되며, 절차들 간에 데이터가 공유됩니다. 이로 인해 데이터 은닉과 캡슐화가 부족할 수 있습니다.
- 단순한 제어 흐름: 조건문과 반복문을 사용하여 제어 흐름을 관리합니다. 프로그램의 흐름은 보통 조건문과 반복문에 따라 단순하게 변경됩니다.
- 코드 재사용성의 어려움: 함수 또는 절차가 다른 곳에서 사용되기 어렵고, 코드 재사용성이 낮을 수 있습니다.
그렇다면 어떤 패러다임이 가장 좋을까요?
답은 "그런 것은 없다."입니다. 디자인 패턴과 마찬가지로 비즈니스 로직,서비스의 특징을 고려해 정하는 것이 가장 좋습니다. 하나의 패러다임을 기반으로 통일해 서비스를 구축하는 것도 좋지만, 여러 패러다임을 조합하여 상황과 맥락에 따라 패러당미 간의 장점만 취해 개발하는 것이 좋습니다.
'Reading' 카테고리의 다른 글
Array, LinkedList 특징, 차이점, 장단점 (0) | 2024.04.03 |
---|---|
프로시저(Procedure) (0) | 2024.04.02 |
[DB] 인덱스(Index) (0) | 2024.03.27 |
스레드 (0) | 2024.03.26 |
1.디자인 패턴(Design Pattern) (0) | 2024.03.17 |