- @참고: https://javaplant.tistory.com/21

 

- 싱글톤 패턴이란

Singleton 패턴은 인스턴스를 불필요하게 생성하지 않고 오직 JVM내에서 한 개의 인스턴스만 생성하여 재사용을 위해 사용되는 디자인패턴이다.

 

6. LazyHolder Singleton 패턴 - 5에서 사용안하는 인스턴스를 만들어 놓는 문제를 개선

더보기
public class Singleton {
    private Singleton(){}
    
    public static Singleton getInstance() {
    	return LazyHolder.INSTANCE;
    }
    
    private static class LazyHolder {
    	private static final Singleton INSTANCE = new Singleton();
    }
}

 

5. static 초기화를 이용한 Singleton 패턴 - 간결한 소스와 성능 개선

더보기
public class Singleton {
	private static Singleton instance;
    
    static {
    	instance = new Singleton();
    }
    
    private Singleton(){}
    
    public static Singleton getInstance() {
    	return instance;
    }
}

 

4. volatile 을 이용한 개선된 DCL Singleton 패턴 (jdk 1.5 이상에서 사용) - 3에서 인스턴스를 null 로 줄 수 있는 문제점 개선

더보기
public class Singleton {
	private volatile static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
    	if (instance == null) {
        	synchronized (Singleton.class) {
            	if (instance == null) {
                	instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

(사용해선 안됨)3. DCL(Double-Checked-Locking) Singleton 패턴 - 2에서 생기는 lock 의 효율성을 개선

더보기
public class Singleton {
	private static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
    	if (instance == null) {
        	synchronized (Singleton.class) {
            	if (instance == null) {
                	instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

2. synchronized 를 이용한 Singleton 패턴 - multi-thread 환경에서 1에서 두개의 인스턴스가 생길 가능성을 개선

더보기
public class Singleton {
	private static Singleton instance;
    
    private Singleton(){}
    
    public static synchronized Singleton getInstance() {
    	if (instance == null) {
        	instance = new Singleton();
        }
        return instance;
    }
}

 

1. 고전적 방식의 Singleton 패턴

더보기
public class Singleton {
	private static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
    	if (instance == null) {
        	instance = new Singleton();
        }
        return instance;
    }
}

'Design Pattern' 카테고리의 다른 글

디자인 패턴의 분류  (0) 2020.01.03
블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://refactoring.guru/design-patterns/facade

 

Facade

Intent Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes. Problem Imagine that you must make your code work with a broad set of objects that belong to a sophisticated l

refactoring.guru

🙌 취지

퍼사드는 라이브러리, 프레임워크, 또는 기타 복잡한 클래스 집합에 심플한 인터페이스를 제공하는 구조적 디자인 패턴입니다.

🤔 문제점

복잡한 라이브러리나 프레임워크에 속한 광범위한 객체 집합에 대해 코드를 작동시켜야 한다고 상상해보십시오. 일반적으로, 당신은 모든 객체를 초기화해야하고, 의존성을 추적하고, 올바른 순서로 메소드를 실행하는 등등을 합니다.

그 결과, 당신의 클래스에 대한 비즈니스 로직은 타사 클래스의 구현 세부사항과 밀접하게 결합되고, 이는 이해와 유지를 어렵게 합니다.

😊 해결책

퍼사드는 많은 동적 부분을 포함하는 복잡한 서브시스템에 단순한 인터페이스를 제공하는 클래스입니다. 퍼사드는 서브시스템을 직접 사용하는 것과 비교하여 제한된 기능을 제공할 수 있습니다. 그러나 클라이언트가 실제로 관심을 갖는 기능만 포함합니다.

수십가지 기능을 갖는 복잡한 라이브러리와 앱을 통합해야 할 필요가 있을 때 퍼사드를 사용하는 것이 편리하지만, 약간의 기능이 필요합니다.

예를 들어, 고양이에 대한 짧은 재밌는 동영상을 소셜 미디어에 업로드하는 앱은 전문적인 비디오 변환 라이브러리를 사용할 수 있습니다. 그러나 필요한 모든 것은 encode(filename, format)이라는 단일 메소드를 가지는 클래스입니다. 이러한 클래스를 생성하고 동영상 변환 라이브러리와 연결하면, 첫번째 퍼사드가 됩니다.

🚗 실세계에 비유

전화로 주문하기

상점에 전화를 걸어 전화 주문을 하면, 운영자는 상점의 모든 서비스와 부서에 대한 퍼사드(정면)입니다. 운영자는 주문 시스템, 지불 게이트웨이 및 다양한 배송 서비스에 대한 간단한 음성 인터페이스를 제공합니다.

🔦구조

1. Facade는 서브시스템 기능의 특정 부분에 편리하게 접근할 수 있도록 합니다. 고객의 요청을 지시하는 위치와 모든 동적 부품을 작동하는 방법을 압니다.

2. 추가적 퍼사드(Additional Facade) 클래스는 또다른 복잡한 구조를 만들 수 있는 관련 없는 기능으로 단일 퍼사드를 오염하는 걸 방지하기 위해 생성될 수 있습니다. 추가 퍼사드는 클라이언트와 다른 퍼사드 모두에게 사용될 수 있습니다.

3. 복잡한 서브시스템(Complex Subsystem)은 수십의 다양한 객체로 구성됩니다. 이들 모두가 의미있는 일을 하게 하려면, 올바른 순서로 객체를 초기화하고 데이터를 적절한 포맷으로 제공하는 것과 같은 서브시스템의 구현 세부사항을 자세히 살펴봐야 합니다.

 서브시스템 클래스는 퍼사드의 존재를 인식하고 있지 않습니다. 이들은 시스템 내에서 작동하고 서로 직접 작업합니다.

# 짜가코드

이 예제에서, 퍼사드 패턴은 복잡한 비디오 변환 프레임워크와의 상호작용을 단순화합니다.

단일 퍼사드 클래스 내에서 여러 종속성을 격리하는 예제

코드를 수십의 프레임워크 클래스와 직접 작동시키는 대신, 해당 기능을 캡슐화하고 코드의 나버지 부분에서 숨기는 퍼사드 클래스를 생성합니다. 이 구조는 또한 향후 버전의 프레임워크로 업그레이드 하거나 다른 것과 대체하는 노력을 최소화하는걸 돕습니다. 당신이 앱에서 바꿀 필요가 있는 것은 퍼사드 메소드의 구현뿐입니다.

//이것은 복잡한 타사 비디오 변환 프레임워크 클래스의 일부입니다. 코드를 제어하지 않으므로 단순화할 수 없습니다.

class VideoFile
// ...

class OggCompressionCodec
// ...

class MPEG4CompressionCodec
// ...

class CodecFactory
// ...

class BitrateReader
// ...

class AudioMixer
// ...


//프레임워크의 복잡성을 단순한 인터페이스 뒤에 숨기기 위해 퍼사드 클래스를 만듭니다. 기능과 단순성 간의 균형을 유지합니다.
class VideoConverter is
	method convert(filename, format):File is
    	file = new VideoFile(filename)
        sourceCodec = new CodeFactory.extract(file)
        if (format == "mp4")
        	destinationCodec = new MPEG4CompressionCodec()
        else
        	destinationCodec = new OggCompressionCodec()
		buffer = BitrateReader.read(filename, sourceCodec)
        result = BitrateReader.convert(buffer, destinationCodec)
        result = (new AudioMixer()).fix(result)
        return new File(result)
        
//어플리케이션 클래스는 복잡한 프레임워크에서 제공하는 10억개의 클래스에 의존하지 않습니다. 또한, 프레임워크를 전환하기로 결정한다면, 퍼사드 클래스만 다시 작성하면 됩니다.
class Application is
	method main() is
    	converter = new VideoConverter()
        mp4 = convertor.convert("funny-cats-video.ogg", "mp4")
        mp4.save()

💡 적용

  • 복잡한 서브시스템에 대한 제한적이지만 간단한 인터페이스가 필요한 경우 퍼사드 패턴을 사용합니다.
    • 종종, 서브시스템은 시간이 지남에 따라 더욱 복잡해집니다. 디자인 패턴을 적용하더라도 일반적으로 더 많은 클래스를 생성하게 합니다. 서브시스템은 다양한 상황에서 더 유연하고 재사용하기 쉬워질 수 있지만, 클라이언트에서 요구하는 구성과 상용구 코드의 양은 점점 더 커지고 있습니다. 퍼사드는 클라이언트의 요구사항에 맞는 서브시스템의 가장 많이 사용되는 기능에 대한 바로가기를 제공하여 이러한 문제를 해결하려 합니다.
  • 서브시스템을 계층으로 구조화하려는 경우 퍼사드를 사용합니다.
    • 서브시스템의 각 단계에 대한 진입점을 정의하는 퍼사드를 작성하세요. 퍼사드를 통해서만 통신하게 함으로써 다중 서브시스템 간의 커플링을 줄일 수 있습니다.
    • 예를 들어, 동영상 변환 프레임워크로 돌아가 보겠습니다. 비디오와 오디오 관련의 두 계층으로 나눌 수 있습니다. 각 계층에 대해, 퍼사드를 생성하고 각 계층의 클레스가 해당 퍼사드를 통해 서로 통신하도록 할 수 있습니다. 이 방법은 중재자 패턴과 매우 유사합니다.

📋 구현 방법

  1. 기존 서브시스템이 제공하는 것보다 더 간단한 인터페이스를 제공할 수 있는지 체크합니다. 인터페이스가 클라이언트 코드를 많은 서브시스템의 클래스와 독립적으로 만들고 있으면 올바른 길을 가고 있습니다.
  2. 새 퍼사드 클래스에서 이 인터페이스를 선언하고 구현합니다. 퍼사드는 호출을 클라이언트 코드로부터 서브시스템의 적절한 객체로 리다이렉트해야 합니다. 퍼사드는 클라이언트 코드가 이미 수행하지 않는 한 서브시스템을 초기화하고 추가 수명 주기를 관리해야 합니다.
  3. 패텀의 이점을 최대한 활용하려면 모든 클라이언트 코드가 퍼사드를 통해서만 서브시스템과 통신하도록 하세요. 그러면 클라이언트 코드는 서브시스템 코드의 변경 사항으로 부터 보호됩니다. 예를 들어, 서브시스템이 새 버전으로 업그레이드 되면 퍼사드의 코드만 수정하면 됩니다.
  4. 퍼사드가 너무 커지면, 동작 부분을 새롭고 세련된 퍼사드 클래스로 추출하는 걸 고려합니다.

⚖ 장단점

✔ 서브시스템의 복잡성으로 부터 코드를 분리할 수 있습니다.

❌ 퍼사드는 앱의 모든 클래스와 연결된 신 객체가 될 수 있습니다.

🐱‍🏍 다른 패턴과의 관계

  • 퍼사드는 기존 객체에 대한 새 인터페이스를 정의하는 반면, 어댑터는 기존 인터페이스를 사용 가능하게 만듭니다. 어댑터는 보통 한 객체를 감싸는 반면, 퍼사드는 객체의 전체 서브시스템에서 작동합니다.
  • 추상 팩토리는 클라이언트 코드에서 서브시스템의 객체가 생성되는 방식만 숨기려는 경우 퍼사드의 대체로 쓸 수 있습니다.
  • Flyweight는 많은 작은 객체를 만드는 방법을 보여주는 반면, 퍼사드는 전체 서브시스템을 나타내는 단일 객체를 만드는 방법을 보여줍니다.
  • 퍼사드중재자는 비슷한 작업을 수행합니다. 강하게 결합된 클래스들 간의 콜라보레이션을 조직하려 합니다.
    • 퍼사드는 단순화된 인터페이스를 정의하지만, 새로운 기능을 도입하지는 않습니다. 서브시스템 자체는 퍼사드를 인식하지 못합니다. 서브시스템 내 객체는 직접 통신할 수 있습니다.
    • 중재자는 시스템 구성 요소 간의 통신을 중앙화합니다. 구성 요소는 중개자 객체에 대해서만 알고 있으며 직접 통신하지 않습니다.
  • 퍼사드 클래스는 대부분의 경우 단일 파사드 오브젝트로 충분하기 때문에 종종 싱글톤으로 변환될 수 있습니다.
  • 퍼사드는 복잡한 객체를 버퍼링하고 자체적으로 초기화한다는 점에서 프록시와 유사합니다. 퍼사드와 달리, 프록시는 서비스 객체와 동일한 인터페이스를 가지고 있어 상호 교환이 가능합니다.

</> 코드 예제

복잡한 동영상 변환 라이브러리에 대한 간단한 인터페이스

이 예제에서, 퍼사드는 복잡한 동영상 변환 프레임워크와의 통신을 단순화 합니다.

퍼사드는 프레임워크의 올바른 클래스를 구성하고 올바른 형식으로 결과를 검색하는 모든 복잡성을 처리하는 단일 메소드를 단일 클래스에 제공합니다.

📁 some_complex_media_library: Complex video conversion library

📄 some_complex_media_library/VideoFile.java

package some_complex_media_library;

public class VideoFile {
	private String name;
	private String codecType;
	
	public VideoFile(String name) {
		this.name = name;
		this.codecType = name.substring(name.indexOf(".") + 1);
	}
	
	public String getCodecType() {
		return codecType;
	}
	
	public String getName() {
		return name;
	}
}

📄 some_complex_media_library/Codec.java

package some_complex_media_library;

public interface Codec {
}

📄 some_complex_media_library/MPEG4CompressionCodec.java

package some_complex_media_library;

public class MPEG4CompressionCodec implements Codec {
	public String type = "mp4";
}

📄 some_complex_media_library/OggCompressionCodec.java

package some_complex_media_library;

public class OggCompressionCodec implements Codec {
	public String type = "ogg";
}

📄 some_complex_media_library/CodecFactory.java

package some_complex_media_library;

public class CodecFactory {
	public static Codec extract(VideoFile file) {
		String type = file.getCodecType();
		if (type.equals("mp4")) {
			System.out.println("CodecFactory: extracting mpeg audio...");
			return new MPEG4CompressionCodec();
		}
		else {
			System.out.println("CodecFactory: extracting ogg audio...");
			return new OggCompressionCodec();
		}
	}
}

📄 some_complex_media_library/BitrateReader.java

package some_complex_media_library;

public class BitrateReader {
	public static VideoFile read(VideoFile file, Codec codec) {
		System.out.println("BitrateReader: reading file...");
		return file;
	}
	
	public static VideoFile convert(VideoFile buffer, Codec codec) {
		System.out.println("BitrateReader: writing file...");
		return buffer;
	}
}

📄 some_complex_media_library/AudioMixer.java

package some_complex_media_library;

import java.io.File;

public class AudioMixer {
	public File fix(VideoFile result) {
		System.out.println("AudioMixer: fixing audio...");
		return new File("tmp");
	}
}

📁 some_complex_media_library: Complex video conversion library

📄 some_complex_media_library/VideoFile.java: Facade provides simple interface of video conversion

package facade;

import java.io.File;

import some_complex_media_library.AudioMixer;
import some_complex_media_library.BitrateReader;
import some_complex_media_library.Codec;
import some_complex_media_library.CodecFactory;
import some_complex_media_library.MPEG4CompressionCodec;
import some_complex_media_library.OggCompressionCodec;
import some_complex_media_library.VideoFile;

public class VideoConversionFacade {
	public File convertVideo(String fileName, String format) {
		System.out.println("VideoconversionFacade: conversion started.");
		VideoFile file = new VideoFile(fileName);
		Codec sourceCodec = CodecFactory.extract(file);
		Codec destinationCodec;
		if (format.contentEquals("mp4")) {
			destinationCodec = new MPEG4CompressionCodec();
		} else {
			destinationCodec = new OggCompressionCodec();
		}
		VideoFile buffer = BitrateReader.read(file, sourceCodec);
		VideoFile intermediateResult = BitrateReader.convert(buffer, destinationCodec);
		File result = (new AudioMixer()).fix(intermediateResult);
		System.out.println("VideoConversionFacade: conversion completed.");
		return result;
	}
}

📄 some_complex_media_library/Codec.java

import java.io.File;

import facade.VideoConversionFacade;

public class Demo {
	public static void main(String[] args) {
		VideoConversionFacade converter = new VideoConversionFacade();
		File mp4Video = converter.convertVideo("youtubevideo.ogg", "mp4");
		// ...
	}
}

📄 OutputDemo.txt: Execution result

VideoconversionFacade: conversion started.
CodecFactory: extracting ogg audio...
BitrateReader: reading file...
BitrateReader: writing file...
AudioMixer: fixing audio...
VideoConversionFacade: conversion completed.
블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://refactoring.guru/design-patterns/factory-method

 

Factory Method

/ Design Patterns / Creational Patterns Also known as: Virtual Constructor Factory Method Intent Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objec

refactoring.guru

🙌 취지

팩토리 메소드는 수퍼클래스에서 객체를 생성하기 위한 인터페이스를 제공하지만, 서브클래스가 작성 될 객체의 타입을 변경할 수 있는 생산적 디자인 패턴인입니다.

🤔 문제점

물류 관리 어플리케이션을 만들고 있다고 생각해보십시오. 앱의 첫 번째 버전은 트럭 운송 만 처리 할 수 있으므로 대부분의 코드는 Truck 클래스 내에 있습니다.

잠시 후, 앱이 꽤 유명해집니다. 매일 해상 운동 회사로부터 앱에 해상 물류를 통합하기 위해 수십 건의 요청을 받습니다.

나머지 코드가 이미 기존 클래스에 연결되어 있으면 프로그램에 새 클래스를 추가하는 것은 그리 간단치 않습니다.

좋은 소식이지요? 그러나 코드는 어떻나요? 현재, 대부분의 코드는 Truck 클래스에 연결되어 있습니다. Ships를 앱에 추가하려면 전체 코드베이스를 변경해야합니다. 또한, 나중에 다른 유형의 운송을 앱에 추가하기로 결정한다면, 모든 이 변경를 다시 해야할지 모릅니다.

그 결과로, 운송 객체 클래스에 따라 앱의 동작을 전환하는 조건부으로 가득찬 매우 불쾌한 코드가 생깁니다.

😊 해결책

팩토리 메소드 패턴은 직접 객체 생성 호출(new 연산자를 사용한)을 특수 팩토리 메소드 호출로 대체하는 걸 제안합니다. 걱정하지 마십세요. 객체는 여전히 new 연산자를 통해 생성되지만, 팩토리 메소드 내부에서 호출됩니다. 팩토리 메소드에 의해 반환된 객체는 종종 "제품(products)"라 불립니다.

서브클래스는 팩토리 메소드에 의해 반환되는 객체의 클래스를 변경할 수 있습니다.

언뜻보기에, 이 변화는 의미 없을 수 있습니다. 생성자 호출을 프로그램의 한부분에서 다른부분으로 옮겼을 뿐입니다. 그러나, 다음 사항을 고려하세요. 이제 서브클래스 안의 팩토리 메소드를 오버라이드 할 수 있고 메소드에 의해 생성된 제품 클래스를 변경할 수 있습니다.

그러나 약간의 제한이 있습니다. 서브클래스는 이 제품이 공통의 베이스 클래스나 인터페이스를 갖을 때에만 다른 타입의 제품을 반환할 수 있습니다. 또한, 베이스 클래스의 팩토리 메소드는 이 인터페이스로 반환 타입이 선언되어 있어야 합니다.

모든 제품은 동일한 인터페이스를 따라야 합니다.

예를 들어, TruckShip 클래스는 Transport 인터페이스를 구현해야하며,  deliver라는 메소드를 선언합니다. 각각의 클래스는 이 메소드를 다르게 구현합니다. 트럭은 화물을 육상으로 운송하고, 배는 해상으로 운송합니다. RoadLogistics 클래스의 팩토리 메소드는 트럭 객체를 반환하는 반면, SeaLogistics 클래스의 팩토리 메소드는 배를 반환합니다.

모든 제품 클래스가 공통 인터페이스를 구현하는 한, 객체를 클라이어트 코드에 중단 없이 전달할 수 있습니다.

팩토리 메소드를 사용하는 코드(종종 클라이언트 코드라 불리는)는 다양한 서브클래스에서 반환한 실제 프로덕트 사이에 차이를 보이지 않습니다. 클라이언트는 모든 프로덕트를 추상 Transport로 취급합니다. 클라이언트는 모든 전송 객체가 deliver 메소드를 갖고 있어야 한다는 알지만, 정확한 작동 방식은 클라이언트에게 중요하지 않습니다.

🔦구조

1. Product는 크리에이터와 그 서브클래스가 생성할 수 있는 모든 객체에 공통인 인터페이스를 선언합니다.

2. Concrete Products는 제품 인터페이스의 다른 구현입니다.

3. Creator 클래스는 새 프로덕트 객체를 반환하는 팩토리 메소드를 선언합니다. 이 메소드의 반환 타입이 프로덕트 인터페이스와 일치해야합니다.

팩토리 메소드를 추상으로 선언하여 모든 서브클래스가 고유한 버전의 메소드를 구현하게 할 수 있습니다. 대안으로, 베이스 팩토리 메소드는 몇가지 기본 프로덕트 타입을 반환할 수 있습니다.

이름과 맞지 않게, 프로덕트 생성은 크리에이터의 주요 책임이 아닙니다. 일반적으로, 크리에이터 클래스에는 이미 프로덕트와 관련된 일부 핵심 비즈니스 로직이 있습니다. 팩토리 메소드는 이 로직이 구체적 프로덕트 클래스로부터 분리되도록 돕습니다. 비유를 하자면, 큰 소프트웨어 개발 회사는 프로그래머를 위한 교육 부서를 운영할 수 있습니다. 그러나, 회사 전체의 주 기능은 코드를 작성하는 것이지 프로그래머를 기르는게 아닙니다.

4. Concrete Creators는 다른 제품 타입을 반환하도록 기본 팩토리 메소드를 재정의합니다.

팩토리 메소드는 항상 새 인스턴스를 생성할 필요가 없습니다. 캐시, 객체 풀, 또는 다른 소스에서 기존 객체를 반환할 수 있습니다.

# 짜가코드

이 예제는 팩토리 메소드를 사용하여 클라이언트 코드를 구체적인 UI 클래스에 연결하지 않고 플랫폼 간 UI 요소를 작성하는 방법을 보여줍니다.

플랫폼 간 대화의 예

기본 대화 상자 클래스는 다른 UI 요소를 사용하여 창을 렌더링합니다. 다양한 운영 체제에서, 이러한 요소는 약간 다르게 보일 수 있지만, 여전히 일관되게 동작해야 합니다. 윈도우의 버튼은 여전히 리눅스의 버튼입니다.

팩토리 메소드가 작동하면, 각 운영 체제에 대한 대화 상자의 로직을 재작성할 필요가 없습니다. 기본 대화상자 클래스 안에 버튼을 생성하는 팩토리 메소드를 선언하면, 나중에 팩토리 메소드에서 윈도우 스타일 버튼을 반환하는 대화 상자 서브 클래스를 생성할 수 있습니다. 그런 다음 서브클래스는 대부분의 대화 상자의 코드를 기본 클래스로에서 상속하지만, 팩토리 메소드 덕분에, 화면에 윈도우 모양의 버튼을 렌더링할 수 있습니다.

이 패턴이 작동하려면 기본 대화상자 클래스는 추상 버튼(모든 구체적인 버튼이 따르는 기본 클래스나 인터페이스)와 함께 작동해야 합니다. 이런 방식으로 대화 상자의 코드는 작동하는 버튼의 유형에 관계없이 작동합니다.

물론, 이 방법을 다른 UI 요소에도 적용할 수 있습니다. 그러나, 대화 상자에 추가할 때마다 각각의 새 팩토리 메소드가 있으면, 추상 팩토리 패턴에 더 가깝습니다. 나중에 이 패턴에 대해 이야기하겠습니다.

//크리에이터 클래스는 제품 클래스 객체를 반환해야 하는 팩토리 메소드를 선업합니다. 크리에이터의 서브 클래스는 일반적으로 이 메소드의 구현을 제공합니다.
class Dialog is
	//크리에이터는 팩토리 메소드의 몇가지 기본 구현을 제공할 수도 있습니다.
    abstract method createButton():Button
    
    //이름에도 불구하고, 크리에이터의 기본 책임은 제품을 만드는 것이 아닙니다. 보통 팩토리 메소드에 의해 반환되는 제품 객체에 의존하는 몇가지 핵심 비즈니스 로직을 포함합니다. 서브클래스는 이 비즈니스 로직을 팩토리 메소드를 재정의하고 다른 유형의 프로젝트를 반환함으로써 간접적으로 바꿀 수 있습니다.
    method render() is
    	//제품 객체를 생성하기 위해 팩토리 메소드를 호출합니다.
        Button okButton = createButton()
        //이제 제품을 사용합니다.
        okButton.onClick(closeDialog)
        okButton.render()
        
//구체적인 크리에이터는 결과 제품의 타입을 바꾸기 위해 팩토리 메소드를 재정의합니다.
class WindowsDialog extends Dialog is
	method createButton():Button is
    	return new WindowsButton()
        
class WebDialog extends Dialog is
	method createButton():Button is
    	return new HTMLButton()
        
//제품 인터페이스는 모든 구체적 제품이 구현해야하는 작업을 선언합니다.
interface Button is
	method render()
    method onClick(f)
    
//구체적 제품은 제품 인터페이스의 다양한 구현을 제공합니다.
class WindowButton implements Button is
	method render(a, b) is
    	//윈도우 스타일로 버튼을 렌더링합니다.
	method onClick(f) is
    	//OS 본연의 클릭 이벤트를 바인딩합니다.
        
class HTMLButton implements Button is
	method render(a, b) is
    	//버튼의 HTML 표현을 반환합니다.
	method onClick(f) is
    	//웹 브라우저 클릭 이벤트를 바인딩합니다.
        
class Application is
	field dialog: Dialog
    
    //어플리케이션은 현재 구성 또는 환경 설정에 따라 크리에이터의 타입을 고릅니다.
    method initialize() is
    	config = readApplicationConfigFile()
        
        if (config.OS == "Windows") then
        	dialog = new WindowsDialog()
		else if (config.OS == "Web") then
        	dialog = new WebDialog()
		else
        	throw new Exception("Error! Unknown operating system.")
            
	//클라이언트 코드는 기본 인터페이스를 통해 구체적인 크리에이터의 인스턴스로 작동합니다. 클라이언트가 기본 인터페이스를 통해 크리에이터와 함께 작업하는 한, 모든 크리에이터의 서브클래스에 전달할 수 있습니다.
    method main() is
    	this.initialize()
        dialog.render()

💡 적용

  • 코드가 작동해야 할 객체의 정확한 타입과 종속성을 미리 알 수 없을 경우 팩토리 메소드를 사용하십시오.
    • 팩토리 메소드는 제품의 구성 코드를 제품을 실제 사용하는 코드와 분리합니다. 따라서 제품 구성 코드를 나머지 코드와 독립적으로 확장하기 더 쉽습니다.
    • 예를 들어, 앱에 새 제품 유형을 추가하려면, 새 크리에이터 서브클래스를 생성하고 팩토리 메소드를 재정의하면 됩니다.
  • 라이브러리나 프레임워크의 사용자에게 내부 구성요소를 확장할 수 있는 방법을 제공하려는 경우 팩토리 메소드를 사용하세요.
    • 상속은 라이브러리나 프레임워크의 기본 동작을 확장하는 가장 쉬운 방법일 것입니다. 그러나 프레임워크는 표준 컴포넌트 대신에 서브클래스를 사용해야 한다는 것을 어떻게 인식할까요?
    • 해결책은 프레임워크에서 컴포넌트를 구성하는 코드를 단일 팩토리 메소드로 줄이고 컴포넌트 자체를 확장하는것 외에 누구나 이 메소드를 재정의할 수 있게 하는 것입니다.
    • 그것이 어떻게 작동하는지 봅시다. 오픈 소스 UI 프레임워크를 사용하여 앱을 작성한다고 상상해 보세요. 당신의 앱은 둥근 버튼이 있어야 하지만, 프레임워크는 네모난 것만 제공합니다. 당신은 표준 Button 클래스를 영광스러운 RoundButton 서브 클래스로 확장합니다. 그러나 이제 주 UIFramework 클래스에 기본 버튼 대신 새 버튼 서브클래스를 사용하도록 지시해야합니다.
    • 이를 위해, 기본 프레임워크 클래스에 서브클래스 UIWithRoundButtons를 생성하고 createButton 메소드를 재정의합니다. 이 메소드가 기본 클래스에서 Button 객체를 반환하는 동안, 서브클래스가 RoundButton 객체를 반환합니다. 이제 UIFramework 대신 UIWithRoundButtons 클래스를 사용하세요. 그게 다입니다!
  • 매번 기존 객체를 재 구축하는 대신 재사용하여 시스템 자원을 절약하려면 팩토리 메소드를 사용하십시오.
    • 데이터베이스 연결, 파일 시스템, 및 네트워크 자원과 같은 리소스를 많이 사용하는 대규모 객체를 다룰 때 종종 필요를 느낍니다.
    • 기존 객체를 재사용하기 위해 뭘 해야하는지 생각해봅시다.
      1. 우선, 생성된 모든 객체를 추적하려면 저장소를 만들어야 합니다.
      2. 객체를 요청하면, 프로그램은 해당 풀 내에서 사용가능한 객체를 찾아야합니다.
      3. ... 그런 다음 클라이언트 코드에 반환해야 합니다.
      4. 사용가능한 객체가 없는 경우, 프로그램은 새로운 객체를 생성해야 합니다.(그리고 풀에 추가해야합니다.)
    • 코드가 많습니다! 그리고 모두 한 곳에 담겨야 중복 코드로 프로그램을 오염시키지 않습니다.
    • 이 코드를 배치할 수 있는 가장 분명하고 편리한 위치는 재사용하려는 객체를 가진 클래스의 생성자일 겁니다. 그러나, 정의에 의하면 생성자는 항상 새 객체를 반환해야 합니다. 기존 인스턴스를 반환할 수 없습니다.
    • 따라서, 새 객체를 생성 할 뿐만 아니라 아니라 기존 것을 재사용할 수 있는 일반적인 메소드가 필요합니다. 그것은 팩토리 메소드와 매우 흡사합니다.

📋 구현 방법

1. 모든 제품이 동일한 인터페이스를 따르게 만드세요. 이 인터페이스는 모든 제품에 적합한 메소드를 선언해야합니다.

2. 크리에이터 클래스 내부에 빈 팩토리 메소드를 추가하세요. 메소드의 반환 타입은 공통 제품 인터페이스와 일치해야 합니다.

3. 크리에이터의 코드에서 제품 생성에 대한 모든 참조를 찾으세요. 제품 생성 코드를 팩토리 메소드로 추출하면서 하나씩, 팩토리 메소드 호출로 대체하세요.

반환 프로덕트의 타입을 제어하기 위해 팩토리 메소드에 임시 파라미터를 추가해야 할 수도 있습니다.

이 시점에서, 팩토리 메소드의 코드는 흉해 보일 수 있습니다. 인스턴스화 할 제품 클래스를 선택하는 큰 switch 연산자가 있을 수 있습니다. 그러나 걱정하지 마세요. 곧 고칠겁니다.

4. 이제 팩토리 메소드에 나열된 각 제품 타입에 대한 크리에이터 서브클래스 세트를 만드세요. 서브클래스에 팩토리 메소드를 재정의하고 기본 메소드에서 적절한 생성 코드 부분을 추출하세요.

5. 너무 많은 제품 유형이 있어 모든 서브클래스를 생성하는 게 적절하지 않으면, 서브클래스 안의 기본 클래스의 제어 파라미터를 재사용할 수 있습니다.

예를 들어, 다음과 같은 클래스 계층이 있다고 가정해보세요. 몇개의 서브클래스가 있는 기본 Mail 클래스: AirMail 과 GroundMail; Transport 클래스는 Plane, Truck 및 Train 입니다. AirMail 클래스가 Plane 객체만 사용하지만, GroundMail 은 Truck 과 Train 객체 모두 사용할 수 있습니다. 두 경우 모두를 다루기 위해 새 서브클래스(TrainMail)를 생성할 수 지만, 다른 선택지가 있습니다. 클라이언트 코드는 GroundMail 클래스의 팩토리 메소드에 인수를 전달하여 수신할 제품을 제어할 수 있습니다.

6. 모든 추출 후, 기본 팩토리 메소드가 비면, 추상으로 만들 수 있습니다. 남은게 있으면, 메소드의 기본 동작으로 만들 수 있습니다.

⚖ 장단점

✔ 크리에이터와 구체적인 제품 사이의 긴밀한 연결을 피합니다.

✔ 단일 책임 원칙(Single Responsibility Principle). 제품 작성 코드를 프로그램의 한 곳으로 옮겨 코드가 지원하기 편하게 할 수 있습니다.

✔ 개방/폐쇄 원칙(Open/Closed Principle). 기존 클라이언트 코드를 손상시키지 않고 새로운 유형의 제품을 프로그램에 도입할 수 있습니다. 

❌ 패턴을 구현하기 위해 많은 새로운 서브클래스를 도입해야하므로 코드는 더 복잡해질 수 있습니다. 가장 좋은 시나리오는 기존 제작자 클래스 계층에 패턴을 도입할 때입니다.

🐱‍🏍 다른 패턴과의 관계

  • 많은 설계는 Factory Method를 사용하여 시작하고(서브클래스를 통해 덜 복잡하고 사용자 정의 가능) Abstract Factory, Prototype 또는 Builder로 발전합니다(보다 유연하지만, 더 복잡함).
  • 추상 팩토리 클래스는 팩토리 메소드 세트를 기반으로 하지만, 이 클래스의 메소드를 작성하기 위해 프로토타입을 사용할 수도 있습니다.
  • 팩토리 메소드이터레이터와 함께 사용하면 컬렉션 서브클래스가 컬렉션과 호환되는 다른 유형의 이터레이터를 반환할 수 있습니다.
  • 프로토타입은 상속에 기반하지 않으므로 단점이 없습니다. 반면에, 프로토타입은 복제된 객체의 복잡한 초기화가 필요합니다. 팩토리 메소드는 상속에 기반하지만 초기화 단계가 필요하지 않습니다.
  • 팩토리 메소드템플릿 메소드의 전문화입니다. 동시에 팩토리 메소드는 큰 템플릿 메소드의 단계 역할을 할 수 있습니다.

</> 코드 예제

플랫폼 간 GUI 요소 제작

이 예제에서, 버튼은 제품의 역할을 하고, 대화 상자는 크리에이터 역할을 합니다.

다른 유형의 대화 상자는 고유 타입이 요소가 필요합니다. 그렇기 때문에 각 대화 상자 유형에 대한 서브클래스를 만들고 팩토리 메소드를 재정의합니다.

📁 buttons

📄 buttons/Button.java: Common product interface

package com.test.factorymethod.buttons;
/**
 * 모든 버튼에 대한 공통 인터페이스
 */
public interface Button {
	void render();
	void onClick();
}

📄 buttons/HtmlButton.java: Concrete product

package com.test.factorymethod.buttons;

/**
 * HTML 버튼 구현.
 */
public class HtmlButton implements Button {

	@Override
	public void render() {
		System.out.println("<button>Test Button</button>");
		onClick();
	}

	@Override
	public void onClick() {
		System.out.println("Click! Button says - 'Hello World!'");
	}
	
}

📄 buttons/WindowsButton.java: Concrete product

package com.test.factorymethod.buttons;

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

/**
 * 윈도우 버튼 구현.
 */
public class WindowsButton implements Button {
	JPanel panel = new JPanel();
	JFrame frame = new JFrame();
	JButton button;

	@Override
	public void render() {
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JLabel label = new JLabel("Hello World!");
		label.setOpaque(true);
		label.setBackground(new Color(235, 233, 126));
		label.setFont(new Font("Dialog", Font.BOLD, 44));
		label.setHorizontalAlignment(SwingConstants.CENTER);
		panel.setLayout(new FlowLayout(FlowLayout.CENTER));
		frame.getContentPane().add(panel);
		panel.add(label);
		onClick();
		panel.add(button);
		
		frame.setSize(320, 200);;
		frame.setVisible(true);
		onClick();
	}

	@Override
	public void onClick() {
		button = new JButton("Exit");
		button.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				frame.setVisible(false);
				System.exit(0);
				
			}
		});
	}
}

📁 factory

📄 factory/Dialog.java: Base creator

package com.test.factorymethod.factory;

import com.test.factorymethod.buttons.Button;

/**
 * 기본 팩토리 클래스. "팩토리"는 클래스의 역할이 아닙니다. 다른 프로덕트를 만들어야하는 핵심 비즈니스 로직이 있어야 합니다.
 */
public abstract class Dialog {
	
	public void renderWindow() {
		// ... 다른 코드 ...
		Button okButton = createButton();
		okButton.render();
	}
	
	/**
	 * 서브클래스는 특정 버튼 객체를 생성하기 위해 이 메소드를 재정의할 것입니다.
	 * @return
	 */
	public abstract Button createButton();
}

📄 factory/HtmlDialog.java: Concrete creator

package com.test.factorymethod.factory;

import com.test.factorymethod.buttons.Button;
import com.test.factorymethod.buttons.HtmlButton;

/**
 * HTML 대화 상자는 HTML 버튼을 생성합니다.
 */
public class HtmlDialog extends Dialog {

	@Override
	public Button createButton() {
		return new HtmlButton();
	}
	
}

📄 factory/WindowsDialog.java: One more concrete creator

package com.test.factorymethod.factory;

import com.test.factorymethod.buttons.Button;
import com.test.factorymethod.buttons.WindowsButton;

public class WindowsDialog extends Dialog {

	@Override
	public Button createButton() {
		return new WindowsButton();
	}
	
}

📄 Demo.java: Client code

package com.test.factorymethod;

import com.test.factorymethod.factory.Dialog;
import com.test.factorymethod.factory.HtmlDialog;
import com.test.factorymethod.factory.WindowsDialog;

public class Demo {
	private static Dialog dialog;
	
	public static void main(String[] args) {
		configure();
		runBusinessLogin();
	}
	
	/**
	 * 구체적 팩토리는 일반적으로 구성이나 환경 옵션에 따라 선택됩니다.
	 */
	private static void configure() {
		if (System.getProperty("os.name").equals("Windows 10")) {
			dialog = new WindowsDialog();
		} else {
			dialog = new HtmlDialog();
		}
		
	}
	
	/**
	 * 모든 클라이언트 코드는 추상 인터페이스를 통해 팩토리와 제품과 함께 작동해야 합니다. 이 방법은 어떤 팩토리와 작업하는지 그리고 어떤 종류의 프로덕트를 반환하는지 신경쓰지하지 않습니다.
	 */
	private static void runBusinessLogin() {
		dialog.renderWindow();
		
	}
	
}

 

블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://refactoring.guru/design-patterns/singleton

 

Singleton

Real-World Analogy The government is an excellent example of the Singleton pattern. A country can have only one official government. Regardless of the personal identities of the individuals who form governments, the title, “The Government of X”, is a globa

refactoring.guru

🙌 취지

싱글톤은 클래스가 하나의 인스턴스만 갖도록 보장하며 이 인스턴스에 대한 글로벌 액세스 포인트를 제공할 수 있는 생성 디자인 패턴입니다.

🤔 문제점

싱글톤 패턴은 단일 책임 원칙을 위반하여 동시에 두가지 문제를 해결합니다.

1. 클래스가 한 인스턴스만 가지는지 확인하십시오. 왜 클래스의 인스턴스 수를 제어하고 싶어할까요? 가장 대표적인 이유는 데이터베이스나 파일과 같은 공유자원에 대한 접근 제어를 위해서입니다.

작동 방식은 다음과 같습니다. 객체를 생성하고, 잠시 후 새로이 생성하려한다고 해봅시다. 새로운 객체를 받는 대신 이미 생성된 객체를 받을 것입니다.

이 동작은 생성자 호출이 디자인에 의해 항상 새로운 객체를 반환해야하기 때문에 일반 생성자로 구현하는게 불가능합니다.

쿨라이언트는 항상 같은 객체로 작업하고 있음을 인식하지 못할 수도 있습니다.

2. 해당 인스턴스에 전역적 접근 포인트를 제공하십시오. 필수 객체를 저장하는데 사용한 전역 변수를 기억합니까? 다루기 매우 편리하지만, 코드가 그 변수의 내용을 덮어 쓰고 앱을 충돌시킬 수 있어서 안전하지 않습니다.

전역 변수와 마찬가지로, 싱글톤 패턴은 프로그램 어디에서든 일부 객체에 접근할 수 있게 합니다. 그러면서 다른 코드로 해당 인스턴스를 덮어 쓰지 않도록 보호합니다.

이 문제의 또 다른 측면이 있습니다. 당신은 #1 문제를 해결하는 코드가 프로그램 전체에 흩어져 있는 것을 원하지 않습니다. 특히 나머지 코드가 이미 의존적일 때 그 코드를 한 클래스 안에 갖는게 더 낫습니다.

요즘, 싱글톤 패턴이 매우 유명해져 나열된 문제 중 하나만 해결하더라도 싱글톤 패턴이라 부를지 모릅니다.

😊 해결책

싱글톤의 모든 구현에는 다음 두 단계가 공통적으로 있습니다.

  • 다른 객체가 싱글톤 클래스에 대해 new 연산자를 사용하지 못하도록 기본 생성자를 private으로 만듭니다.
  • 생성자 역할을 하는 정적 생성 메소드를 만듭니다. 후드에서 이 메소드는 private 생성자를 호출하여 객체를 생성하고 정적 필드에 저장합니다. 이 메소드에 대한 다음의 모든 호출은 캐시 된 객체를 반환합니다.

코드가 싱글톤 클래스에 대해 접근할 수 있으면, 싱글톤의 정적 메소드를 호출 할 수 있습니다. 따라서 해당 메소드가 호출될 때마다 동일한 객체가 항상 반환됩니다.

🚗 실세계에 비유

정부는 싱글톤 패턴의 훌륭한 예입니다. 국가는 하나의 공식 정부만을 가질 수 있습니다. 정부를 구성하는 개개인의 개인 신원에 관계없이, 타이틀 "X 정부"는 담당자 그룹을 식별하는 전역적 접근 지점입니다.

🔦구조

1. 싱글톤 클래스는 자체 클래스와 동일한 인스턴스를 반환하는 getInstance 정적 메소드를 선언합니다.

싱글톤의 생성자는 클라이언트 코드에서 감춰져야 합니다. getInstance 메소드를 호출하는 것만이 싱글톤 객체를 얻는 유일한 방법이어야 합니다.

 

 

 

 

# 짜가코드

이 예제에서, 데이터베이스 연결 클래스는 싱글톤 역할을 합니다. 이 클래스는 public 생성자가 없으므로 객체를 얻는 유일한 방법은 getInstance 메소드를 호출하는 것뿐입니다. 이 메소드는 처음 생성된 객체를 캐시하고 모든 후속 호출에서 이를 반환합니다.

//데이터베이스 클래스는 클라이언트가 프로그램 전체에서 동일한 데이터베이스 연결 인스턴스에 접근하게 하는 'getInstance' 메소드를 정의합니다.
class Database is 
	//싱글톤 인스턴스를 저장하기 위한 필드는 정적으로 선언되어야 합니다.
    private static field instance: Database
    
    //싱글톤의 생성자는 'new' 연산자로 직접 생성 호출을 방지하기 위해 항상 프라이빗이어야 합니다.
    private constructor Database() is
    	//데이터베이스 서버로의 실제 연결과 같은 어떤 시작 코드...
        
	//싱그론 인스턴스에 접근을 제어하는 정적 메소드
    public static method getInstance() is
    	if (Database.instance == null) then
        	acquiredThreadLock() and then
            	//이 스레드가 잠금 해제를 기다리는 동안 다른 스레드가 인스턴스를 초기화되지 않았음을 확인하십시오.
                if (Database.instance == null) then
                	Database.instance = new Database()
		return Database.instance
        
	//마지막으로, 싱글톤은 인스턴스에서 실행될 수 있는 비즈니스 로직을 정의해야 한다.
    public method query(sql) is
    	//예를 들어, 앱의 모든 데이터베이스 쿼리는 이 메소드를 거칩니다. 그러므로, 여기에 제한 또는 캐싱 로직을 배치할 수 있습니다.
        //...
        
class Application is
	method main() is
    	Database foo = Database.getInstance()
        foo.query("SELECT ...")
        //...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        //변수 'bar'는 변수 'foo'와 동일한 객체를 포함합니다.
    	

💡 적용

  • 프로그램의 클래스가 모든 클라이언트가 사용할 수 있는 단일 인스턴스가 있어야 하는 경우 싱글톤 패턴을 사용하십시오. 예를 들면, 프로그램의 다른 부분에서 공유되는 단일 데이터베이스 객체가 있습니다.
    • 싱글톤 패턴은 특수 생성 메소드를 제외하고 클래스 객체를 생성하는 모든 방법을 비활성화합니다. 이 메소드는 새로운 객체를 생성하거나, 이미 생성된 경우 기존 객체를 반환합니다.
  • 전역 변수에 대한 엄격한 제어가 필요할 때 싱글톤 패턴을 사용하십시오.
    • 전역 변수와 달리, 싱글톤 패턴은 클래스의 단 하나의 인스턴스가 있음을 보장합니다. 싱글톤 클래스 외의 어떤 것도 캐쉬된 인스턴스를 대체할 수 없습니다.
  • 언제든지 이 제한을 조정할 수 있고 여러 개의 싱글톤 인스턴스를 만들 수 있습니다. 변경이 필요한 유일한 코드는 getInstance 메소드의 본문입니다.

📋 구현 방법

  1. 싱글톤 인스턴스를 저장하기 위해 클래스에 프라이빗 정적 필드를 추가하십시오.
  2. 싱글톤 인스턴스를 얻기 위한 퍼블릭 정적 생성 메소드를 선언합니다.
  3. 정적 메소드 내부에 "지연 초기화"를 구현하십시오. 첫번째 호출에 새 객체를 만들어 정적 필드에 담아야 합니다. 메소드는 항상 모든 후속 호출에서 해당 인스턴스를 반환해야 합니다.
  4. 클래스의 생성자를 비공개로 만듭니다. 클래스의 정적 메소드는 여전히 생성자 호출이 가능하지만 다른 객체는 호출 할 수 없습니다.
  5. 클라이언트 코드를 살펴보고 싱글톤 생성자에 대한 모든 직접 호출을 정적 생성 메소드 호출로 대체하십시오.

⚖ 장단점

✔ 클래스가 단일 인스턴스만 가짐을 보장할 수 있습니다.

✔ 해당 인스턴스에 대한 전역적 접근 포인트를 얻습니다.

✔ 싱글톤 객체는 처음 요청된 경우에만 초기화됩니다.

단일 책임 원칙을 위반합니다. 이 패턴은 두가지 문제를 같이 해결합니다.

❌ 싱글톤 패턴은 프로그램의 구성요소가 서로에 대해 너무 많은 정보를 알 때 잘못된 디자인을 숨길 수 있습니다.

❌ 이 패턴은 다중 스레드가 싱글톤 객체를 여러번 만들지 않게 하기 위해 다중 스레드 환경에서 특별한 처리가 필요합니다.

❌ 많은 테스트 프레임워크가 목 객체를 생성할 때 상속에 의존하기 때문에 싱글톤의 클라이언트 코드를 단위 테스트하기 어려울 수 있습니다. 싱글톤 클래스의 생성자가 프라이빗하고 정적 메소드를 재정의하는 것이 대부분의 언어에서 불가능하므로, 싱글톤을 목(mock)으로 하는 창의적인 방법을 고려해야합니다. 아니면 그냥 테스트를 쓰지 마십시오. 또는 싱글톤 패턴을 사용하지 마십시오.

🐱‍🏍 다른 패턴과의 관계

  • Facade 클래스는 대부분의 경우 싱글톤 facade 객체로 충분하기 때문에 종종 싱글톤으로 변환될 수 있습니다.
  • 어떤 방식으로든 객체의 모든 공유 상태를 하나의 플라이급 객체로 줄이면 Flyweight는 싱글톤과 비슷합니다. 그러나 두 패턴에는 두가지 근본적인 차이점이 있습니다.
    1. Flyweight 클래스는 고유한 상태가 다른 여러 인스턴스를 가질 수 있는 반면, 싱글톤 인스턴스는 하나만 있어야 합니다.
    2. 싱글톤 객체는 변경 가능합니다. 플라이급 객체는 변경할 수 없습니다.
  • 추상 팩토리, 빌더, 프로토타입은 모두 싱글톤으로 구현할 수 있습니다.

</> 코드 예제

나이브 싱글톤 (단일 스레드)

조잡한 싱글톤을 구현하는 것은 꽤 쉽습니다. 생성자를 숨기고 정적 생성 메소드를 구현하면 됩니다.

더보기

📄 Singleton.java: Singleton

package com.test.singleton.example.non_thread_safe;

public final class Singleton{
	private static Singleton instance;
	public String value;
	
	private Singleton(String value){
		//다음 코드는 느린 초기화를 모방합니다.
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.value = value;
	}
	
	public static Singleton getInstance(String value){
		if (instance == null) {
			instance = new Singleton(value);
		}
		
		return instance;
	}
}

📄 DemoSingleThread.java: Client code

package com.test.singleton.example.non_thread_safe;

public class DemoSingleThread {
	public static void main(String[] args) {
		System.out.println("같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)" + "\n" +
				"다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)" + "\n\n" +
				"결과:" + "\n");
		Singleton singleton = Singleton.getInstance("FOO");
		Singleton anotherSingleton = Singleton.getInstance("BAR");
		System.out.println(singleton.value);
		System.out.println(anotherSingleton.value);
	}
}

📄 OutputDemeSingleThread.txt: Execution results

같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)
다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)

결과:

FOO
FOO

나이브 싱글톤 (멀티 스레드)

멀티스레드 환경에서 동일한 클래스가 잘못 작동합니다. 멀티 스레드는 생성 메소드를 동시에 호출하고 싱글톤 클래스의 여러 인스턴스를 가져올 수 있습니다.

더보기

📄 Singleton.java: Singleton

package com.test.singleton.example.non_thread_safe;

public final class Singleton {
	private static Singleton instance;
	public String value;
	
	private Singleton(String value) {
		//다음 코드는 느린 초기화를 모방합니다.
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.value = value;
	}
	
	public static Singleton getInstance(String value) {
		if (instance == null) {
			instance = new Singleton(value);
		}
		
		return instance;
	}
}

📄 DemoMultiThread.java: Client code

package com.test.singleton.example.non_thread_safe;

public class DemoMultiThread {
	public static void main(String[] args) {
		System.out.println("같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)" + "\n" +
				"다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)" + "\n\n" +
				"결과:" + "\n");
		Thread threadFoo = new Thread(new ThreadFoo());
		Thread threadBar = new Thread(new ThreadBar());
		threadFoo.start();
		threadBar.start();
	}
	
	static class ThreadFoo implements Runnable {

		@Override
		public void run() {
			Singleton singleton = Singleton.getInstance("FOO");
			System.out.println(singleton.value);
		}
	}
	
	static class ThreadBar implements Runnable {

		@Override
		public void run() {
			Singleton singleton = Singleton.getInstance("BAR");
			System.out.println(singleton.value);
		}
	}
}

📄 OutputDemoMultiThread.txt: Execution results

같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)
다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)

