'JAVA/Design Pattern'에 해당되는 글 11건

JAVA/Design Pattern

스트래티지 패턴 (Strategy Pattern)

공통적인 특징을 가진 객체를 

만들 경우 우리는 대게 다음과 같이 

추상클래스나 인터페이스를 만들어 놓고, 그 것을 상속받아 객체를 구현한다.

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
// 추상 클래스
package com.wedul.study.strategy;
 
public abstract class Animals {
 
    public Animals() {}
 
    private String name;
    
    public Animals(String name) {
        this.name = name;
    }
 
    public void eat() {
     System.out.println("a gu a gu");
    }
    
    abstract void attck();
}
 
// 고양이
public class Cat extends Animals {
    public Cat() {}
 
    public Cat(String name) {
        super(name);
    }
 
    @Override
    void attck() {
        System.out.println("scretch and bite");
    }
 
}
 
// 강아지
public class Dog extends Animals {
    public Dog() {}    
 
    public Dog(String name) {
        super(name);
    }
 
    @Override
    void attck() {
        System.out.println("bark and bite");
    }
 
}
 
// main
public class Main {
    public static void main(String args[]) {
        Animals dog = new Dog();
        Animals cat = new Cat();
        
        dog.attck();
        cat.attck();
    }
}
 
 
// 결과
bark and bite
scretch and bite
cs



하지만 이런 유형의 경우 

몇가지 문제점이 발생할 수 있다.

문제점
1. 기존 추가된 객체의 동작을 변경하려는 경우.

강아지는 물지 않고, 고양이는 할퀴지 않는경우에 

기존의 코드를 수정을 해주어야 한다.

하지만 그런 경우 새로운 기능을 위해 기존의 속성을 변경해야 하기에 

OCP를 위반하는 행위이다.

2. 새로운 객체가 추가되었을 경우

새로운 rabbit이라는 객체가 추가되었을 때 고양이의 attack 메소드를 동일하게 사용하고 싶은 경우에도

별도로 만들어 주어야 한다.

이는 중복된 코드를 만드는 좋지 않은 코드 습관이 될 수 있다.

또한 attack의 의 성격이 변경될 경우 토끼와 고양이의 메소드를 모두 수정해 주어야 한다.

해결방법

이렇게 자주 변경되는 속성에 관해서는 

그 속성에 대한 인터페이스를 구분하는 것이 좋다.

위의 예제에서 보면 공격이라는 속성이 자주 변경 되는 경우

공격이라는 인터페이스를 만들고 

공격유형에 따른 객체를 인터페이스를 구현하여 만들고 동물에게 해당 속성을 부여해 주는 방식으로 진행해야 한다.


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
// 공격 타입 인터페이스
public interface AttactType {
    void attack();
}
 
// bite 타입
public class BiteAttackType implements AttactType {
 
    @Override
    public void attack() {
        System.out.println("bite you");
    }
 
}
 
// scretch 타입
public class ScretchType implements AttactType {
 
    @Override
    public void attack() {
        System.out.println("scretch use");
    }
 
}
 
// 공격 속성 부여
public abstract class Animals {
    private String name;
    private AttactType attactType;
    
    public Animals() {}
    
    public Animals(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public AttactType getAttactType() {
        return attactType;
    }
 
    public void setAttactType(AttactType attactType) {
        this.attactType = attactType;
    }
    
}
 
// main (동적으로 속성 부여)
public class Main {
    public static void main(String args[]) {
        Animals dog = new Dog();
        Animals cat = new Cat();
 
        // 속성 부여
        dog.setAttactType(new BiteAttackType());
        cat.setAttactType(new ScretchType());
        
        dog.getAttactType().attack();
        cat.getAttactType().attack();
    }
}
cs




위와 같이 스트래티지 패턴은 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.

스트래트지 패턴은 

역할에 따라 3가지 요소로 구분된다.

1) Strategy 
- 인터페이스나 추상화로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시 ( 위의 예에서 Attack Type 인터페이스)
2) ConcreteStrategy
- 인터페이스를 구현한 실제 전략 객체들 (위의 예에서 bite Attack Type, Scretech Attack Type)
3) Context 
- 스트래티지 패턴을 이용하는 역할을 수행한다.  (위의 예에서 Animals)
- 위에서 생성된 전략을 setter을 통해서 매핑한다.



