Software Development

[Design Pattern] 여러가지 디자인 패턴과 간단한 예제

프로그래민 2021. 3. 1. 00:09
반응형

어댑터 패턴(Adapter Pattern)

호출당하는 쪽의 메소드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴

DB관련 프로그램을 작성해보면 다양한 DB시스템을 공통의 인터페이스인 JDBC를 이용해 조작한다. 이러한 JDBC는 어댑터 패턴을 이용해 다양한 DB 시스템을 단일한 인터페이스로 조작할 수 있게 해 준다. 자바의 JRE 또한 어댑터 패턴의 일종이다. JDBC와 JRE는 개방 폐쇄 원칙(OCP)의 예이기도 하다. 결국 어댑터 패턴은 개방 폐쇄 원칙을 활용한 설계 패턴이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//어댑터 패턴 미적용
class ServiceA {
    void runServiceA() {
        System.out.println("ServiceA");
    }
}
 
public class Test {
    public static void main(String[] args) {
        ServiceA serviceA = new ServiceA();
        serviceA.runServiceA();
    }
}
 
//어댑터 패턴 적용
class AdapterServiceA {
    ServiceA serviceA = new ServiceA();
 
    void runServiceA() {
        serviceA.runServiceA();
    }
}
 
public class Test {
    public static void main(String[] args) {
        AdapterServiceA adapterServiceA = new AdapterServiceA();
        adapterServiceA.runServiceA();
    }
}
 
                                                                    

 

프록시 패턴(Proxy Pattern)

제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴

프록시는 대리자 대변인을 의미한다. 프록시 패턴의 경우 실제 서비스 객체가 가진 메소드와 같은 이름의 메소드를 사용하는 이를 위해 인터페이스를 사용한다. 인터페이스를 사용하면 서비스 객체가 들어갈 자리에 대리자 객체를 대신 투입해 클라이언트 쪽에서는 실제 서비스 객체를 통해 메소들를 호출하고 반환값을 받는지, 대리자를 통해 메소드를 호출하고 반환값을 받는지 모르게 처리할 수 있다. 개방 폐쇄 원칙(OCP)와 의존 역전 원칙(DIP)가 적용된 것을 확인할 수 있다.

  • 대리자는 실제 서비스와 같은 이름의 메소드를 구현한다. 이때 인터페이스를 사용한다.
  • 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성).
  • 대리자는 실제 서비스의 같은 이름을 가진 메소드를 호출하고 그 값을 클라이언트에게 돌려준다.
  • 대리자는 실제 서비스의 메소드 호출 전후에 별도의 로직을 수행할 수 도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//프록시 패턴 미적용
class Service {
    public String runSomething() {
        return "서비스";
    }
}
 
public class Test {
    public static void main(String[] args) {
       Service service = new Service();
        System.out.println(service.runSomething());
    }
}
 
//프록시 패턴 적용
interface ServiceInterface {
    String runSomething();
}
 
class Service implements ServiceInterface {
    @Override
    public String runSomething() {
        return "서비스";
    }
}
 
class Proxy implements ServiceInterface {
    ServiceInterface serviceInterface;
 
    @Override
    public String runSomething() {
        System.out.println("호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달");
 
        serviceInterface = new Service();
        return serviceInterface.runSomething();
    }
}
 
public class Test {
    public static void main(String[] args) {
       ServiceInterface proxy = new Proxy();
        System.out.println(proxy.runSomething());
    }
}
 
                                      

 

데코레이터 패턴(Decorator Pattern)

메소드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴

여기서 데코레이터는 장식자를 의미한다. 원본에 장식을 더하는 느낌이다. 데코레이터 패턴은 프록시 패턴과 구현방법이 같다. 다만 프록시 패턴은 클라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고 그대로 전달하는 반면 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힌다. 개방 폐쇄 원칙(OCP)와 의존 역전 원칙(DIP)가 적용된 것을 확인할 수 있다.

프록시 패턴 : 제어의 흐름을 변경하거나 별도의 로직처리를 목적으로 한다. 클라이언트가 받는 반환값을 특별한 경우가 아니면 변경하지 않는다.
데코레이터 패턴 : 클라이언트가 받는 반환값에 장식을 더한다.

  • 장식자는 실제 서비스와 같은 이름의 메소드를 구현한다. 이때 인터페이스를 사용한다.
  • 장식자는 실제 서브스에 대한 참조 변수를 갖는다(합성).
  • 장식자는 실제 서비스의 같은 이름을 가진 메소드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
  • 장식자는 실제 서비스의 메소드 호출 전후에 별도의 로직을 수행할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//데코레이터 패턴 미적용
