☁️ 디자인 패턴
객체 지향 4대 특성(캡상추다)은 객체 지향에서 지원하는 도구이다.
객체 지향 5원칙(SOLID)은 도구를 올바르게 사용하는 방법이다.
디자인 패턴은 도구를 올바르게 사용하여 프로그래밍 하도록 도와주는 비법이다.
디자인 패턴은 실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양한 해결책 중에서 많은 사람들이 인정한 베스트 프렉티스를 정리한 것이다. 디자인 패턴은 당연히 객체 지향 특성과 설계 원칙을 기반으로 구현되어 있다.
스프링은 객체 지향의 특성과 설계 원칙을 극한까지 적용한 프레임워크이다. 따라서 스프링을 공부하다보면 자연스럽게 디자인 패턴을 만날 것이다.
디자인 패턴이란 객체 지향 프로그래밍으로 비즈니스 요구사항을 처리하며 마주친 문제들을 객체 지향의 특성과 원칙에 맞추어 해결한 대표적인 사례들을 모아놓은 것.
이라고 정의할 수 있겠다.
디자인 패턴은 객체지향의 특성 중 상속
, 인터페이스
, 컴포지션
을 이용한다.
☁️ Adapter
서로 다른 두 인터페이스 사이에 통신을 가능하게 해주는 것. DB와 Java를 예로 들면 DB와 Java는 서로 다른 인터페이스를 가지고 있지만, JDBC라는 어댑터를 통해 조작할 수 있다. DB 시스템을 단일한 인터페이스로 조작할 수 있게 해주는 것이 어댑터 패턴이다.
지금까지는 개념적인 adapter에 대한 설명이고 코드에서 adapter의 목적과 사용처에 대해 알아보자.
어댑터 패턴은 기존 클래스를 수정하지 않고 새로운 인터페이스와 맞도록 중간에 어댑터를 추가하는 것을 목적으로한다. 만약에 A라는 회사에서 정현수라는 사원이 작성하고 도망간 DB 커넥션 라이브러리가 아래와 같이 존재하고 사용된다고 생각해보자.
interface OldDB {
void read();
}
class MySQL implements OldDB {
public void read() {
System.out.println("MySQL!");
}
}
class Oracle implements OldDB {
public void read() {
System.out.println("Oracle!");
}
}
public class Main {
public static void main(String[] args) {
Main main = new Main();
OldDB mysql = new MySQL();
main.readDB(mysql);
}
public void readDB(OldDB db) {
db.read();
}
}
기존에는 이렇게 사용하고 있었는데 이 코드가 너무 레거시라 새로운 디비 커넥션 코드를 도입했다.
interface NewDB {
void newRead();
}
class NewMongoDB implements NewDB {
public void newRead() {
System.out.println("MongoDB!");
}
}
이런 상황에서 기존의 아래 코드 수정없이 새로운 디비 커넥션 코드를 적용시키는 방법이 있을까?
public void readDB(OldDB db) {
db.read();
}
만약에 엄청나게 많은 코드에서 위와 같이 OldDB를 의존하고 있다면? 이걸 모두 NewDB와 의존하도록 코드를 변경해야 할까? 이런 방법은 너무 많은 리소스를 사용하고 효율적이지 않다.
이럴 때, 어댑터 패턴이 사용된다. OldDB와 NewDB 두 인터페이스는 호환되지 않는다.
즉, OldDB와 의존하는 모든 곳에서 NewDB를 사용할 수 없다는 것이다. 이제 이게 가능하도록 변경시켜보자.
// OldDB
interface OldDB {
void read();
}
class MySQL implements OldDB {
@Override
public void read() {
System.out.println("MySQL!");
}
}
class Oracle implements OldDB {
@Override
public void read() {
System.out.println("Oracle!");
}
}
// NewDB
interface NewDB {
void newRead();
}
class NewMongoDB implements NewDB {
@Override
public void newRead() {
System.out.println("MongoDB!");
}
}
// OldDB를 구현하고, NewDB를 컴포지션으로 가지는
// 어댑터 추가!!!!
class DBAdapter implements OldDB {
private final NewDB db;
public DBAdapter(NewDB db) {
this.db = db;
}
@Override
public void readDB() {
db.newRead();
}
}
자 이렇게 어댑터를 구현했다면 이제 기존 사용처에서는 어댑터를 통해 NewDB를 사용할 수 있다.
public class Main {
public static void main(String[] args) {
Main main = new Main();
// OldDB mysql = new MySQL();
OldDB adapter = new DBAdapter(new NewMongoDB);
main.readDB(adapter)
}
public void readDB(OldDB db) {
db.read();
}
}
메인 코드에서 단 한줄의 변경으로 레거지 인터페이스를 의존하던 메서드에게 새로운 인터페이스를 사용할 수 있도록 변경했다. 자, 이렇게 호환되지 않는 인터페이스를 구현과, 컴포지션을 통해 호환되도록 어댑터를 만드는 것을 어댑터 패턴이라고 한다.
스프링에서는 어떻게 사용했을까?
이런 코드 패턴을 기억하고 이게 어댑터 패턴이야! 라는 생각을 하지 말도록 하자.
스프링의 MultiValueMapAdapter를 보면 앞선 예시와는 반대로 새로운 MultiValueMap을 기존의 Map으로 동작시키기 위해MutiValueMapAdapter에서 Map을 컴포지션으로 가진다.
package org.springframework.util;
public class MultiValueMapAdapter<K, V>
implements MultiValueMap<K, V>, Serializable {
private final Map<K, List<V>> targetMap;
public MultiValueMapAdapter(Map<K, List<V>> targetMap) {
Assert.notNull(targetMap, "'targetMap' must not be null");
this.targetMap = targetMap;
}
@Nullable
public V getFirst(K key) {
List<V> values = (List)this.targetMap.get(key);
return values != null && !values.isEmpty() ? values.get(0) : null;
}
따라서 코드로 기억하는 것보다는 위와 같이 동작하는 것이 어댑터 패턴이다 라고 생각하는 것이 좋을 것 같다.
'JAVA' 카테고리의 다른 글
디자인 패턴 - Template Method, Factory Mehtod (0) | 2024.01.20 |
---|---|
디자인 패턴 - Proxy, Decolator (0) | 2024.01.19 |
SOLID (0) | 2024.01.18 |
BigDecimal 생성, 비교시 유의사항 (0) | 2024.01.17 |
데몬 스레드 (Daemon Thread) (2) | 2024.01.01 |