본문으로 바로가기

어댑터 패턴 - adapter pattern

category 디자인 패턴 2017. 11. 8. 13:29

어댑터 패턴의 어탭터는 일상 생활에서 플러그 인터페이스를 변환하는 역할과 똑같은 역할을 한다.
소프트웨어 시스템은 여러 모듈로 구성되고 여러 업체에서 제공한 인터페이스를 사용한다.
기존 소프트웨어 시스템이 변경 또는 갱신될 수 있고 업체의 인터페이스 역시 변경될 수 있다.
새로운 업체의 인터페이스가 기존 소프트웨어 시스템과 다르다면 둘 중 하나를 고쳐야 하지만
양쪽 모두 변경할 수 없는 상황이 발생하며 이를 어댑터 패턴으로 해결할 수 있다.

어댑터 패턴이 동작하는 원리와 설명

1. Client에서 타겟 인터페이스를 사용하여 메소드를 호출하여 Adapter에 요청한다.

2. Adapter에서는 Adaptee 인터페이스를 사용하여 그 요청을 Adapter의 하나 이상의 메소드 호출로 변환한다.

3. Client에서는 호출의 결과를 받지만 변환 중 Adapter가 있는지를 전혀 알지 못한다.


클래스(Adaptee)가 실제로는 지원하지 않는 인터페이스를 지원하는것 처럼 만든다. (즉 클래스의 인터페이스를 Client가 기대하는 인터페이스로 변환해 준다). 이를 통해 리팩토링 없이도 기존의 클래스를 이용해 새로운 클래스를 만들수있다.

- 실용주의 디자인 패턴 - 

어댑터 패턴 클래스 다이어그램
어댑터 패턴 예제
public interface Duck { public void quack(); public void fly(); }
public class MallardDuck implements Duck { @Override public void quack() { System.out.println("Quack"); } @Override public void fly() { System.out.println("I'm flying"); } }
public interface Turkey { public void gobble(); public void fly(); }
public class WildTurkey implements Turkey{ @Override public void gobble() { System.out.println("Gobble gobble"); } @Override public void fly() { System.out.println("I'm flying a short distance"); } }

Duck객체 대신 Turkey라는 새로운 인터페이스를 사용해야 하는 상황
TurkeyAdapter를 구현하여 Duck과 Turkey를 호환

public class TurkeyAdapter implements Duck { Turkey turkey; public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } @Override public void quack(){ turkey.gobble(); } @Override public void fly() { turkey.fly(); } }
public class DuckTestDrive { public static void main(String[] args) { MallardDuck duck = new MallardDuck(); WildTurkey turkey = new WildTurkey(); Duck turkeyAdapter = new TurkeyAdapter(turkey); System.out.println("The turkey says..."); turkey.gobble(); turkey.fly(); System.out.println("The Duck says..."); testDuck(duck); System.out.println("The TurkeyAdapter says..."); testDuck(turkeyAdapter); } public static void testDuck(Duck duck){ duck.quack(); duck.fly(); } }

클라이언트 -> request() -> 어댑터 - specificRequest() -> 어댑티.
클라이언트는 타겟 인터페이스에 맞게 구현, 어댑터는 타겟 인터페이스를 구현하며, 어댑티 인스턴스가 들어있음.
이 예제에서 Target은 Duck, Adaptee는 Turkey

안드로이드의 Adapter

안드로이드에서의 어댑터는 뷰(Client)와 데이터 셋(Adaptee)를 연결해주는 역할을 한다. 어댑터는 데이터 아이템에 접근할 수 있도록 하며, 데이터 셋 속 아이템의 뷰를 그리는 역할도 담당한다.

android.widget.Adapter 인터페이스를 상속받은 어댑터들
* SpinnerAdapter
* ListAdapter

android.widget.BaseAdapter 추상 클래스를 상속받은 어댑터들
* ArrayAdapter
* CursorAdapter
* SimpleAdapter
* RemoteViewsAdapter