결과:

FOO
BAR

게으른 로딩에 대한 스레드 안전 싱글톤

이 문제를 해결하려면, 싱글톤 객체를 처음 만드는 동안 스레드를 동기화해야 합니다.

더보기

📄 Singleton.java: Singleton

package com.test.singleton.example.non_thread_safe;

public final class Singleton {
	//필드는 이중 점검 잠금이 올바르게 작동하도록 휘발성으로 선언되어야 합니다.
	private static volatile Singleton instance;
	public String value;
	
	private Singleton(String value) {
		this.value = value;
	}
	
	public static Singleton getInstance(String value) {
		//여기서 취한 접근 방식을 이중 점검 잠금(double-checked locking, DCL)이라고 합니다.싱글톤 인스턴스를 동시에 얻으려 시도하는 멀티 스레드 간 경쟁 상태를 방지하여 결과적으로 별도의 인스턴스를 만듭니다.
		//
		//여기에 'result' 변수를 갖는게 완전히 무의미해 보일 수 있습니다. 그러나 자바에서 이중 점검 잠금을 구현할 때 이 로컬 변수를 도입해 해결해야하는 매우 중요한 경고가 있습니다.
		//
		//DCL 이슈에 대해 여기서 더 읽을 수 있습니다.
		//https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
		Singleton result = instance;
		if (result != null) {
			return result;
		}
		
		synchronized(Singleton.class) {
			if (instance == null) {
				instance = new Singleton(value);
			}
			return instance;
		}
	}
}