정리하면 

어떤 주체의 행위는 동일하나,
그 도구가 계속 변경된다면 그 도구를 인터페이스로 정의하고
인터페이스 메서드로 그 행위를 기재 한후.

주체에게 그 도구를 주입해서 동적으로 사용하는 방식을 고려하라.

JAVA/Design Pattern

싱글톤 패턴 (Singleton Pattern)

대게 

공용으로 사용하는 유틸성 클래스의 경우

하나의 공용 인스턴스 객체를 생성하고 

필요로 할 때마다, 해당 이스턴스 객체에 접근 하여 사용한다.

이를 싱글톤 패턴이라고 한다.

싱글톤 패턴
- 하나의 인스턴스만을 생성하는 책임이 있으며, getInstance 메소드를 통해 모든 클라이언트에게 동일한 인스턴스를 반환한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
// 유틸성 클래스의 경우 인스턴스 객체를 만들거나 상속을 할 필요가 없으므로 final과 private 생성자를 만든다.
public final class Util {
  private Utill () {}
  private Util util = null;
 
  public static Util getUtil() {
   if (util == null ) {
     util = new Util();
   }
 
   return util;
  } 
}
cs



하지만 이경우 동시에 여러 스레드가 Util 클래스의 접근하였을 때

경합 조건이 발생하여 두 개 이상의 스레드가 인스턴스 객체를 만들려고 시도 할 수 있다. 

이런경우에 문제가 싱글톤의 장점이 사라진다.

또한 단순히 정보만을 전달해주는 클래스일 경우에는 큰 문제가 없을 수 있으나,

스태틱 변수가 아닌 멤벼 변수 값의 경우 여러 인스턴스 객체가 만들어지면 

잘못된 데이터가 출력되기 때문에 문제가 발생할 수 있다.

ex)
int count = 10;

public void upCount() {
count++;
}

해결방법
1. 정적 변수에 인스턴스를 만들어 초기화 하기 




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 방법 1 
public class Util {
 private static Util = new Util();
 private Util() {}
 
}
 
 
// 방법 2. (Lazy 방식)
public class Util {
  private Util() {}
  public static Util getInstance() {
    return LazyHolder.INSTANCE;
  }
  
  private static class LazyHolder {
    private static final Util INSTANCE = new Util();  
  }
}
cs



클래스가 로드 되는 경우에는 초기화가 한번만 이루어 지기 때문에 인스턴스 객체가 여러개 발생할 수 있는 문제가 야기되지 않는다.

2. Enum 클래스로 사용하기
 Enum은 인스턴스가 여러 개 생기지 않도록 확실하게 보장해주고 복잡한 직렬화나 리플렉션 상황에서도 직렬화가 자동으로 지원된다는 장점이 있어서 

Enum 을 사용해서 싱글톤 패턴을 사용하면 좋다.



1
2
3
4
5
6
7
8
9
10
11
12
13
// Enum
public enum Util {
    INSTANCE;
    
    public void getData() {
        System.out.println("db");
    }
}
 
// Main 사용
public void main(String args[]) {
  Util.INSTANCE.getData();
}
cs



3. 인스턴스 생성 메서드 동기화 하기

1
2
3
4
5
6
7
8
9
10
11
12
public final class Util {
  private Utill () {}
  private Util util = null;
 
  public synchronized static Util getUtil() {
   if (util == null ) {
     util = new Util();
   }
 
   return util;
  } 
}
cs



정적 클래스 VS 싱글톤 객체

정적 클래스는 굳이 인스턴스 객체를 공유해서 사용하지 않고, 정적 클래스를 이용해서 유틸성 클래스를 사용할 수 있다.

또한 정적 메서드를 사용하므로 인스턴스를 사용하는것 보다 성능적으로 우수하다. (컴파일 바인딩 부분에서)

하지만 이런 정적 클래스가 장점이 있어도 사용하지 못하는 경우가 있었다.

항상 모든 메서드가 정적 메서드이어야 하는 정적 클래스의 경우 
공통으로 사용하는 인터페이스의 메서드 또한 정적 메서드여야 한다.

하지만 인터페이스에서 구현된 static 메서드는 상속받아서 구현할 수 없기 때문에 문제가 발생할 수 있다.

하지만 