class Service {
    public String runSomething() {
        return "서비스";
    }
}
 
public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        System.out.println(service.runSomething());
    }
}
 
//데코레이터 패턴 적용
interface ServiceInterface {
    String runSomething();
}
 
class Service implements ServiceInterface {
    @Override
    public String runSomething() {
        return "서비스";
    }
}
 
class Decorator implements ServiceInterface {
    ServiceInterface serviceInterface;
 
    @Override
    public String runSomething() {
        System.out.println("호출에 대한 장식 주목적, 클라이언트에게 반환 결과에 장식을 더하여 전달");
 
        serviceInterface = new Service();
        return serviceInterface.runSomething() + "장식";
    }
}
 
public class Test {
    public static void main(String[] args) {
        ServiceInterface decorator = new Decorator();
        System.out.println(decorator.runSomething());
    }
}
 
    

 

싱글톤 패턴(Singleton Pattern)

클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴

싱글톤 패턴이란 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 커넥션 풀, 스레드 풀, 디바이스 설정 객체등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 또 프로그램이 예쌍히 못한 결과를 낳을 수 있다. 싱글톤 패턴의 경우 오직 하나의 인스턴스를 만들고 그것을 재사용한다. 

  • new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
  • 유일한 단일 객체를 반환할 수 있는 정적 메소드가 필요하다.
  • 유일한 단일 객체를 참조할 정적 참조 변수가 필요하다.

싱글톤 패턴의 특징은 다음과 같다.

  • private 생성자를 갖는다.
  • 단일 객체 참조 변수를 정적 속성으로 갖는다.
  • 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메소드를 갖는다.
  • 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//싱글톤 패턴 적용
class Singleton {
    //정적 참조 변수
    static Singleton singletonObject;
    
    //기본 생성자
    private Singleton() {
    };
    
    //객체 반환 정적 메소드
    public static Singleton getInstance() {
        if(singletonObject == null) {
            singletonObject = new Singleton();
        }
        
        return singletonObject;
    }
}
 
public class Test {
    public static void main(String[] args) {
        //기본 생성자가 private 이기에 new를 통한 인스턴스 생성 불가
        //Singleton s = new Singleton();
        
        Singleton singletonObject = Singleton.getInstance();
    }
}
 
                                                            

 

템플릿 메소드 패턴(Template Method Pattern)

상위 클래스의 견본 메소드에서 하위 클래스가 오버라이딩한 메소드를 호출하는 패턴

상위 클래스에 공통 로직을 수행하는 템플릿 메소드와 함위 클래스에 오버라이딩을 강제하는 추상메소드 또는 선택적으로 오버라이딩할 수 있는 훅 메소도를 두는 패턴을 템플릿 메소드 패턴이라고 한다.

  • 템플릿 메소드 : 공통 로직을 수행, 로직 중에 하위 클래스에서 오버라이딩한 추상 메소드/훅 메소드를 호출. playWtihOwner()
  • 템플릿 메소드에서 호출하는 추상 메소드 : 하위 클래스가 반드시 오버리딩해야 한다. play(), 오버라이딩 필수.
  • 템플릿 메소드에서 호출하는 Hook 메소드 : 하위 클래스가 선택적으로 오버라이딩 한다. runSomething(), 오버라이딩 선택.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//템플릿 메소드 패턴 미적용
class Dog{
    public void playWithOwner(){
        System.out.println("이리와");
        System.out.println("멍멍");
        System.out.println("잘했어");
    }
}
 
class Cat{
    public void playWithOwner(){
        System.out.println("이리와");
        System.out.println("야옹");
        System.out.println("잘했어");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.playWithOwner();
 
        Cat cat = new Cat();
        cat.playWithOwner();
    }
}
 
//템플릿 메소드 패턴 적용
abstract class Animal{
    //템플릿 메소드
    public void playWithOwner(){
        System.out.println("이리와");
        play();
        runSomething();
        System.out.println("잘했어");
    }
 
    //추상 메소드
    abstract void play();
 
    //Hook 메소드
    void runSomething(){
 
    }
}
 
class Dog extends Animal{
    @Override
    void play() {
        System.out.println("멍멍");
    }
 
    @Override
    void runSomething() {
        System.out.println("멍!");
    }
}
 
class Cat extends Animal{
    @Override
    void play() {
        System.out.println("야옹");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.playWithOwner();
 
        Animal cat = new Cat();
        cat.playWithOwner();
    }
}
                                                                                                

 

