디자인 패턴이란 프로그램을 설계할 때 발생 했던 문제들을 객체간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 규약 형태로 만들어 놓은 것을 말한다.
하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다. 하나의 인스턴스를 만들어 놓고 그 인스턴스를 재활용하는것.
아래 코드를 봤을 때 싱글톤 a, b 여러번 객체를 생성하는게 아닌가 생각했는데 생성자가 여러번 호출되도 제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것라고한다.
(java에서는 생성자를 private으로 선언해 다른 곳에서 생성하지 못하도록 만들고, getInstance() 메소드를 통해 받아서 사용하도록 구현한다)
데이터베이스 연결 모듈(커넥션풀), 스레드 풀, 캐시, 로그 기록 객체 등에 많이 사용됨
객체 : 소프트웨어 세계에 구현해야할 대상 (소프트웨어 관점에서 concept 일뿐)
클래스 : 객체를 구현하기 위한 설계도
인스턴스 : 소프트웨어 세계에 구현된 실체 (설계도를 바탕으로 객체를 소프트웨어에 실체화 하면 인스턴스, 인스턴스는 객체에 포함된다)
class Singleton{
private static class singleInstanceHolder{ //외부에서 생성자를 호출할 수 없게 private로 선언한다.
private static final Singleton Instance=new Singleton(); //자신을 멤버로 선언하여 메모리에 올려 놓기 (static)
} //그래도 private이므로
public static synchronized Singleton getInstance() {
//외부에서 멤버로 선언된 car를 가져올 수 있는 method를 만든다
return singleInstanceHolder.Instance;
}
}
public class HelloWorld {
public static void main(String[] args) {
Singleton a=Singleton.getInstance();
Singleton b=Singleton.getInstance();
System.out.println(a.hashCode()); //해시알고리즘에 의해 생성된 정수 값
//equals 비교에 사용되는 정보가 변경되지 않는다면 애플리케이션이 실행되는 동안 그 객체의 hashcode메소드는 항상 같은 값을 반환한다.
System.out.println(b.hashCode());
if(a==b) {
System.out.println(true);
}
}
}
객체를 생성할 때마다 메모리 영역을 할당받아야 한다. 하지만 한번의 new를 통해 객체를 생성한다면 메모리 낭비를 방지할 수 있다.
또한 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능한 장점이 있다.
싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아져서 객체지향 설계원칙 중 개방-폐쇄 원칙가 위배된다.
결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생한다. 그래서 TDD를 할 때 단위테스트를 주로 하는데 단위테스트는 서로 독립적이어야 하며 테스트를 어떤 순서로든 수행할 수 있어야한다. 하지만 싱글톤 패턴은 하나의 인스턴스를 기반으로 구현하는 패턴이므로 독립적인 인스턴스를 만ㄷ르기가 어렵다.
또한, 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제도 발생할 수 있다.
<동기화>
임계영역(멀티 스레드에 의해 공유자원이 서로 참조될 수 있는 코드의 범위)에서 스레드들이 순서를 갖춰 자원을 사용하게 하는 것을 동기화라고한다. 동기화 메소드를 구현하기 위해서는 synchronized 키워드를 사용한다. 동기화를 처리하기위해 모든객체에 락을 포함 시켰다. 락은 모든 객체가 힙영역에 생성될 때 자동으로 만들어진다.
public class ThreadSafe_Lazy_Initialization{
private static ThreadSafe_Lazy_Initialization instance;
private ThreadSafe_Lazy_Initialization(){}
public static synchronized ThreadSafe_Lazy_Initialization getInstance(){
if(instance == null){
instance = new ThreadSafe_Lazy_Initialization();
}
return instance;
}
}
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){
if(instance == null){
instance = new ThreadSafe_Lazy_Initialization();
}
}
}
return instance;
}
}
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
팩토리 패턴은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이다.
= 객체를 만드는 부분을 Sub Class에 맡기는 패턴
상속관계의 두 클래스에서 상위클래스가 중요한 뼈대를 결정하고 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴이다.
객체지향의 디자인패턴 기본원칙중 개방 폐쇠 원칙, 확장에 있어서는 열려있어야 하며, 수정에 있어서는 닫혀 있어야 한다에서 수정이 일어날 가능성이 큰 부분과 그렇지 않은 부분을 분리하는 것이 좋다는 것을 알 수 있다. 그래서 팩토리 패턴이 나타내게 된것이다.
public abstract class RobotFactory {
abstract Robot createRobot(String name);
}
public class SuperRobotFactory extends RobotFactory {
@Override
Robot createRobot(String name) {
switch(name) {
case "super" :
return new SuperRobot();
case "power" :
return new PowerRobot();
}
return null;
}
}
아래의 클래스는 RobotFactory 클래스를 상속하고 있기 때문에 반드시 createRobot을 선언해야함. name으로 건너오는 값에 따라서 생성되는 Robot이 다르게 설계된다.
숫자를 전달하거나 문자열을 전달함에 따라 다른 타입의 객체를 생성한다.
패턴을 구현할 많은 서브 클래스를 도입함으로써 코드가 복잡해 질 수 있음
->
객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴이다 .