📄 DemoMultiThread.java: Client code

package com.test.singleton.example.non_thread_safe;

public class DemoMultiThread {
	public static void main(String[] args) {
		System.out.println("같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)" + "\n" +
				"다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)" + "\n\n" +
				"결과:" + "\n");
		Thread threadFoo = new Thread(new ThreadFoo());
		Thread threadBar = new Thread(new ThreadBar());
		threadFoo.start();
		threadBar.start();
	}
	
	static class ThreadFoo implements Runnable {

		@Override
		public void run() {
			Singleton singleton = Singleton.getInstance("FOO");
			System.out.println(singleton.value);
		}
	}
	
	static class ThreadBar implements Runnable {

		@Override
		public void run() {
			Singleton singleton = Singleton.getInstance("BAR");
			System.out.println(singleton.value);
		}
	}
}

📄 OutputDemoMultiThread.txt: Execution results

같은 값이 보이면, 싱글톤이 재사용된 것입니다 (예!!)
다른 값이 표시되면, 2개의 싱글톤이 생성된 것입니다 (우우;;)

결과:

BAR
BAR
블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://refactoring.guru/design-patterns/catalog

 

The Catalog of Design Patterns

 

refactoring.guru

생성 패턴(Creational patterns)은 기존 코드의 유연성과 재사용성을 증가시키고 객체 생성 메커니즘을 제공한다.
- Factory Method, Builder, Singleton, Prototype, Abstract Factory


