디자인 패턴이란 일종의 설계 기법이며, 설계 방법이다.
목적 : SW 재사용성, 호환성, 유지 보수성을 보장
특징 : 아이디어이며, 특정한 구현이 아니다 / 프로젝트에 항상 적용해야 하는것은 아니지만, 추후 재사용, 호환, 유지 보수시 발생하는 문제 해결을 예방하기 위해 패턴을 만들어 둔 것.
클래스를 바로 사용할 수 없는 경우가 있다.주로 다른 곳에서 개발했다거나, 수정할 수 없을때. 이에 중간에서 변환 역할을 해주는 클래스가 필요하다 -> 어댑터 패턴
사용방법 : 상속
호환되지 않은 인터페이스를 사용하는 클라이언트 그대로 활용 가능
향후 인터페이스가 바뀌더라도, 변경 내역은 어댑터에 캡슐화 되므로 클라이언트 바뀔 필요X
오리와 칠면조 인터페이스 생성
만약 오리 객체가 부족해서 칠면조 객체를 대신 사용해야 한다면?
두 객체는 인터페이스가 다르므로, 바로 칠면조 객체를 사용하는 것은 불가능함
따라서 칠면조 어댑터를 생성해서 활용해야 함
package AdapterPattern;
public interface Duck {
public void quack();
public void fly();
}
package AdapterPattern;
public interface Turkey {
public void gobble();
public void fly();
}
package AdapterPattern;
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
package AdapterPattern;
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");
}
}
package AdapterPattern;
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();
}
}
package AdapterPattern;
public class DuckTest {
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();
}
}
/* 출력결과
The turkey says...
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
*/
애플리케이션이 시작될 때, 어떤 클래스가 최초 한 번만 메모리를 할당(static)하고 해당 메모리에 인스턴스를 만들어 사용하는 패턴 즉, 싱글톤 패턴은 '하나'의 인스턴스만 생성하여 사용하는 디자인 패턴이다.(인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고 기존의 인스턴스를 활용하는 것)
생성자가 여러번 호출되어도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것이다.
Java에서는 생성자를 private으로 선언해 다른 곳에서 생성하지 못하도록 만들고, getInstance() 메소드를 통해 받아서 사용하도록 구현한다.
왜 쓸까? - 객체를 생성할 때마다 메모리 영역을 할당받아야 한다. 하지만 한번의 new를 통해 객체를 생성한다면 메모리 낭비를 방지할 수 있다.
또한 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능한 장점이 있다.
그럼 많이 사용하는 경우는 언제? - 주로 공통된 객체를 여러개 생성해서 사용해야하는 상황 : DB에서 커넥션 풀, 스레드 풀, 캐시, 로그 기록 객체 등등
public class Singleton {
private static Singleton instance; // 단 하나의 인스턴스만 사용
private Singleton() {} // private 생성자. 외부에서 인스턴스 생성못함.
public static Singleton getInstance() { // 단하나의 인스턴스만 사용
if (instance == null){ //여러 스레드에서 이 곳을 동시에 실행하면 문제 발생
instance = new Singleton();
}
return instance;
}
}
public class ThreadSafe_Lazy_Initialization{
private static ThreadSafe_Lazy_Initialization instance;
private ThreadSafe_Lazy_Initialization(){} //private으로 생성자를 만들어 외부에서의 생성을 막음
public static synchronized ThreadSafe_Lazy_Initialization getInstance(){
if(instance == null){
instance = new ThreadSafe_Lazy_Initialization();
}
return instance;
}
}
여러 client의 요청이 있을 때 getInstance() 메서드에 static synchronized를 적용했기에 싱글톤 클래스 자체를 락을 걸었다. 즉, 클래스를 사용할 때 단 하나의 스레드만 사용하게 되는 것이다.
public class ThreadSafe_Lazy_Initialization{
private volatile static ThreadSafe_Lazy_Initialization instance;
private ThreadSafe_Lazy_Initialization(){}
public static ThreadSafe_Lazy_Initialization getInstance(){
if(instance == null) { //첫번째 검사는 락을 사용안함. 이미 초기화되어있다면 바로 리턴하여 동기화 비용을 없애준다.
synchronized (ThreadSafe_Lazy_Initialization.class){ //위와 동일한 static synchronized
if(instance == null){ //두번째 검사는 락을 사용한다. 초기화되어있지 않기 때문에
instance = new ThreadSafe_Lazy_Initialization();
}
}
}
return instance;
}
}
public class Singleton {
private volatile static DoubleCheckedSingleton instance;
}
참고 : https://dev-coco.tistory.com/109
https://gyoogle.dev/blog/design-pattern/Singleton%20Pattern.html