지난번 구조패턴으로 데코레이터 패턴에 대해서 알아보았다, 구조패턴의 두 번째 패턴으로 어댑터 패턴을 정리해 보고 자 한다.
구조패턴 이란?
구조패턴 은 구조를 유연하고 효율적으로 유지하면서 객체들과 클래스들을 더 큰 구조로 조립하는 방법을 설명한다.
어댑터 패턴이란 ?
어댑터 패턴(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로 호환성 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다. -wiki-
어댑터는 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴입니다. -guru-
구루, 위키에서 정의하는 공통적인 목표가 있는 패턴이다.
바로 "인터페이스를 원하는 인터페이스에 맞도록 동작"에 초점이 맞춰져 있다 는 의미이다.
왜 사용하는가?
- 기존 패턴들보다 명확한 이유가 보인다. 바로 인터페이스를 통한 객체의 통신을 연결해 주는 중간다리로 생각하면 될 것 같다.
- 어댑터 패턴은 SOLID 원칙 중 특히 OCP를 잘 지키고 있다. (기존 인터페이스와 새로운 인터페이스의 연결을 위해 중간다리를 만들어주는거니깐 )
- 리스코프치환 원칙 또한 지켜지고 있다. 인터페이스 의 사용주체 "클라이언트"는 어댑터를 사용하더라도 원래 인터페이스를 사용하던 것과 동일한 방식으로 동작하며, 결과를 반환해야 한다. 프로그램의 정확성을 깨지 않으면서 하위타입의 인스턴스를 상위 타입의 객체로 치환 가능해야 한다는 리스코프 원칙이 어느 정도 반영되었다고 볼 수도 있다.
구조
왼쪽은 어댑터 패턴의 구조이고, 오른쪽은 데코레이터 패턴의 구조이다. 구조를 보게 되면 아무래도 데코레이터 도 그렇고, 어댑터도 그렇고 생성패턴? 과 는 달리 인터페이스가 아닌 구조체로 받아서 작성하게 된다.
이 부분에 대해서는 코드를 작성하면서 추가적으로 예시를 작성하고자 한다.
어댑터패턴의 적용
- 어댑터 구조체는 기존 구조체를 사용하고 싶지만 그 인터페이스가 나머지 코드와 호환되지 않을 때 사용한다.
- 이 패턴은 부모 클래스에 추가할 수 없는 어떤 공통 기능들이 없는 여러 기존 자식 클래스들을 재사용하려는 경우에 사용한다.
type PostgreSQL interface {
InsertColumn()
DeleteColumn()
UpdateColumn()
ReadColumn()
}
type PostgreSQL_V15 struct {
db string
}
func (p PostgreSQL_V15) InsertColumn() {
fmt.Println("Insert by postgresql version 15")
}
func (p PostgreSQL_V15) DeleteColumn() {
fmt.Println("Delete by posgresql version 15")
}
func (p PostgreSQL_V15) UpdateColumn() {
fmt.Println("Update by postgresql version 15")
}
func (p PostgreSQL_V15) ReadColumn() {
fmt.Println("Read by postgresql version 15")
}
var _ PostgreSQL = (*PostgreSQL_V15)(nil)
func NewPostgreSQL() PostgreSQL {
return &PostgreSQL_V15{"postgresql version 15"}
}
type MySQL interface {
InsertData()
DeleteData()
UpdateData()
ReadData()
}
type MySQL_V8 struct {
db string
}
func (m MySQL_V8) InsertData() {
fmt.Println("Insert by mysql version 8")
}
func (m MySQL_V8) DeleteData() {
fmt.Println("Delete by mysql version 8")
}
func (m MySQL_V8) UpdateData() {
fmt.Println("Update by mysql version 8")
}
func (m MySQL_V8) ReadData() {
fmt.Println("Read by mysql version 8")
}
var _ MySQL = (*MySQL_V8)(nil)
func NewMySQL() MySQL {
return &MySQL_V8{"mysql version 8"}
}
type DbBatch interface {
BatchInsert()
BatchDelete()
BatchUpdate()
BatchRead()
}
type MySQL_V8_Batch_Adapter struct {
mysql MySQL
}
func (m MySQL_V8_Batch_Adapter) BatchInsert() {
m.mysql.InsertData()
}
func (m MySQL_V8_Batch_Adapter) BatchDelete() {
m.mysql.DeleteData()
}
func (m MySQL_V8_Batch_Adapter) BatchUpdate() {
m.mysql.UpdateData()
}
func (m MySQL_V8_Batch_Adapter) BatchRead() {
m.mysql.ReadData()
}
var _ DbBatch = (*MySQL_V8_Batch_Adapter)(nil)
func NewDbBatchMySQLAdapter() DbBatch {
mysql := NewMySQL()
return &MySQL_V8_Batch_Adapter{mysql}
}
type PostgreSQL_V15_Batch_Adapter struct {
postgres PostgreSQL
}
func (p PostgreSQL_V15_Batch_Adapter) BatchInsert() {
p.postgres.InsertColumn()
}
func (p PostgreSQL_V15_Batch_Adapter) BatchDelete() {
p.postgres.DeleteColumn()
}
func (p PostgreSQL_V15_Batch_Adapter) BatchUpdate() {
p.postgres.UpdateColumn()
}
func (p PostgreSQL_V15_Batch_Adapter) BatchRead() {
p.postgres.ReadColumn()
}
var _ DbBatch = (*PostgreSQL_V15_Batch_Adapter)(nil)
func NewDbBatchPostgreSQLAdapter() DbBatch {
postgres := NewPostgreSQL()
return &PostgreSQL_V15_Batch_Adapter{postgres}
}
func Test_DB(t *testing.T) {
batch := []DbBatch{NewDbBatchMySQLAdapter(), NewDbBatchPostgreSQLAdapter()}
for _, v := range batch {
v.BatchInsert()
v.BatchDelete()
v.BatchRead()
v.BatchUpdate()
fmt.Println("----------------------------------")
}
}
=== RUN Test_DB
Insert by mysql version 8
Delete by mysql version 8
Read by mysql version 8
Update by mysql version 8
----------------------------------
Insert by postgresql version 15
Delete by posgresql version 15
Read by postgresql version 15
Update by postgresql version 15
----------------------------------
--- PASS: Test_DB (0.00s)
PostgreSQL 15 버전과, MySQL8 버전을 배치로 작업하기 위해 어댑터 패턴을 적용해 클라이언트에서는 DbBatch 만 사용하고 있으면 내부구현의 수정이 어떻게 변하던지 동일한 결과를 얻게 된다.
말도 안 되지만 여기서 만약 PostgreSQL 13 버전, MySQL 5 버전 등의 버전들이 필요하게 되고 이 구조체들은 서로 다른 인터페이스를 따르고 있다면? DbBatch에 맞는 새로운 구조체를 생성해서 각각 필드값을 주입받아 해당 함수를 호출시켜 주면 되는 것이다.
데코레이터 패턴과 뭔가 느낌이 비슷하지 않은가? 정리를 해보자.
- 데코레이터 패턴 은 기존 객체에 새로운 기능을 동적으로 추가할 수 있도록 설계된 패턴이다. 객체의 데코레이션을 담당하는 별도의 데코레이터 객체를 만들어, 기본 객체에 새로운 행동을 추가하거나 수정한다. 이에 객체를 확정하거나 변경할 수 있다.
- 어댑터패턴 은 한 클래스의 인터페이스를 클라이언트가 사용하고자 하는 다른 인터페이스로 변환하는 패턴이다.
두 패턴 모두 특징이 하나 있다.
기존 코드를 건드리지 않고도 확장하거나 변경하는데 유용하다. 이게 제일 핵심인 것 같다. 다만 사용의 목적 이 상당히 다를 뿐이지만
이래서 디자인패턴은 공부를 하면 할수록 원점으로 돌아가는 나 자신이 느껴진다.
'Go > Design Pattern' 카테고리의 다른 글
[Design Pattern] 행동패턴-템플릿 메서드 패턴 (Template-Method Pattern) (1) | 2023.08.10 |
---|---|
[Design Pattern] 구조패턴-퍼사드 패턴 (Facade Pattern) (0) | 2023.07.28 |
[Design Pattern] 행동패턴-커맨드 패턴 (Command Pattern) (0) | 2023.07.14 |
[Design Pattern] 구조패턴-데코레이터 패턴 (Decorator Pattern) (0) | 2023.07.10 |
[Design Pattern] 행동패턴-옵저버패턴 (Observer Pattern) (0) | 2023.07.03 |