구조적 패턴(Structural patterns)은 구조를 유연하고 효과적으로 유지하면서 객체와 클래스를 더 큰 구조로 조립하는 방법을 설명한다.
- Composite, Proxy, Adapter, Bridge, Decorator, Facade, Flyweight


행동 패턴(Behavioral patterns)은 효과적인 의사소통과 객체 간 책임 할당을 처리한다.
- Observer, Strategy, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, State, Visitor

 

블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://refactoring.guru/design-patterns/template-method

 

Template Method

Problem Imagine that you’re creating a data mining application that analyzes corporate documents. Users feed the app documents in various formats (PDF, DOC, CSV), and it tries to extract meaningful data from these docs in a uniform format. The first versio

refactoring.guru

현재 공부 중인 스프링 시큐리티에서 위임에 대한 내용이 나와 공부하는 도중에 상속과 위임의 관계에 대해서 Template Method 패턴에 등장한다고 하여 살펴보려합니다.

🙌 취지

Template Method는 상위클래스에 대한 알고리즘의 골격을 정의하는 행동 디자인 패턴이지만 서브클래스가 특정 알고리즘 스텝에 따라 그 구조를 바꾸지 않고 오버라이드 할 수 있게 합니다.

🤔 문제점

당신이 기업 문서를 분석하는 데이터 마이닝 어플리케이션을 만든다고 상상해보세요. 사용자는 다양한 포맷(PDF, DOC, CSV)으로 문서를 생산하고, 앱은 의미있는 데이터를 일정한 포맷으로 뽑아내려 합니다.

