
대한민국은 220V 콘센트를 사용한다. 옆나라 일본은 110V을 사용한다. 일본에 220V 플러그를 가진 전자제품을 가져가도 110V 콘센트에 220V 플러그 접지가 맞지 않아 사용할 수 없다. 소위 말하는 돼지코를 꽂아 사용해야한다.
돼지코가 어댑터의 역할을 하고, 전자제품은 클라이언트 클래스, 그리고 콘센트는 서비스 클래스에 해당한다고 생각해보면 된다.
어댑터 패턴은 이처럼 인터페이스가 맞지 않는 서비스 클래스와 클라이언트 클래스가 있을 때, 서비스 클래스를 래핑해서 클라이언트 클래스가 이용할 수 있도록 하는 디자인 패턴이다.
이제 개발 관점에서 어댑터를 이해하기 위해 상황을 가정해보겠다.
사진을 찍어서 PNG 이미지로 보여주는 앱이 있다. 앱에 사진을 편집하는 기능을 추가하려고 서드 파티 라이브러리를 추가하고자 하는데 이 서드 파티는 JPG 이미지만 다룬다. 이미지의 확장자가 맞지 않아 사용이 불가능한 상황이다.
어떻게 하면 서드파티를 사용할 수 있을까?
몇가지 방법을 떠올려 볼 수 있다.
첫번째 방법은 서비스 클래스를 수정하는 방법인데, 상황에 따라 서비스 클래스의 수정이 막혀있을 수도 있고 (서드 파티 라이브러리인 경우), 추가된 변경 사항이 기존 동작을 망가뜨릴 수도 있다.
두번째 방법은 클라이언트 클래스 입장에서 비즈니스 로직과는 별개의 변환 로직을 추가하게 되는 것이고, 이는 단일책임원칙(single responsibility rule)에서 멀어지는 방향이다.
어댑터 클래스를 사용하면 위 두가지 경우에서의 단점을 모두 보완하면서 문제를 해결할 수 있다.
이렇게 하면 서드 파티를 수정하지 않음과 동시에 기존 클라이언트 코드에 단일 책임 원칙을 그대로 지키면서 서드 파티를 사용할 수 있다. 확장에는 열려 있고, 수정에는 닫혀있도록 하는 개방 폐쇄 원칙에도 부합하는 수정 방향이다.

class RoundHole {
var radius: CGFloat
init(radius: CGFloat) {
self.radius = radius
}
func isFitting(peg: RoundPeg) -> Bool {
return self.radius >= peg.radius
}
}
class RoundPeg {
var radius: CGFloat
init(radius: CGFloat) {
self.radius = radius
}
}
class SquarePeg {
var width: CGFloat
init(width: CGFloat) {
self.width = width
}
}
class SquarePegAdapter {
private var peg: SquarePeg
var radius: CGFloat { peg.width * sqrt(2) / 2 }
init(peg: SquarePeg) {
self.peg = peg
}
}
// somewhere in client code
var roundHole = RoundHole(radius: 3)
var squarePeg1 = SquarePeg(width: 4)
roundHole.isFitting(peg: squarePeg1) // compiler error: type mismatch
var squarePegAdapter1 = SquarePegAdapter(peg: squarePeg) // radius: 4 * sqrt(2) / 2 ~= 2.82
var squarePeg2 = SquarePeg(width: 5)
var squarePegAdapter2 = SquarePegAdapter(peg: squarePeg) // radius: 5 * sqrt(2) / 2 ~= 3.54
roundHole.isFitting(peg: squarePegAdapter1) // true
roundHole.isFitting(peg: squarePegAdapter2) // false