팩토리 메소드 패턴(Factory Method Pattern)

오버라이드된 메소드가 객체를 반환하는 패턴

팩토리 메소드는 객체를 생성 반환하는 메소드를 말한다. 팩토리 메소드 패턴은 하위 클래스에서 팩토리 메소드를 오버라이딩해서 객체를 반환하게 하는 것을 의미한다. 의존 역전 원칙(DIP)를 활용한다.  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//팩토리 메소드 패턴 적용
abstract class Animal {
    //추상 팩토리 메소드
    abstract AnimalToy getToy();
}
 
//팩토리 메소드가 생성할 객체의 상위 클래스
abstract class AnimalToy {
    abstract void identify();
}
 
class Dog extends Animal {
    //추상 팩토리 메소드
    @Override
    AnimalToy getToy() {
        return new DogToy();
    }
}
 
class DogToy extends AnimalToy {
    @Override
    void identify() {
        System.out.println("강아지 장난감");
    }
}
 
class Cat extends Animal {
    //추상 팩토리 메소드
    @Override
    AnimalToy getToy() {
        return new CatToy();
    }
}
 
class CatToy extends AnimalToy {
    @Override
    void identify() {
        System.out.println("고양이 장난감");
    }
}
 
public class Test {
    public static void main(String[] args) {
        //팩토리 메소드를 보유한 객체 생성
        Animal dog = new Dog();
        Animal cat = new Cat();
 
        //팩토리 메소드가 반환하는 객체
        AnimalToy dogToy = dog.getToy();
        AnimalToy catToy = cat.getToy();
 
        //팩토리 메소드가 반환한 객체를 사용
        dogToy.identify();
        catToy.identify();
    }
}
 
                                                                                                    

 

전략 패턴(Strategy Pattern)

클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴

클라이언트는 다양한 전략 중 하나를 선택해 생성한 후 컨텍스트에 주입힌다. 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용되어있다. 전략 패턴을 구성하는 세 요소는 기억해야 한다. 

  • 전략 메소드를 가진 전략 객체
  • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//전략 패턴 적용
interface Strategy {
   void runStrategy();
}
 
class StrategyGun implements Strategy {
    @Override
    public void runStrategy() {
        System.out.println("쏘기");
    }
}
 
class StrategySword implements Strategy {
    @Override
    public void runStrategy() {
        System.out.println("베기");
    }
}
 
class Soldier {
    void runContext(Strategy strategy) {
        System.out.println("전투 시작");
        strategy.runStrategy();
        System.out.println("전투 끝");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Strategy strategy = null;
        Soldier soldier = new Soldier();
 
        strategy = new StrategyGun();
        soldier.runContext(strategy);
 
        strategy = new StrategySword();
        soldier.runContext(strategy);
    }
}
 
                                                                                                 

 

템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)

전략을 익명 내부 클래스로 구현한 전략 패턴

템플릿 콜백 패턴은 전략 패턴의 변형으로 DI에 사용하는 형태의 전략 패턴이다. 전략 패턴과 모든 것이 동일하지만 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//템플릿 콜백 패턴 적용
interface Strategy {
    void runStrategy();
}
 
class Soldier1 {
    void runContext(Strategy strategy) {
        System.out.println("전투 시작");
        strategy.runStrategy();
        System.out.println("전투 끝");
    }
}
 
class Soldier2 {
    void runContext(String weaponSound) {
        System.out.println("전투 시작");
        execute(weaponSound).runStrategy();
        System.out.println("전투 끝");
    }
 
    Strategy execute(String weaponSound){
        return new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println(weaponSound);
            }
        };
    }
}
 
public class Test {
    public static void main(String[] args) {
        //익명 클래스를 이용한 정의
        Soldier1 soldier1 = new Soldier1();
 
        soldier1.runContext(new Strategy() {
            @Override
            public void runStrategy() {
                System.out.println("쏘기");
            }
        });
 
        //익명 클래스를 메소드화
        Soldier2 soldier2 = new Soldier2();
        
        soldier2.runContext("베기");
    }
}
 
                                                                                            

 

출처 : 스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 위키북스, 김종민 저
반응형

'Software Development' 카테고리의 다른 글

[OOP] SOLID 원칙  (0) 2021.10.03
[CI/CD] Jenkins란  (0) 2020.12.21
[CI/CD] CI/CD란  (0) 2020.12.21
[Test] 테스트 케이스(Test Case)란  (0) 2020.12.18