첫번째 버전은 DOC 파일일 경우에만 작동합니다. 다음 버전에는 CSV 파일도 지원합니다. 한달 뒤 당신은 PDF 파일로 데이터를 추출하는 방법을 "가르쳤습니다".

많은 중복된 코드를 포함하는 데이터 마이닝 클래스들

어느 시점에서, 당신은 이 세가지 클래스들이 많은 비슷한 코드를 갖고 있다는 것을 알아챘습니다. 다양한 포맷의 데이터를 다루는 코드는 각 클래스에서 완전히 다른 반면에, 데이터를 처리하고 분석하는 코드는 거의 동일합니다. 그렇다면 알고리즘 구조는 그대로 놔두면서 중복 코드를 제거하는게 더 낫지 않겠습니까?

이 클래스에 사용된 클라이언트 코드에 또 다른 문제점이 있습니다. 처리 객체의 클래스에 따라 적절한 조치를 취한 많은 조건이 있습니다. 만약 이 세 처리 클래스들이 공통 인터페이스나 기본 클래스를 갖는다면 당신은 클라이언트 코드에서 조건을 제거할 수 있고 처리 오브젝트에서 메소드를 호출할 때 다형성을 사용할 수 있습니다.

😊 해결책

Template Method 패턴은 알고리즘을 일련의 단계로 구분짓고 이 단계를 메소드로 바꾸고, 하나의 "템플릿 메소드" 안에서 이러한 메소드에 대한 일련의 호출을 넣는 것을 제안합니다. 이 단계는 추상적이거나, 일부 기본 구현이 있을 수도 있습니다. 알고리즘을 사용하기 위해 클라이언트는 자체 서브클래스를 제공해야 하고, 모든 추상 단계를 구현하고, 필요시(템플릿 메소드 자체에서는 아니지만) 어떤 선택적 단계를 오버라이드해야 한다.