싱글톤 패턴의 경우 스태틱 메서드를 사용하지 않으므로 인터페이스의 내용을 구현하여 
인스턴스 메서드를 사용할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
public interface Utils {
    static void getData() {
            System.out.println("dbs");
    }
}
 
public class UtilClass implements Utils {
    @Override
    public static void getData() { // 오류 발생
        
    }
}
cs



위와 같은 문제가 아니라면
정적 클래스를 사용하는 것이 싱글톤보다 더욱 편해보인다.



  1. wedul 편집답글

    thread safe single tone 생성법 정리
    https://medium.com/@joongwon/multi-thread-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%AC%EB%B0%94%EB%A5%B8-singleton-578d9511fd42

JAVA/Design Pattern

스테이트 패턴 (state pattern)

스테이트 패턴

객체의 상태를 효율적으로 관리 할 수 있도록 

스테이트 패턴을 알아보자

많은 개체들은 상태값이 변경됨에 따라 다른 일을 수행하게 된다.

이를 객체에 어떤 동작이 수행될 때마다 상태를 확인하고 그에 맞는 동작이 수행되도록 하는 경우

코드가 지저분해지고, 많은 switch, if문을 가지게 될 것이다.

이것을 스테이트 패턴을 이용하여 공통의 상태 인터페이스를 만든 후

각 상태에 따른 객체를 생성한 후 상세한 동작을 상태 객체에서 수행하도록 설정하는 것이다.

문제의 상황의 예를 살펴보자

기존의 객체의 상태값이 있는 경우 다음의 자동차 객체처럼 하는 경우가 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Car {
    private int ON = 1;
    private int OFF = 0;
    private int state;
    
    public Car() {
        // 시동이 꺼있는 상태 
        this.state = OFF;
    }
    
    public void onCar() {
        if (state != ON) {
            state = ON;
        }
    }
    
    public void offCar() {
        if (state != OFF) {
            state = OFF;
        }
    }
     
}
cs



하지만 만약에 시동이 켜진상태에서 다시한번 시동 버튼을 누를 경우
파워모드로 변경되고, 다시 시동버튼을 누르면 일반 모드로 넘어가도록 수정을 한다고 해보자.


다음과 같이 상태값 POWER_MODE_ON을 추가해야 하며,
onCar 메소드도 그에 따라 계속 변경되어야 한다.



1
2
3
4
5
6
7
8
9
10
11
private int POWER_MODE_ON = 2;
 
public void onCar() {
    if (state == ON) {
        this.state = POWER_MODE_ON;
    } else if (state == POWER_MODE_ON ) {
        this.state = ON; 
    } else if (state != ON) {
        state = ON;
    }
}

cs



상태값이 추가되거나 할 때마다 계속 수정된다면 매우 비효율 적이다.

해결방법

이를 해결하기 위해서는 현재 객체(Car)의 상태가 어떠하든지 상관없게 구성하고 
상태 변화에도 독립적 이도록 코드를 수정해야한다.

구성
- Car 객체는 별도의 상태를 나타내는 State를 구현한 객체를 보유한다.
- Car 객체에서 onCar, offCar 동작을 수행할 때마다 state에게 자신을 전달하여 상태를 변경한다.



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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class Car {
    // 자동차의 상태를 가지고 있는다.
    private CarState state;
    
    public Car() {
        // 시동이 꺼있는 상태를 기본으로 가지고 있는다.
        this.state = CarPowerOff.getInstance();
    }
    
    public CarState getState() {
        return state;
    }
 
    public void setState(CarState state) {
        this.state = state;
    }
 
    public void onCar() {
        this.state.on_buutton_pushed(this);
    }
    
    public void offCar() {
        this.state.off_button_pushed(this);
    }    
}
 
// 상태 객체를 구현하기 위한 인터페이스
public interface CarState {
    void on_buutton_pushed(Car car);
    void off_button_pushed(Car car);
}
 
// 시동 파워모드 On
public class CarPowerModeState implements CarState {
    
    private static CarPowerModeState instance = new CarPowerModeState();
    
    public static CarPowerModeState getInstance() {
        return instance;
    }
 
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarOneState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        car.setState(CarPowerOff.getInstance());
    }
 
}
 
// 시동 On
public class CarOneState implements CarState {
    
    private static CarOneState instance = new CarOneState();
    
