반응형

Adapter 패턴은 우리가 인지하지도 못한 사이 엄청나게 사용하고 있는 패턴이다. 과연 Adapter 패턴이란 무엇인가? 


다음은 위키피티아의 정의이다. 어댑터 패턴(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다. 


한마디로 내가 사용하기 편한 메소드들로 감싼다고 생각을 하면 이해가 쉬울 것이다. 


이러한 패턴은 외부 라이브러리를 사용할 때 활용 될 수 있다. 실제 비즈니스 로직을 구현하는데는 외부 라이브러리가 많이 혼재되어 있다면 굉장히 이해하기 어려울 것이다. 이런 경우 내가 사용하고자 하는 인터페이스를 잘 정의하고 그의 구현에서 외부 라이브러리의 클래스들을 사용한다면 훨씬 깔끔하게 우리의 비즈니스 로직을 구현할 수 있을 것이다

public class JdbcTemplateAdapter {
private NamedParameterJdbcTemplate adaptee;

public JdbcTemplateAdapter(DataSource dataSource) {
adaptee = new NamedParameterJdbcTemplate(dataSource);
}

public Map<String, Object> queryForMap(String sql) {
return adaptee.queryForMap(sql, Collections.EMPTY_MAP);
}
}

위와 같이 심플한 외부 스프링 프레임워크에 대한 간단한 Adapter를 만들어 보았다. 언듯 보면 왜 굳이 이러한 Adapter클래스를 만들어야 하는지 이해가 안될 수도 있다. 그러나 일단 만들었으니 이런 경우 실제 데이터베이스 테스트를 하는 것이 확실히 유용하다. 위 클래스에 대한 테스트케이스는 다음과 같이 작성되었다.

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class JdbcTemplateAdapterTest {
@Inject
private DataSource dataSource;

private JdbcTemplateAdapter jdbcTemplateAdapter;

@Before
public void setup() {
jdbcTemplateAdapter = new JdbcTemplateAdapter(dataSource);
}

@Test
public void testQueryForMap() {
Map<String, Object> result = jdbcTemplateAdapter.queryForMap("SELECT KAKAO FROM KAKAO_FRIENDS WHERE KAKAO_ID = 'A'");

assertEquals(1, result.size());
assertEquals("KAKAO", result.get("KAKAO"));
}
}

만약 위의 테스트케이스가 정상적으로 수행했다면 우리는 성공적으로 Adapter 클래스를 작성했다고 볼 수 있겠다. 여기서 자세한 스프링 테스트에 대한 설명은 생략하겠다.


다음은 해당 클래스를 사용하는 다른 클래스를 하나 작성해보자. 이 클래스의 목적은 데이터베이스로부터 특정값을 읽어서 URL을 생성하는 것이다.

public class JdbcUrlFactory {
private JdbcTemplateAdapter jdbcTemplateAdapter;
private String urlPattern;
private String sql;
private UriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();

public JdbcUrlFactory(JdbcTemplateAdapter jdbcTemplateAdapter, String urlPattern, String sql) {
this.jdbcTemplateAdapter = jdbcTemplateAdapter;
this.urlPattern = urlPattern;
this.sql = sql;
}

public String create() {
Map<String, Object> result = jdbcTemplateAdapter.queryForMap(sql);
URI uri = uriTemplateHandler.expand(urlPattern, result);
return uri.toString();
}
}

역시 간단하다. 그러나 진가는 테스트에서 발휘된다. JdbcTemplate에 대한 어댑터를 만들어놓았기 때문에 해당 Factory 클래스의 테스트는 데이터베이스 디펜던시가 사라진다.

public class JdbcUrlFactoryTest {
private static final String QUERY = "SELECT V_ID FROM MOVIE";
private static final String EXPECTED_URL = "http://api.kakao.com?v_id=1234";
private JdbcTemplateAdapter jdbcTemplateAdapter;

@Before
public void setup() {
jdbcTemplateAdapter = mock(JdbcTemplateAdapter.class);

Map<String,Object> result = new HashMap<>();
result.put("V_ID", "1234");

when(jdbcTemplateAdapter.queryForMap(QUERY)).thenReturn(result);
}

@Test
public void testCreate() {
JdbcUrlFactory jdbcUrlFactory = new JdbcUrlFactory(
jdbcTemplateAdapter,
"http://api.kakao.com?v_id={V_ID}",
QUERY);

String url = jdbcUrlFactory.create();

assertEquals(EXPECTED_URL, url);
}
}

결론적으로 어댑터를 잘 활용하면 외부 라이브러리에 대한 검증을 확실히 하는 반면 테스트에 대한 이점도 얻을 수 있다. 그리고 어댑터에 대한 테스트 케이스를 충분히 작성해놓는다면 외부 라이브러리의 변경에 대한 완충 작용을 수행할 수 있게되어 좀 더 안전한 버젼업을 진행할 수 있다.

반응형

'Story > Design Pattern' 카테고리의 다른 글

Builder pattern vs Factory pattern  (4) 2016.05.02
반응형
When Using It

언어로 문제를 해결하기

Class Diagram



Sample Code



public interface AbstractExpression {

public void interprete(Context context);

}


public class TerminalExpression implements AbstractExpression {

@Override

public void interprete(Context context) {

String token = context.currentToken();

if (!"go".equals(token) ) {

new NonTerminalExpression().interprete(context);

} else {

System.out.println("go");

}

}

}


public class NonTerminalExpression implements AbstractExpression {

@Override

public void interprete(Context context) {

String token = context.currentToken();

if ( "begin".equals(token)) {

System.out.println("begin");

while ((token = context.nextToken()) != null ) 

{

new TerminalExpression().interprete(context);

}

} else if ( "end".equals(token)) {

System.out.println("end");

} else {

System.out.println("error");

}

}

}


public class Context {

private StringTokenizer tokenizer;

private String currentToken;

public Context(String text) {

tokenizer = new StringTokenizer(text);

this.nextToken();

}

public String nextToken() {

if (tokenizer.hasMoreTokens()) {

currentToken = tokenizer.nextToken();

} else {

currentToken = null;

}

return currentToken;

}

public String currentToken() {

return currentToken;

}

}


public class Client {

public static void main(String[] args) {

String text = "begin go go go end";

Context context = new Context(text);

NonTerminalExpression nonTerminalExpression = new NonTerminalExpression();

nonTerminalExpression.interprete(context);

}

}


Caution


특별한 주의사항은 없다. 
반응형
반응형
When Using It

명령을 클래스로 표현하고 싶을때

Class Diagram


Sample Code



public interface Command {

public void execute();

}



public class ConcreteCommand implements Command {

private Receiver receiver = new Receiver();

@Override

public void execute() {

receiver.action();

}

}


public class Receiver {

public void action() {

System.out.println("Action Performed!!!");

}

}


public class Invoker {

private List<Command> commands = new ArrayList<Command>();

public void addCommand(Command command) {

commands.add(command);

}

public void excute() {

for ( Command command : commands) {

command.execute();

}

}

}



public class Client {

public static void main(String[] args) {

Invoker invoker = new Invoker();

invoker.addCommand(new ConcreteCommand());

invoker.excute();

}

}



Caution


특별한 주의사항은 없다.
반응형
반응형
When Using It

조건에 따른 상태 자체를 클래스로 표현하고 싶을때

Class Diagram



Sample Code

public interface State {

public void handle(Context context, boolean condition);

}


public class ConcreteState implements State {

@Override

public void handle(Context context, boolean condition) {

if (condition) {

System.out.println("ConcreteState Handled.");

} else {

context.setState(new ConcreteState2());

}

}

}


public class ConcreteState2 implements State {

@Override

public void handle(Context context, boolean condition) {

if (condition) {

System.out.println("ConcreteState2 Handled.");

} else {

context.setState(new ConcreteState());

}

}

}


public class Context {

private State state; 

public void request() {

state = new ConcreteState();

state.handle(this, false);

state.handle(this, true);

}


public void setState(State state) {

this.state = state;

}

}


Caution


특별한 주의사항은 없다.
반응형
반응형
When Using It

자신의 상태에 변화가 일어났을 경우 Observer들에게 통보하기

Class Diagram


Sample Code



public interface Observer {

public void update(Subject subject);

}


public class ConcreteObserver implements Observer {

@Override

public void update(Subject subject) {

System.out.println(subject.getState());

}

}


public abstract class Subject {

private List<Observer> observers = new ArrayList<Observer>();

public void attach(Observer observer) {

observers.add(observer);

}

public void detach(Observer observer) {

observers.remove(observer);

}


public void notifies() {

for (Observer observer : observers) {

observer.update(this);

}

}

public abstract void execute();

public abstract String getState();

}



public class ConcreteSubject extends Subject {

private String state;


@Override

public void execute() {

state = "GOOD";

notifies();

}


@Override

public String getState() {

return state;

}

}


Caution


특별한 주의사항은 없다.
반응형
반응형

When Using It

다수의 객체에 통합적인 지시를 내리기 위한 패턴. 클래스의 기능들을 분할하여 세분화된 클래스로 내렸을때, 세분화된 클래스간의 통신이 필요할때 유용하다.

Class Diagram


Sample Code

public interface Mediator {

}


public class ConcreteMediator implements Mediator {

private Colleague colleague;

private Colleague colleague2;

public void initializeColleague() {

colleague = new Colleague();

colleague2 = new Colleague();

colleague.setMediator(this);

colleague2.setMediator(this);

}

public void colleagueExecute() {

colleague.execute();

}

public void colleague2Execute() {

colleague2.execute();

}

}


public class Colleague {

private Mediator mediator;


public void setMediator(Mediator mediator) {

this.mediator = mediator;

}

public void execute() {

}

}

Case Study

1. GWT나 SWT같은 화면을 개발할 경우 Layout 아래에 Grid, Tree, Tab과 같은 다수의 클래스들이 선언된다. 이럴경우 Layout에 그려진 Grid를 클릭했을때 Detail Form에 데이터가 표시되어야 한다라는 요구사항이 있다고 가정하자. 이럴 경우 Layout 자체를 Mediator로 Grid와 Detail Form을 Colleague로 묶어놓으면 Grid와 Detail Form간의 변화 상황을 Layout을 통해 통보할 수 있다.

Caution


특별한 주의사항은 없다.

반응형
반응형
When Using It

특정한 데이터 구조 가진 오브젝트를 방문하면서 처리를 하고 싶을때

Class Diagram


Sample Code



public interface Element {

public void accept(Visitor visitor);

public void add(Element element);

public List<Element> getChildren();

}


public class ConcreteElement implements Element {

private List<Element> children = new ArrayList<Element>();

@Override

public void accept(Visitor visitor) {

visitor.visitConcreteElement(this);

}


@Override

public void add(Element element) {

children.add(element);

}


@Override

public List<Element> getChildren() {

return this.children;

}

}


public abstract class Visitor {

public abstract void visitConcreteElement(Element element);

}



public class ConcreteVisitor extends Visitor {

@Override

public void visitConcreteElement(Element element) {

List<Element> children = element.getChildren();

for (Element child : children ) {

System.out.println(child);

}

}

}


Caution


특별한 주의사항은 없다.
반응형
반응형
When Using It

알고리즘 구현 부분을 교체 하고 싶을때

Class Diagram 
 


Sample Code



public interface Strategy {

public void algorithmInterface();

}


public class ConcreteStrategy implements Strategy {

@Override

public void algorithmInterface() {

}

}


public class Context {

private Strategy strategy;

public Context(Strategy strategy) {

this.strategy = strategy;

}

public void contextInteface() {

strategy.algorithmInterface();

}

}


Caution


특별한 주의사항은 없다.
반응형
반응형
When Using It

하위클래스에 구현을 위임하고 싶을때

Class Diagram 
 


Sample Code



public abstract class AbstractClass {

public abstract void templateMethod1();

public abstract void templateMethod2();

public abstract void templateMethod3();

public void execute() {

templateMethod1();

templateMethod2();

templateMethod3();

}

}


public class ConcreteClass extends AbstractClass {

@Override

public void templateMethod1() {}


@Override

public void templateMethod2() {}


@Override

public void templateMethod3() {}

}


Caution


특별한 주의사항은 없다.
반응형
반응형
When Using It

Heavy한 Job이 수행되는 클래스들을 미리 만들어 놓고 공유하고 싶을때

Class Diagram 
 


Sample Code



public interface Aggregate {

}


public interface Iterator {

public Object next();

public boolean hasNext();

}


public class ConcreteAggregate implements Aggregate {

private Object[] items;

public Iterator iterator() {

return new ConcreteIterator(this);

}


public Object getItem(int index) {

if ( index < items.length - 1)

return items[index];

else 

return null;

}

public int getLength() {

return items.length;

}

}




public class ConcreteIterator implements Iterator {

private ConcreteAggregate aggregate;

private int index;

public ConcreteIterator(ConcreteAggregate aggregate) {

this.aggregate = aggregate;

this.index = 0;

}


@Override

public Object next() {

Object item = aggregate.getItem(index);

if ( item != null ) {

index++;

}

return item;

}


@Override

public boolean hasNext() {

if ( index < aggregate.getLength()) {

return true;

} else {

return false;

}

}

}



Caution


특별한 주의사항은 없다.
반응형

+ Recent posts