이것이 우리의 데이터 마이닝 앱에서 어떻게 작동할지 봅시다. 우리는 모든 세개의 파싱 알고리즘에 대한 기본 클래스를 생성할 수 있습니다. 이 클래스는 다양한 문서 처리 단계에 대한 일련의 호출로 구성된 템플릿 메소드를 정의합니다.

Template method는 알고리즘을 단계별로 구분하여 서브클래스가 이 단계를 실제 메소드는 아니지만 오버라이드하게 합니다.

첫째, 모든 단계를 abstract로 선언합니다. 이는 서브클래스가 자체 구현을 이 메소드로 구현하게끔 합니다. 우리의 경우, 서브클래스가 이미 모든 필요한 구현을 하고 있기 때문에 우리는 상위클래스의 메소드에 맞게 메소드의 signature만 조정하면 됩니다.

이제, 중복코드를 없애기 위해 무엇을 해야하는지 봅시다. 파일 열기/닫기나 데이터 추출하기/파싱하기는 각각의 데이터 포맷마다 달라보입니다. 따라서 이 메소드들에 대해서는 건드릴 곳이 없습니다. 그러나 원시 데이터를 분석하거나 보고서 작성과 같은 다른 단계의 구현은 매우 비슷하므로 서브 클래스가 코드를 공유할 수 있게 기본 클래스로 가져올 수 있습니다.