    public static CarOneState getInstance() {
        return instance;
    }
 
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarPowerModeState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        car.setState(CarPowerOff.getInstance());
    }
 
}
 
// 시동 off
public class CarPowerOff implements CarState {
    private static CarPowerOff instance = new CarPowerOff();
    
    public static CarPowerOff getInstance() {
        return instance;
    }
    
    @Override
    public void on_buutton_pushed(Car car) {
        car.setState(CarOneState.getInstance());
    }
 
    @Override
    public void off_button_pushed(Car car) {
        // Not Work
    }
 
}
 
cs



결과적으로 위에 코드를 보면 알겠지만

결론
상태에 필요 부분을 인터페이스로 명세하고, 
상태별로 각자 필요 메소드를 Override하여 구현한다.

자동차는 현재 상태값을 객체 형태로 가지고 있으며, on 또는 off 요청이 들어왔을 때
현재 자신의 상태 객체에게 자신을 넘겨서 동작을 본인이 하는 것이아니라 현재 상태 객체에게 넘겨서
진행되도록 한다.



JAVA/Design Pattern

커맨드 패턴 (command pattern)

커맨드 패턴은 이벤트가 발생되었을 때

실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스는 변경하지 않고 재사용하고자 할 때 유용하다.

다음 예를 살펴보자

자동차의 
시동버튼이 눌러졌을 때 시동이 켜지도록 설계해보자.



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
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// execute 인터페이스
public interface Execute {
    void execute();
}
 
// 엔진 
public class Engine implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Engine Start");
    }
 
}
cs




하지만 이런 구조에서
버튼을 눌렀을 때, 시동이 아닌 램프를 키고 싶은 경우에는 다음과 같이 변경작업을 진행해야한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// 엔진 
public class Lamp implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Lamp On);
    }
}
cs



이런경우 동작이 변경될 때 
기존의 코드가 변경되면 안된다는 OCP를 위반하게 된다.

이를 다음과 같이 변경하여
이 문제를 해결하는 것이 command pattern이다.



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
75
76
77
78
79
80
81
82
// 버튼 클래스 (동작이되는 Command 인터페이스를 가지고 있는다.)
public class Button {
    private Command executeEle;
    
    public Button(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void setCommand(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void press() {
        executeEle.execute();
    }
    
}
 
// command 인터페이스
public interface Command {
    void execute();
}
 
// 엔진 객체
public class Engine {
 
    public void on() {
        System.out.println("Engine Start");
    }
 
}
 
// 엔진의 실행동작을 가지고 있는 command
public class EngineOnCommand implements Command {
    private Engine engine;
    
    public EngineOnCommand(Engine engine) {
        this.engine = engine;
    }
    
    @Override
    public void execute() {
        this.engine.on();
    }
    
}
 
// Lamp
public class Lamp {
    
    public void on() {
        System.out.println("Lamp on");
    }
}
 
// Lamp를 실행시키는 command
public class LampOnCommand implements Command {
 
    private Lamp lamp;
    
    public LampOnCommand(Lamp lamp) {
        this.lamp = lamp;
    }
    
    @Override
    public void execute() {
        this.lamp.on();
    }
    
}
 
public class Main {
    public static void main(String[] args) {
        // 엔진 키기
        Button button = new Button(new EngineOnCommand(new Engine()));
        button.press();
        
        button.setCommand(new LampOnCommand(new Lamp()));
        button.press();
    }
 
}
cs





실행을 요구하는 Button클래스와 
각 동작을 구현하는 Command 클래스, 그 클래스 내부에 동작이되는 대상을 삽입한다.





한가지 적용 가능한 예를 보면
화면에 메뉴를 구성하는 MenuItem 객체를
공용해서 사용하는데 동작만 변경해서 사용하고 싶을 경우 

1. MenuItem 클래스에 command라는 인터페이스를 가지고 있게 설계한다.
2. 객체의 동작을 호출하는 클래스를 command를 구현하여 작성한다.
3. 2번의 클래스는 각자 자신이 호출할 객체를 가지고 있다.

이렇게 설계하면 MenuItem 클래스에서 동작을 
변경하여 사용할 수 있다.


결론
기능의 실행을 요구하는 호출자(invoke)와 실제 기능을 수행하는 수진자(Receiver)사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 호출자 클래스를 수정없이 사용할 수 있어야 한다.

결국 서로간의 의존성 및 커플링을 줄일 수 있도록 설계해야 한다.



JAVA/Design Pattern

커맨드 패턴 (command pattern)

커맨드 패턴은 이벤트가 발생되었을 때

실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스는 변경하지 않고 재사용하고자 할 때 유용하다.

다음 예를 살펴보자

자동차의 
시동버튼이 눌러졌을 때 시동이 켜지도록 설계해보자.



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
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// execute 인터페이스
public interface Execute {
    void execute();
}
 
// 엔진 
public class Engine implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Engine Start");
    }
 
}
cs




