OOP vs FP: 객체와 함수로 행위를 전달하는 두 가지 방식

Posted on Sep 29, 2023

객체와 함수: 서로 다른 패러다임, 유사한 목적

객체 지향 프로그래밍(OOP)과 함수형 프로그래밍(FP)은 서로 다른 철학을 가지고 있지만, 공통적인 목표를 위해 유사한 개념을 활용합니다. 대표적인 예는 매개변수 분리를 위한 객체와 함수의 사용입니다. 이 두 개념은 특정 문맥에서는 서로 대체 가능한 구조로 작동합니다.

예를 들어, OOP에서는 객체 foo가 내부 상태 x를 가지며, 외부 입력 y를 사용하는 메서드 bar를 통해 계산을 수행할 수 있습니다. 같은 동작은 FP에서도 함수 hello(x, y)를 클로저(closure)로 변환하거나, 고차 함수(higher-order function)로 표현하여 x를 분리한 함수로 만들 수 있습니다. 즉, 고정된 컨텍스트(x)를 캡처하여 새로운 함수를 만들어 전달 가능하다는 점에서 함수는 객체의 행위를 대체할 수 있습니다.

이처럼 객체와 함수는 특정 조건하에 서로 대체할 수 있으며, 실제로 이는 전략 패턴(Strategy Pattern)처럼 특정 행위를 외부에서 주입하고 실행하는 구조에서 특히 자주 나타납니다.

함수/객체를 전달할 때의 차이: 이름과 형태, 그리고 테스트 용이성

객체와 함수는 외부로 전달될 때 다음과 같은 차이점을 가집니다:

  • 객체는 명시적으로 인터페이스를 구현하고 메서드명을 고정해야 합니다.
  • 함수는 타입 시그니처만 만족하면 익명 함수나 클로저를 유연하게 사용할 수 있습니다.

이는 의존성 주입(Dependency Injection)이나 테스트(mocking) 측면에서 함수가 더 유리한 경우가 많습니다. 예를 들어 고차 함수를 사용하는 구조는 테스트 시점에 간단히 람다를 넣어 빠르게 mocking할 수 있는 반면, OOP에서는 객체 생성을 위한 보일러플레이트가 많아지는 단점이 있습니다.

또한, 동시성 환경에서 상태를 어떻게 분리하느냐에 따라 함수형 접근 방식이 불변성(immutability)을 활용해 더 안전하게 구성되는 장점도 고려할 수 있습니다.

Java 예제 분석 (OOP 방식)

// 클래스 A는 테스트용 main 메서드를 가집니다.
public class A {
	public static void main(String[] args) {
		int x = 1;
		int y = 3;

		System.out.println(new Foo() {
			public int apply(int x) {
				return x + y;
			}
		}.apply(x));

		System.out.println(new Sum(y).apply(x));
	}
}
public interface Foo {
	public int apply(int x);
}
public class Sum implements Foo {
	private int y;

	public Sum(int y) {
		this.y = y;
	}

	public int apply(int x){
		return x + this.y;
	}
}

OOP 방식은 명시적인 타입 안정성과 IDE 지원의 용이성이라는 강점이 있습니다. 하지만 클로저가 없는 언어에서는 y와 같은 상태를 유지하기 위해 클래스를 따로 만들어야 하고, 매번 생성자를 통해 값을 주입해야 하므로 코드가 장황해질 수 있습니다. 특히 동시성 문제에 있어 내부 상태가 공유된다면, 동기화를 고려해야 합니다.

Go 예제 분석 (함수형 방식)

func main() {
	x := 1
	y := 3

	fmt.Println(sum(y)(x))

	fmt.Println(func(x int) int {
		return y + x
	}(x))
}
func sum(y int) func(int) int {
	return func(x int) int {
		return x + y
	}
}

Go는 일급 함수(first-class function)와 클로저를 지원함으로써, 상태 캡처와 행위 전달을 간단히 처리할 수 있습니다. 함수형 스타일은 테스트, 병렬 처리, 그리고 함수 조합(composition) 측면에서 매우 유연합니다.

단점으로는 IDE에서 함수 시그니처 기반 자동완성이나 타입 추론이 부족한 경우, 가독성이나 유지보수에서 어려움이 생길 수 있습니다. Go는 함수형 언어는 아니지만 일급 함수와 클로저를 통해 FP 스타일을 부분적으로 지원합니다. 단, 포인터 전달 시 상태 불변성이 보장되지 않기 때문에 주의가 필요합니다.

비교 요약

항목Java (OOP)Go (FP)
상태 전달필드로 보관 (this.y)클로저로 캡처
실행 단위메서드함수
표현 방식인터페이스 + 구현체고차 함수 + 클로저
테스트 용이성Mock 객체 필요간단한 함수 전달로 mocking 가능
동시성 고려상태 공유 시 동기화 필요캡처된 불변 상태로 상대적으로 안전
코드량 / 유지보수성많고 반복적간결하고 유연
디버깅 / 트레이싱IDE 친화적, 디버깅 편리익명 함수 다중 중첩 시 디버깅 난이도 상승

결론

OOP와 FP는 각각의 철학을 바탕으로 매개변수의 분리, 상태와 행위의 전달 등을 다룹니다. 특정 상황에서는 객체와 함수가 같은 역할을 수행할 수 있으며, 전달 방식에서는 함수가 더 유연한 장점을 가집니다.

다음과 같은 기준으로 선택할 수 있습니다:

  • 유지보수성과 테스트 유연성이 우선이라면 함수형 전달 방식이 유리합니다.
  • 명확한 타입 구조와 IDE 지원을 원한다면 객체 기반 방식이 안정적입니다.
  • 멀티스레드나 병렬 환경에서는 불변 상태 유지가 쉬운 함수 기반 구현이 더 안전할 수 있습니다.

언어가 제공하는 기능을 바탕으로, 목표하는 코드의 특성과 개발 문화에 맞는 선택이 중요합니다.