보시다시피, 우리는 두가지 단계를 거칩니다 :

  • 추상 단계는 모든 서브클래스에서 구현되어야 합니다.
  • 선택적 단계는 이미 일부 기본 구현을 갖지만, 필요하다면 오버라이드 될 수 있습니다.

또 다른 단계가 있는데 hooks라 불립니다. hook는 바디가 비어있는 선택적 단계입니다. 템플릿 메소드는 hook이 오버라이드 되지 않아도 작동할 수 있습니다. 일반적으로 hook은 중요한 알고리즘 스텝 전이나 후에 위치되어, 추가적인 알고리즘 확장 포인트를 서브클래스에 제공합니다.

🚗 실세계에 비유

일반적인 건축 계획이 고객의 요구에 더 잘 맞도록 약간 변형될 수 있습니다.

템플릿 메소드적 접근은 대량 주택 건설에 사용될 수 있습니다. 표준 주택을 짓기위한 건축 계획은 몇가지 확장점들을 갖고 있고 소유자들이 결과 주택의 일부 세부사항을 조정할 수 있게 합니다.

토대를 다지고, 구조를 짜고, 벽을 세우고, 배선을 설치하고, 수도 및 전기를 연결하는 등의 각각의 건축 단계는 결과 주택을 다른 건물과 약간 다르게 만들며 약간의 변화가 있을 수 있습니다.

🔦구조

1. 추상 클래스(Abstract Class)는 특정 단계에서 이러한 메소드를 호출하는 실제 템플릿 메소드 뿐만이 아니라 알고리즘의 단계로 작동하는 메소드를 선업합니다. 이 단계는 추상적이거나 일부 기본 구현을 갖도록 선언 될 수 있습니다.

2. 구체적 클래스(Concrete Classes)는 모든 단계를 오버라이드 할 수 있지만 그것은 템플릿 메소드는 아닙니다.

 

 

 

 

 

# 짜가코드

이 예제에서, Template Method 패턴은 간단한 전략 비디오 게임에서 다양한 인공지능 분기에 대한 "골격"을 제공합니다. 

간단한 비디오 게임의 AI 클래스들

게임의 모든 종족은 거의 같은 유닛과과 건물을 가지고 있습니다. 그러므로 당신은 동일한 AI 구조를 여러 종족에서 재사용하며, 약간의 디테일만 다르게 오버라이드 할 수 있습니다. 이 방법을 사용하여, 당신은 오크 AI를 좀 더 공격적으로 재정의 할 수 있고, 휴먼은 좀 더 방어적으로, 몬스터는 건축을 할 수 없게 만들 수 있습니다. 게임에 새 종족을 추가하려면 새로운 AI 서브클래스를 만들고 기본 AI 클래스에 선언된 기본 메소드를 재정의해야 합니다.

/*
추상 클래스는 일반적으로 기본 작업을 추상화하기 위해 호출로 구성된 일부 알고리즘 골격을 포함하는 템플릿 메소드를 정의합니다. 구체적 서브 클래스는 이 작업을 구현하지만, 템플릿 메소드 자체는 그대로 둡니다.
*/
class GameAI is
	//템플릿 메소드는 알고리즘의 뼈대를 정의합니다.
	method turn() is
    	collectResources()
        buildStructures()
        buildUnits()
        attack()
    
    //일부 단계는 기본 클래스에서 바로 구현될 수도 있습니다.
    method collectResources() is
    	foreach (s in this.buildStructures) do
        	s.collect()
            
   	//그리고 몇몇은 추상적으로 정의되어 집니다.
    abstract method buildStructures()
    abstract method buildUnits()
    
    //클래스는 여러 템플릿 메소드를 가질 수 있습니다.
    method attack() is
    	enemy = closestEnemy()
        if (enemy == null)
        	sendScouts(map.center)
        else
        	sendWarriors(enemy.position)
            
    abstract method sendScouts(position)
    abstract method sendWarriors(position)
    