하지만 이런 구조에서
버튼을 눌렀을 때, 시동이 아닌 램프를 키고 싶은 경우에는 다음과 같이 변경작업을 진행해야한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 버튼 클래스
public class Button {
    private Engine engine;
    
    public Button(Engine engine) {
        this.engine = engine;
    }
    
    public void on() {
        engine.execute();
    }
    
}
 
// 엔진 
public class Lamp implements Execute {
 
    @Override
    public void execute() {
        System.out.println("Lamp On);
    }
}
cs



이런경우 동작이 변경될 때 
기존의 코드가 변경되면 안된다는 OCP를 위반하게 된다.

이를 다음과 같이 변경하여
이 문제를 해결하는 것이 command pattern이다.



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
75
76
77
78
79
80
81
82
// 버튼 클래스 (동작이되는 Command 인터페이스를 가지고 있는다.)
public class Button {
    private Command executeEle;
    
    public Button(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void setCommand(Command executeEle) {
        this.executeEle = executeEle;
    }
    
    public void press() {
        executeEle.execute();
    }
    
}
 
// command 인터페이스
public interface Command {
    void execute();
}
 
// 엔진 객체
public class Engine {
 
    public void on() {
        System.out.println("Engine Start");
    }
 
}
 
// 엔진의 실행동작을 가지고 있는 command
public class EngineOnCommand implements Command {
    private Engine engine;
    
    public EngineOnCommand(Engine engine) {
        this.engine = engine;
    }
    
    @Override
    public void execute() {
        this.engine.on();
    }
    
}
 
// Lamp
public class Lamp {
    
    public void on() {
        System.out.println("Lamp on");
    }
}
 
// Lamp를 실행시키는 command
public class LampOnCommand implements Command {
 
    private Lamp lamp;
    
    public LampOnCommand(Lamp lamp) {
        this.lamp = lamp;
    }
    
    @Override
    public void execute() {
        this.lamp.on();
    }
    
}
 
public class Main {
    public static void main(String[] args) {
        // 엔진 키기
        Button button = new Button(new EngineOnCommand(new Engine()));
        button.press();
        
        button.setCommand(new LampOnCommand(new Lamp()));
        button.press();
    }
 
}
cs



실행을 요구하는 Button클래스와 
각 동작을 구현하는 Command 클래스, 그 클래스 내부에 동작이되는 대상을 삽입한다.





한가지 적용 가능한 예를 보면
화면에 메뉴를 구성하는 MenuItem 객체를
공용해서 사용하는데 동작만 변경해서 사용하고 싶을 경우 

1. MenuItem 클래스에 command라는 인터페이스를 가지고 있게 설계한다.
2. 객체의 동작을 호출하는 클래스를 command를 구현하여 작성한다.
3. 2번의 클래스는 각자 자신이 호출할 객체를 가지고 있다.

이렇게 설계하면 MenuItem 클래스에서 동작을 
변경하여 사용할 수 있다.


결론
기능의 실행을 요구하는 호출자(invoke)와 실제 기능을 수행하는 수진자(Receiver)사이의 의존성을 제거한다. 따라서 실행될 기능의 변경에도 호출자 클래스를 수정없이 사용할 수 있어야 한다.

결국 서로간의 의존성 및 커플링을 줄일 수 있도록 설계해야 한다.



 [ 1 ]  [ 2 ]  [ 3 ] 

푸터바

알림

이 블로그는 구글에서 제공한 크롬에 최적화 되어있고, 네이버에서 제공한 나눔글꼴이 적용되어 있습니다.

카운터

  • Today : 0
  • Yesterday : 460
  • Total : 82,691