프로그래밍에서 위임(delegation)은 프로그램의 한 부분에서 다른 부분으로 작업을 넘겨 처리하는 것을 의미합니다. 객체 지향 프로그래밍(OOP)에서 필수적인 기법으로, 각 객체나 메소드가 특정 작업과 책임만 담당하도록 하여 깔끔하고 유지보수하기 좋은 코드를 만들 수 있게 해줍니다.
위임을 이해하고 활용하는 것은 Ruby를 비롯한 객체 지향 언어를 마스터하는 핵심입니다. 위임은 관심사의 분리(separation of concerns)를 촉진하여 코드를 더 모듈화하고, 이해·테스트·리팩토링하기 쉽게 만들어 줍니다.
이 글에서는 Ruby에서 위임을 구현하는 세 가지 방법, 즉 명시적 위임, Forwardable 모듈, 그리고 Rails 전용 도구인 ActiveSupport::Delegate를 자세히 살펴보겠습니다.
Ruby에서의 위임
먼저 메소드 안에서 다른 메소드를 직접 호출하는 명시적 위임(explicit delegation)부터 시작하겠습니다. 이어서 Ruby에 내장된 Forwardable 모듈이 어떻게 위임을 더 간결하게 만들어 주는지 알아보고, 마지막으로 편리한 기능을 갖춘 Rails 전용 위임 도구인 ActiveSupport::Delegate를 다룹니다.
명시적 위임(Explicit Delegation)
가장 단순한 형태의 위임은 한 메소드 안에서 다른 메소드를 직접 호출하는 방식입니다. 이 방식은 위임 대상 메소드가 별도의 객체에 속해 있거나, 더 고급 기법을 사용할 필요가 없을 만큼 위임 구조가 단순할 때 자주 활용됩니다.
Ruby에서 명시적 위임을 구현하는 것은 매우 간단합니다. HP 객체를 사용해 텍스트를 출력하는 Printer 클래스를 예로 들어 보겠습니다.
위 예제에서 Printer 클래스의 print 메소드는 HP 클래스의 format 메소드를 호출함으로써 서식 지정 작업을 명시적으로 위임합니다. 이처럼 인쇄라는 책임을 다른 클래스에 아주 단순하고 읽기 쉬운 방식으로 외주를 주는 셈입니다.
명시적 위임은 관심사의 분리와 유지보수성을 높이는 데 도움이 되기 때문에 실제 애플리케이션에서도 널리 사용됩니다. 예를 들어 여러 서비스 간 통신이 필요한 애플리케이션에서, 한 서비스가 다른 서비스를 호출해 특정 작업을 수행하도록 하는 경우에 흔히 볼 수 있습니다.
또한 명시적 위임은 서드파티 라이브러리를 감싸는 어댑터나 래퍼(wrapper)를 만들 때도 유용합니다. 외부 코드와의 상호작용을 격리할 수 있기 때문입니다.
정리하면, 명시적 위임은 Ruby에서 객체 간 작업을 넘기는 가장 직관적이면서도 효과적인 방법입니다. 메소드 안에서 다른 메소드를 호출하기만 하면 손쉽게 책임을 위임할 수 있고, 코드 유지보수성과 관심사 분리 모두 향상됩니다.
Ruby의 Forwardable 모듈
Forwardable 모듈은 Ruby에 내장된 표준 라이브러리로, 명시적 위임보다 더 간결하고 유연하게 메소드를 위임할 수 있는 방법을 제공합니다. 클래스에 이 모듈을 포함(include)하면 def_delegator와 def_delegators 같은 메소드를 사용할 수 있어 위임이 한결 수월해집니다.
Forwardable 모듈을 사용하려면 클래스에 모듈을 포함한 뒤, def_delegator(단일 메소드 위임) 또는 def_delegators(여러 메소드 동시 위임)를 호출하면 됩니다. 새로운 Formatter 클래스를 추가한 Printer 예제에 Forwardable을 적용해 보겠습니다.
위 예제에서는 def_delegator를 사용해 Formatter 클래스의 format 메소드를 위임했습니다. 여러 메소드를 한 번에 위임해야 한다면 def_delegators를 사용하면 됩니다. 예를 들어 Formatter 클래스에 capitalize 같은 추가 메소드가 있다면 다음과 같이 위임할 수 있습니다.
Forwardable 모듈을 사용할 때는 몇 가지 잠재적인 단점도 염두에 두어야 합니다.
- 첫째, 위임을 사용하기 전에 반드시 대상 객체가 초기화되어 있어야 합니다. 그렇지 않으면 오류가 발생할 수 있습니다.
- 둘째, 위임을 과도하게 사용하면 어떤 메소드가 클래스 고유 것이고 어떤 메소드가 위임된 것인지 불분명해져 코드 가독성이 오히려 떨어질 수 있습니다.
- 셋째, 직접 메소드 호출에 비해 Forwardable 사용 시 약간의 성능 오버헤드가 발생할 수 있습니다. 하지만 대부분의 애플리케이션에서 이 오버헤드는 무시할 수 있는 수준입니다.
Forwardable 모듈에 대해 더 자세히 알고 싶다면 공식 문서를 참고하세요.
Rails 애플리케이션을 위한 ActiveSupport::Delegate
ActiveSupport::Delegate는 Rails가 제공하는 위임 유틸리티로, 연관 객체에 메소드를 위임할 수 있는 간결한 문법을 제공합니다. Ruby 내장 Forwardable 모듈과 유사하지만, Rails 애플리케이션에 맞춘 추가 옵션과 기능을 갖추고 있다는 점이 차별점입니다.
ActiveSupport::Delegate를 사용하려면 클래스 내에서 delegate 메소드를 호출하고, 위임할 메소드와 대상 객체를 지정하기만 하면 됩니다. Printer와 Formatter 예제를 ActiveSupport::Delegate로 다시 구현해 보겠습니다.
ActiveSupport::Delegate는 다양한 상황에서 유용한 추가 옵션도 제공합니다. 예를 들어 :prefix 옵션을 사용하면 위임된 메소드 이름 앞에 접두어를 붙일 수 있습니다. 여러 메소드를 한 번에 위임하려면 to: 옵션 앞에 메소드들을 나열하면 됩니다.
이렇게 하면 Printer 클래스에 formatter_format, formatter_bold, formatter_italic 같은 메소드가 생성되며, 각각 Formatter 클래스의 해당 메소드로 위임됩니다.
ActiveSupport::Delegate는 실무 Rails 프로젝트에서 연관된 모델이나 객체 간 메소드를 위임할 때 폭넓게 사용됩니다. 예를 들어 User 모델이 조직에 belongs_to 관계로 연결되어 있다면, organization 모델의 name 메소드를 User 모델에 다음과 같이 위임할 수 있습니다.
그러면 user 객체를 통해 조직 이름에 바로 접근할 수 있습니다: user.organization_name.
결론적으로 ActiveSupport::Delegate는 Rails 애플리케이션에서 간결하고 표현력 있는 메소드 위임을 가능하게 하는 강력한 도구입니다. 다양한 옵션과 기능을 잘 활용하면, 객체 지향 설계 원칙에 부합하는 유지보수성 높고 체계적인 코드를 Rails 프로젝트에서 작성할 수 있습니다.
ActiveSupport::Delegate와 사용 가능한 모든 옵션에 대한 자세한 내용은 Rails ActiveSupport 공식 문서를 참고하세요.
위임 기법 비교
명시적 위임은 가장 직관적인 방식으로, 위임 대상 객체의 메소드를 호출하는 메소드를 직접 정의해야 합니다. 외부 라이브러리 없이 구현할 수 있는 장점이 있지만, 위임해야 할 메소드가 많아질수록 코드가 장황해질 수 있습니다.
Forwardable 모듈은 Ruby에 내장되어 있어 더욱 간결하게 메소드를 위임할 수 있는 방법을 제공합니다. def_delegator와 def_delegators를 사용하면 하나 또는 여러 개의 메소드를 짧은 코드로 위임할 수 있습니다.
ActiveSupport::Delegate는 Rails의 ActiveSupport 라이브러리 일부로, 선언적인 방식으로 메소드 위임을 처리합니다. 깔끔한 문법과 접두어(prefix) 같은 추가 옵션 덕분에 Rails 환경에서 매우 표현력이 뛰어난 도구입니다. 명시적 위임과 비교했을 때 특히 여러 메소드를 위임할 때 더 간결하고 표현력이 뛰어나지만, Rails가 필요하기 때문에 순수 Ruby 프로젝트에는 적합하지 않습니다.
어떤 위임 기법을 선택할지는 프로젝트의 요구 사항과 애플리케이션 특성에 달려 있습니다. 명시적 위임은 단순함과 명확성을, Forwardable 모듈은 간결함을, ActiveSupport::Delegate는 Rails에 최적화된 강력한 해법을 제공합니다. 세 가지 옵션을 모두 이해하면 프로젝트 성격과 코딩 스타일에 가장 적합한 기법을 선택할 수 있습니다.
마치며
명시적 위임부터 내장 Forwardable 모듈, 그리고 Rails 전용인 ActiveSupport::Delegate까지, 각 기법은 고유한 장점과 잠재적인 단점을 지니고 있습니다. 어떤 기법을 선택할지는 개발 상황, 프로젝트의 성격, 그리고 개인의 코딩 스타일에 따라 결정하면 됩니다.
이런 기법들을 익혀 두면 Ruby 프로그래밍 실력이 한층 넓어질 뿐 아니라, 더 모듈화되고 읽기 쉬우며 유지보수하기 좋은 코드를 작성할 수 있게 됩니다. 이 지식을 바탕으로 우아한 설계로 복잡한 문제에 더 능숙하게 대응할 수 있기를 바랍니다.
즐거운 위임 되세요!
P.S. Ruby Magic 게시물이 발행되자마자 읽고 싶으시다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!