/*
구체적 클래스는 기본 클래스의 모든 추상 작업을 구현해야 하지만 템플릿 메소드 자체를 재정의해서는 안됩니다.
*/
class OrcsAI extends GameAI is
	method buildStructures() is
    	if (there are some resources) then
        	//농장을 짓고, 막사를 짓고, 요새를 건설합니다.
            
    method buildUnits() is
    	if (there are plenty of resources) then
        	if (there are no scouts)
            	//보병을 만들고 정찰 그룹에 추가합니다.
            else 
            	//대원을 만들고 전사 그룹에 추가합니다.
                
    // ...
    
    method sendScouts(position) is
    	if (scouts.length > 0) then
        	// 그 위치로 정찰을 보냅니다.
            
    method sendWarriors(position) is
    	if (warriors.length > 5) then
        	// 그 위치로 전사를 보냅니다.
            
// 서브클래스는 기본 구현으로 일부 조작을 재정의할 수 있습니다.
class MonstersAI extends GameAI is
	method collectResources() is
    	// 몬스터는 자원을 모으지 않습니다.
        
	method buildStructures() is
    	// 몬스터는 구조물을 건축하지 않습니다.
        
	method buildUnits() is
    	// 몬스터는 유닛을 생산하지 않습니다.
            
 

💡 적용

  • 클라이언트가 알고리즘의 어떤 특정 단계만을 확장하고 전체 알고리즘이나 구조는 확장하지 못하게 하고 싶을 때 템플릿 메소드를 사용합니다.
  • 템플릿 메소드는 획일적 알고리즘을 상위클래스에서 정의된 구조는 그대로 유지하면서 서브클래스에 의해 쉽게 확장되는 일련의 개별 단계로 전환 할 수 있습니다.
  • 거의 동일한 알고리즘을 포함한 여러 클래스가 있는 경우 이 패턴을 사용합니다. 그 결과로, 당신은 알고리즘을 변경할 때 모든 클래스를 수정해야할 수도 있습니다.
  • 이러한 알고리즘을 템플릿 메소드로 전환하면, 당신은 상위클래스로 유사한 구현 단계를 끌어 올려 코드 중복을 제거할 수 있습니다. 하위클래스마다 다른 코드는 하위클래스에 남아있을 수 있습니다.

📋 구현 방법

  1. 단계적으로 쪼갤 수 있는지 대상 알고리즘을 분석합니다. 어떤 단계가 모든 서브 클래스에 공통인지 어떤 단계가 고유한지를 고려합니다.
  2. 추상 기본 클래스를 만들고 알고리즘 단계를 나타내는 템플릿 메소드와 추상 메소드 집합을 선언합니다. 해당 단계를 실행하여 템플릿 메소드의 알고리즘 구조를 윤곽짓습니다. 하위클래스에서 오버라이드하는걸 방지하기 위해 템플릿 메소드를 final로 설정하십시오.
  3. 모든 단계가 추상적이어도 괜찮습니다. 그러나 일부 단계는 기본 구현으로 이점을 얻을 수 있습니다. 서브클래스가 이러한 메소드를 구현할 필요가 없습니다.
  4. 알고리즘의 중요 단계 사이에 hook을 추가하는 걸 고려하십시오.
  5. 알고리즘의 각 변형에 대해 새 구체적 서브클래스를 작성하십시오. 모든 추상 단계를 구현해야하지만, 일부 선택적 단계를 오버라이드 할 수도 있습니다.

⚖ 장단점

✔ 당신은 클라이언트가 커다란 알고리즘의 특정 부분만 오버라이드하게 하여 알고리즘의 다른 부분에서 발생하는 변경의 영향을 덜받게 할 수 있습니다.

✔ 당신은 중복 코드를 상위클래스로 당겨올 수 있습니다.

❌ 일부 클라이언트는 제공된 알고리즘 골격에 의해 제한될 수 있습니다.

❌ 당신은 하위 클래스를 통한 기본 단계 구현를 억제하여 Liskov 대체 원칙을 위반할지 모릅니다.

❌ 템플릿 메소드는 더 많은 단계를 갖게 될 수록 유지하기 어려운 경향이 있습니다.

🐱‍🏍 다른 패턴과의 관계

  • 팩토리 메소드템플릿 메소드의 전문화입니다. 동시에 팩토리 메소드는 큰 템플릿 메소드의 단계 역할을 할 수 있습니다.
  • 템플릿 메소드는 상속에 기반합니다. 하위클래스에서 해당 파트를 확장하여 알고리즘의 일부를 변경할 수 있습니다. 전략은 구성을 기반으로 합니다. 당신은 해당 동작에 해당하는 다른 전략을 제공하여 오브젝트 동작의 일부를 변경할 수 있습니다. 템플릿 메소드는 클래스 레벨에서 작동하며, 정적(static)입니다. 전략은 개체 수준에서 작동하므로 런타임시 동작을 전환할 수 있습니다.

</> 코드 예제

이 예제에서, 템플릿 메소드 패턴은 소셜 네트워크 알고리즘을 정의합니다. 특정 소셜 네트워크와 매치되는 서브클래스는 소셜 네트워크에서 제공하는 API를 따라 이 단계를 구현합니다.

📁 networks

📄 networks/Network.java: Base social network class

package com.test.example.networks;

/**
 * Base class of social network.
 *
 */
public abstract class Network {
	String userName;
	String password;
	
	Network() {}
	
	/**
	 * Publish the data to whatever network.
	 */
	public boolean post(String message) {
		// Authenticate before posting. Every network uses a different authentication method.
		if (logIn(this.userName, this.password)) {
			// Send the post data.
			boolean result = sendData(message.getBytes());
			logOut();
			return result;
		}
		return false;
	}

	abstract boolean logIn(String userName, String password);
	abstract boolean sendData(byte[] bytes);	
	protected abstract void logOut();
}

 

📄 networks/Facebook.java: Concrete social network

package com.test.example.networks;

/**
 * Class of social network
 *
 */
public class Facebook extends Network {
	public Facebook(String userName, String password) {
		this.userName = userName;
		this.password = password;
	}

	@Override
	public boolean logIn(String userName, String password) {
		System.out.println("\nChecking user's parameters");
		System.out.println("Name: " + this.userName);
		System.out.print("Password: ");
		for (int i=0; i<this.password.length(); i++) {
			System.out.print("*");
		}
		simulateNetworkLatency();
		System.out.println("\n\nLogin success on Facebook");
		return true;
	}

	@Override
	public boolean sendData(byte[] data) {
		boolean messagePosted = true;
		if (messagePosted) {
			System.out.println("Message: '" + new String(data) + "' was posted on Facebook");
			return true;
		} else {
			return false;
		}
	}

	@Override
	public void logOut() {
		System.out.println("User: '" + userName + "' was logged out from Facebook");
	}
	
	private void simulateNetworkLatency() {
		try {
			int i = 0;
			System.out.println();
			while (i < 10) {
				System.out.print(".");
				Thread.sleep(500);
				i++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

📄 networks/Twitter.java: One more social network

package com.test.example.networks;

/**
 * Class of social network
 *
 */
public class Twitter extends Network {
	
	public Twitter(String userName, String password) {
		this.userName = userName;
		this.password = password;
	}

	@Override
	public boolean logIn(String userName, String password) {
		System.out.println("\nChecking user's parameters");
		System.out.println("Name: " + this.userName);
		System.out.print("Password: ");
		for (int i=0; i<this.password.length(); i++) {
			System.out.print("*");
		}
		simulateNetworkLatency();
		System.out.println("\n\nLogin success on Twitter");
		return true;
	}

	@Override
	boolean sendData(byte[] data) {
		boolean messagePosted = true;
		if (messagePosted) {
			System.out.println("Message: '" + new String(data) + "' was posted on Twitter");
			return true;
		} else {
			return false;
		}
	}

	@Override
	protected void logOut() {
		System.out.println("User: '" + userName + "' was logged out from Twitter");
		
	}
	
	private void simulateNetworkLatency() {
		try {
			int i = 0;
			System.out.println();
			while (i < 10) {
				System.out.print(".");
				Thread.sleep(500);
				i++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

📄 Demo.java: Client code

package com.test.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import com.test.example.networks.Facebook;
import com.test.example.networks.Network;
import com.test.example.networks.Twitter;

/**
 * Demo class. Everything comes together here.
 *
 */
public class Demo {
	public static void main(String[] args) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		Network network = null;
		System.out.print("Input user name: ");
		String userName = reader.readLine();
		System.out.print("Input password: ");
		String password = reader.readLine();
		
		// Enter the message.
		System.out.print("Input message: ");
		String message = reader.readLine();
		
		System.out.println("\nChoose social network for poasting message.\n" +
					"1 - Facebook\n" +
					"2 - Twitter");
		int choice = Integer.parseInt(reader.readLine());
		
		// Create proper network object and send the message.
		if (choice == 1) {
			network = new Facebook(userName, password);
		} else {
			network = new Twitter(userName, password);
		}
		network.post(message);
	}
}

 

📄 OutputDemo.txt: Execution result

Input user name: hong
Input password: 1234
Input message: Hello, World!

Choose social network for poasting message.
1 - Facebook
2 - Twitter
2

Checking user's parameters
Name: hong
Password: ****
..........

Login success on Twitter
Message: 'Hello, World!' was posted on Twitter
User: 'hong' was logged out from Twitter
블로그 이미지

uchacha

개발자 일지

,