public class TaskFragment extends Fragment { private ArrayList<Task> mChildList = null; public static TaskAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final ListView mListView = (ListView) view.findViewById(R.id.task_list); mAdapter = new TaskAdapter(getActivity(), mChildList); mListView.setAdapter(mAdapter); } }
public class TaskAdapter extends BaseAdapter { private Context mContext; private final LayoutInflater mInflater; private ArrayList<Task> mChildList; @Override public int getCount() { return mChildList.size(); } @Override public Task getItem(int position) { return mChildList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ChildViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.item_list_child, parent, false); } else { holder = (ChildViewHolder) convertView.getTag(); } . . . //뷰 작업 return convertView; }
JDBC의 Adapter 패턴

JDBC란 Java DataBase Connectivity의 약자로 데이터베이스에 연결 및 작업을 하기 위한 자바 표준 인터페이스이다.
자바는 DBMS의 종류에 상관없이 하나의 JDBC API를 사용해서 데이터베이스 작업을 처리할 수 있다.

static final String JDBC_DRIVER = "com.hama.jdbc.HAMADriver"; static final String DB_URL = "jdbc:hama://localhost/EMP"; Class.forName(JDBC_DRIVER); // 1. JDBC driver 등록 connection conn = DriverManager.getConnection(DB_URL,USER,PASS); // 2. db 로 접속 stmt = conn.createStatement(); // 3. SQL 실행을 위한 Statement 객체얻기 String sql = "SELECT id, age FROM Employees"; ResultSet rs = stmt.executeQuery(sql); // 4. result set 으로 부터 데이터 가져오기 while(rs.next()) { // 5. 커서를 통해 한 로우씩 // 6. 한 컬럼씩 가져오기 int id = rs.getInt("id"); int age = rs.getInt("age"); }

1. JDBC 드라이버 클래스를 로딩한다. (로딩함과 동시에 내부에서 HAMADriver 인스턴스를 생성한후에 DriverManager 에게 넘겨준다.)

2.  DriverManager 는 1번에서 얻게된 HAMADriver를 통해서 
디비에 접속한후에 HAMAConnection 객체를 얻는다

3. HAMAConnection 클래스를 통해서 
HAMAStatement 객체를 생성한다. 

4. HAMAStatement  클래스를 통해서 디비에 
쿼리명령을 실행후 ResultSet 로 테이블의 일부분의 데이터를 가져온다. 

5. ResultSet 안의 
커서를 통해서 ROW 별로 접근한다.

6. 
나의 컬럼에 접근한다. 

public class HAMAResultSet implements java.sql.ResultSet { // HAMAResultSet 는 어댑터이다. private final Cursor cursor; // adpatee 역할을 하는 Cursor를 가지고있다. public HAMAResultSet(Cursor cursor) { this.cursor = cursor; } public boolean next() { return cursor.advance(); // 클라이언트가 next 를 호출하면, advance() 로 변경시켜준다. } 110v 가 220v 로 바뀌듯이.. public int getInt(String columnName) { // HAMA DB 는 모든컬럼이 Sting 으로 저장되어있음. String contents = getString(columnName); return (contents == null) ? 0 : format.parse(contents).intValue(); } }
실제 Mysql Connector의 ResultSet Class
public class ResultSetImpl implements ResultSetInternalMethods { /** The current row #, -1 == before start of result set */ protected int currentRow = -1; // Cursor to current row; /** Pointer to current row data */ protected ResultSetRow thisRow = null; // Values for current row public boolean next() throws SQLException { synchronized (checkClosed()) { ... if (this.thisRow != null) { this.thisRow.closeOpenStreams(); } if (this.rowData.size() == 0) { b = false; } else { this.thisRow = this.rowData.next(); if (this.thisRow == null) { b = false; } else { clearWarnings(); b = true; } } ... return b; } } public int getInt(int columnIndex) throws SQLException { checkRowPos(); return getNativeInt(columnIndex); } }

ResultSetInternalMethods는 java.sql.ResultSet을 구현한 인터페이스