
"프로그래밍을 어떻게 할 것인가?"에 대한 사고 방식 또는 코딩 스타일
즉, 문제를 어떻게 구조화하고, 어떻게 해결할지를 결정하는 프로그래밍의 큰 틀
코드를 작성하는 방법론, 관점, 패턴 정의
같은 문제를 어떤 관점에서 접근하느냐에 따라 코드의 작성 방식이 달라지며 패러다임에 따라 코드의 구조, 가독성, 유지보수, 확장성이 크게 달라짐
print(sum([1,2,3,4,5]))
from functools import reduce
print(reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]))
class SumCalculator {
int sumUpTo(int n){
int sum = 0;
for (int i=1; i<=n; i++) sum += i;
return sum;
}
}
sum = 0
for i in range(1,6):
sum += i
print(sum)
"어떻게"가 아닌 "무엇"을 할 것인지를 설명하는 프로그래밍 방식
SQL, HTML, React의 JSX, 함수형 언어가 대표적
SELECT name FROM users WHERE age > 20;
<h1>Hello, World!</h1>
함수를 수학적 함수처럼 순수하게 작성하고, 상태를 변경하지 않는 패러다임
함수가 자신의 외부 상태를 변경하거나, 외부에 영향을 주는 모든 행동
//순수 함수
function add(a, b) {
return a + b;
}
//순수 하지 않은 함수
let count = 0;
function increment() {
count += 1; // 외부 상태 변경 (부수 효과 발생)
}
//함수형 인터페이스를 인자로 받는 고차 함수
public static int applyFunction(int x, Function<Integer, Integer> func) {
return func.apply(x);
}
public static void main(String[] args) {
// 람다식을 함수로 전달
int result = applyFunction(5, num -> num * 2);
System.out.println(result); // 결과: 10
}
//Stream API에서 고차 함수 사용
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// map, filter, reduce 모두 함수를 인자로 받는 고차 함수
int sumOfSquares = numbers.stream()
.filter(num -> num % 2 == 0)
.map(num -> num * num)
.reduce(0, Integer::sum);
System.out.println(sumOfSquares); // 결과: 20
JavaScript, Scala, Haskell에서 활용
const add = (a, b) => a + b;
jdk 1.8 이전의 자바는 객체지향 프로그래밍을 지원
jdk 1.8부터
함수형 프로그래밍 패러다임을 지원하기 위해 람다식, 생성자 레퍼런스, 메서드 레퍼런스를 도입했고
//람다식
Comparator<String> compLambda = (o1, o2) -> o1.length() - o2.length());
//생성자 레퍼런스 (클래스명::new)
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
//메서드 레퍼런스 (객체명::메서드명)
names.forEach(System.out::println);
선언형 프로그래밍을 위해 스트림(Stream) 같은 표준 API 등을 추가
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(num -> num % 2 == 0) // 짝수만 선택
.map(num -> num * num) // 제곱
.reduce(0, Integer::sum); // 합산
System.out.println(sum); // 👉 결과: 20
//어떤 작업을 할 것인지만 명시, "어떻게"의 작업은 Java 내부가 처리
함수형 프로그래밍은 선언형 프로그래밍의 한 종류로,
선언형은 넓은 개념, 함수형은 선언형의 한 종류
선언형 프로그래밍
함수형 프로그래밍
선언형은 단순히 "결과만 말하는 코드"지만,
함수형은 "결과 + 상태 변경 금지 + 순수 함수" 까지 포함
객체와 객체 간의 상호작용을 통해 문제를 해결하는 프로그래밍 방식
Java, C++, Python 등이 대표적
// 부모 클래스
class Animal {
String name;
void speak() {
System.out.println("Some sound");
}
}
// 자식 클래스
class Dog extends Animal {
@Override
void speak() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.name = "Buddy";
myDog.speak();
}
}
/*
Woof!
*/
유지보수성과 확장성이 뛰어나며, 복잡한 시스템 설계에 적합
하지만 객체 간 결합도가 높아질 위험 존재
SRP, Single Responsibility Principle (단일 책임 원칙)
모든 클래스는 각각 하나의 책임만 가져야 한다. (A는 A에 대한 책임만)
OCP, Open Closed Principle (개방-폐쇄 원칙)
기존 코드는 잘 변경하지 않으면서도 확장은 쉽게 할 수 있어야 한다.
LSP, Liskov Substitution Principle (리스코프 치환 원칙)
프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
(부모 객체에 자식 객체를 넣어도 문제가 없어야 함)
ISP, Interface Segregation Principle (인터페이스 분리 원칙)
하나의 일반적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 한다.
DIP, Dependency Inversion Principle (의존 역전 원칙)
추상화된 인터페이스나 상위 클래스를 두어, 변하기 쉬운 것에 의존해도 변화에 영향받지 않게 해야 한다.
코드를 위에서 아래로 순차적으로 실행하는 전통적인 방식
C 언어가 대표적
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
}
printf("합계: %d\n", sum);
return 0;
}
변수 선언 → 반복문 → 합산 → 출력
위에서 아래로 순서대로 진행됨.
| 구분 | 절차형 | 객체 지향 | 함수형 | 선언형 |
|---|---|---|---|---|
| 중심 사고 | 순서, 절차 중심 | 객체와 메세지 중심 | 함수와 조합 중심 | 결과(What) 중심 |
| 코드 흐름 | 명령어를 순차 실행 | 객체 간 메세지 전달 | 함수 조합 | 무엇을 할 지 선언 |
| 데이터 처리 | 전역/공유 변수 | 객체 속성 관리 | 데이터 불변 | 상태 숨김, 최종 결과만 기술 |
| 상태 변경 | 자유롭게 상태 변경 | 객체 내에서 변경 | 상태 변경 최소화 | 상태 추상화 |
| 부수 효과 | 있음 | 상대적으로 적음 | 최소화 지향 | 없음 또는 숨김 |
절차형: "어떻게"를 하나씩 직접 지시하는 방식
객체 지향: "누가" (객체) 처리할지를 설계하는 방식
함수형: "무엇을" 함수로 조합할지를 선언하는 방식 (선언형의 하위 개념)
선언형: "결과"만 명시하고, 처리 방법은 시스템이 알아서 하도록 하는 방식