










소프트웨어 디자인 패턴을 설명하는 일관된 형식이 존재함.

**“만약 싱글톤 == null, 없으면 new로 새로 만든다. 있으면 어디간에 있는 인스턴스 호출**
**생성자는 private로 선언해야한다. → public으로 만들면 누군가가 만들어 버리기 때문.**
**싱글톤은 클래스 내부에서만 접근할 수 있도록 해야한다.”**
```java
public class DatabaseConnection {
private static DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() { //생성자
// Initialize the database connection
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306...");
} catch (SQLException e) {
e.printStackTrace();
}
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public Connection getConnection() {
return connection;
}
// Other database-related methods
public void executeQuery(String query) {
// ...
}
}
public class Main {
public static void main(String[] args) {
DatabaseConnection databaseConnection = DatabaseConnection.getInstance();
Connection connection = databaseConnection.getConnection();
// Use the connection to execute queries or perform database operations
try {
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM mytable");
// Process the result set
while (resultSet.next()) {
// ...
}
// Close the result set, statement, and connection
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
```
“이터레이터는 인터페이스이기 때문에 각 자료형마다 정의가 되어 있다. 즉 여러분이 개발할 때는 implement하면 된다. 위 UML을 보고 반복자 패턴인걸 알면 된다.”


“XML을 JSON으로 변환하는 어댑터를 만들어서 준다”
// 클라이언트가 기대하는 Target 인터페이스
// "Json을 받아서 Xml로 변환할 수 있어야 한다"는 약속
interface IDataAdapter {
Xml convert(Json json);
}
// 기존에 이미 존재하던 클래스 (Adaptee)
// 클라이언트가 바로 쓰기엔 인터페이스가 맞지 않음
class Json {
public Json(){}
// Json 데이터를 Xml 형태로 바꾸는 기존 기능
Xml convertToXML(){
// Logic to convert the data into Xml
}
}
// Adapter 클래스
// 클라이언트가 원하는 IDataAdapter 인터페이스를 구현하면서
// 내부적으로는 기존 Json 객체의 기능(convertToXML)을 사용함
class JsonToXmlAdapter implements IDataAdapter {
// Adaptee(Json)를 내부에 포함
// 즉, "상속"보다 "구성(composition)"으로 연결하는 형태
private Json json;
public JsonToXmlAdapter(Json json){
this.json = json;
}
// 클라이언트는 convert()만 호출하면 됨
// 실제 변환은 내부 Json 객체에게 위임
public Xml convert(Json json){
// Logic to convert Json to Xml
// 기존 Json 클래스의 기능을 호출해서 Xml로 변환
this.json.convertToXML();
}
}
// 원본 Json 데이터 생성
Json json = new Json("some json data");
// 클라이언트는 JsonToXmlAdapter를 직접 쓰지만,
// 타입은 IDataAdapter로 받음 -> 인터페이스에 의존
IDataAdapter adapter = new JsonToXmlAdapter(json);
// 클라이언트는 "convert()"만 호출하면 Xml을 얻을 수 있음
// 내부적으로는 adapter가 Json의 convertToXML()을 대신 호출해줌
Xml xml = adapter.convert();
// 변환된 Xml 데이터를 이용해 다른 시스템/API 호출 가능
Decimal tax = calculateTax(xml);
// 서로 다른 데이터 포맷 객체들
Json json = new Json("some json data");
Csv csv = new Csv("some csv data");
Xml xml = new Xml("some xml data");
Bson bson = new Bson("some bson data");
// Client code
// 1) Json -> Xml 변환
// Json 형식 데이터를 Xml 형식으로 바꾸기 위한 어댑터 사용
IDataAdapter adapter = new JsonToXmlAdapter(json);
Xml xml = adapter.convert();
// 2) Json -> Csv 변환
// 같은 방식으로 Json을 Csv로 바꾸는 다른 어댑터 사용
adapter = new JsonToCsvAdapter(json);
Csv csv = adapter.convert();
// 3) Csv -> Bson 변환
// 이번에는 Csv를 Bson으로 변환하는 어댑터 사용
adapter = new CsvToBsonAdapter(csv);
Bson bson = adapter.convert();
하나의 json 데이터를 여러 가지 형태의 데이터로 변환할 필요가 있을때, 어댑터 패턴을 사용하면 좋다. 그럴 필요가 없다면 오히려 어댑터 패턴은 독이 된다.
Application은 Notifier 타입만 알고 있음.




Notifier을 상속받는 서브 클래스들

상속시, 만약 여러 기능을 조합해서 사용하긴 원한다면 조합별 클래스를 모두 만들어야함.
“상속으로 추가”는 OCP를 잘 따르는 것 같지만, 조합 수만큼 서브클래스 필요, 캡슐화를 약화시킴
객체의 구성 관계 (합(Composition) 관계)+ 위임으로 기존 클래스 동작을 가볍고 유연하게 동적 확장
- 데코레이터 패턴의 두 가지 구성 요소 : componet 클래스 , 확장 기능이 담긴 데코레이터
데코레이터 객체가 component를 재귀적으로 래핑


BaseDecorator를 공통 부모로 해서 SMSDecorator, FacebookDecorator, SlackDecorator를 만들고, 기능을 추가하고 싶을 때는 조합별 클래스를 새로 만드는 대신 필요한 데코레이터 객체를 순서대로 감싸서 붙인다.
public interface Notifier {
public void send(String message);
}
--------------------------------------------------------------------
public class BaseDecorator implements Notifier {
private Notifier notifier;
public void send(String message) {
// 기본 메시지 로직
}
}
--------------------------------------------------------------------
public class SMSDecorator extends BaseDecorator {
public SMSDecorator(Notifier notifier) {
super(notifier);
}
public void send(String message) {
message += "SMS Format message";
super.send(message);
}
}
--------------------------------------------------------------------
BaseDecorator base = new SMSDecorator(new FBDecorator(new BaseDecorator()));
base.send(message);
// SMS -> FB -> 최종 전송







“서로 관련 있는 객체들을 세트로 묶어서 생성하게 해주는 패턴”
“빅토리안 스타일 가구 세트”, “모던 스타일 가구 세트”
처럼 같은 계열의 객체들을 한꺼번에 맞춰서 생성
같은 종류의 제품(Chair) 안에도 스타일별 구현체가 여러 개 있을 수 있다.

FurnitureFactory : 추상 팩토리 인터페이스createChair()createCoffeeTable()createSofa()VictorianFurnitureFactoryModernFurnitureFactory이제 제품 하나만 만드는 게 아니라 서로 관련된 제품 묶음을 한 번에 만드는 공장을 만든다.

실제 예시

제품군
ButtonCheckbox스타일(운영체제 계열)
WinButton, WinCheckboxMacButton, MacCheckbox클라이언트
Application추상 팩토리
GUIFactorycreateButton()createCheckbox()구체 팩토리
WinFactoryMacFactorypublic class Demo {
/**
* Application picks the factory type and creates it in run time (usually at
* initialization stage), depending on the configuration or environment
* variables.
*/
private static Application configureApplication() {
Application app;
GUIFactory factory; //내가 원하는 형태의 GUI를 GUIfactory를 통해서 결정
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("mac")) { //MAC이면 Mac에 맞는 팩토리를 만듦
factory = new MacOSFactory();
} else {
factory = new WindowsFactory();
}
app = new Application(factory);
return app;
}
public static void main(String[] args) {
Application app = configureApplication();
app.paint();
}//GUI를 세분화해서 표현
}

Context는 initialState로 시작하고, state에 따라 상태가 바뀐다.
클라이언트는 Context를 사용할건데, ConcreteState를 만든다.
State는 여러개의 상태로 이루어져 있고, 각각의 doThis와 doThat로 여러 액션을 할수가 있다.
ConcreteStates는 여러 개의 context로 이루어짐


상태 패턴을 이용한 Document 사례
내가 스테이트를 넣으면 그 스테이트에 따라 내가 멀 할 수 있는지 정해진다.
즉, 복잡하게 스위치 문이나 케이스문을 사용안할 수 있다.
//1. 상태 패턴 적용 전: 문자열 + switch로 상태 관리
public class Document {
private String state;
public Document() {
state = "draft";
}
public void publish() {
switch (state) {
case "draft":
moveToModeration();
break;
case "moderation":
approveForPublication();
break;
case "published":
break;
}
}
private void moveToModeration() {
state = "moderation";
System.out.println("Document moved to moderation status.");
}
private void approveForPublication() {
state = "published";
System.out.println("Document approved for publishable status.");
}
}
//2. 상태 패턴 적용 후
interface DocumentState {
void publish(Document document);
}
class DraftState implements DocumentState {
@Override
public void publish(Document document) {
System.out.println("Document moved to moderation status.");
document.setState(new ModerationState());
}
}
class ModerationState implements DocumentState {
@Override
public void publish(Document document) {
System.out.println("Document approved for publishable status.");
document.setState(new PublishedState());
}
}
class PublishedState implements DocumentState {
@Override
public void publish(Document document) {
System.out.println("The document has already been published.");
}
}
public class Document {
private DocumentState state;
public Document() {
this.state = new DraftState();
}
public void publish() {
state.publish(this);
}
public void setState(DocumentState state) {
this.state = state;
}
}
상태 패턴은 객체의 현재 상태에 따라 같은 요청이라도 다르게 동작하도록,
상태를 별도의 클래스로 분리해서 관리하는 패턴이다.
상태:
interface PlayerState {
void play();
void pause();
void stop();
}
class PlayingState implements PlayerState {
@Override
public void play() {
System.out.println("Already playing");
}
@Override
public void pause() {
System.out.println("Pausing music");
// Pause playback logic
}
@Override
public void stop() {
System.out.println("Stopping music");
// Stop playback logic
}
}
class PausedState implements PlayerState {
@Override
public void play() {
System.out.println("Resuming playback");
// Resume playback logic
}
@Override
public void pause() {
System.out.println("Already paused");
}
@Override
public void stop() {
System.out.println("Stopping music");
// Stop playback logic
}
}
class StoppedState implements PlayerState {
@Override
public void play() {
System.out.println("Starting playback");
// Start playback logic
}
@Override
public void pause() {
System.out.println("Can't pause when stopped");
}
@Override
public void stop() {
System.out.println("Already stopped");
}
}
class MusicPlayer {
private PlayerState currentState;
public MusicPlayer() {
this.currentState = new StoppedState();
}
public void play() {
currentState.play();
}
public void pause() {
currentState.pause();
}
public void stop() {
currentState.stop();
}
public void setState(PlayerState newState) {
this.currentState = newState;
}
}
public class Client {
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
player.setState(new PlayingState());
// Play music
player.play();
// Pause music
player.pause();
// Stop music
player.stop();
}
}
- MusicPlayer는 현재 상태 객체(PlayerState)를 가지고 있다.
- play(), pause(), stop() 요청이 들어오면 현재 상태 객체에 위임한다.
- 현재 상태가 PlayingState인지, PausedState인지, StoppedState인지에 따라 동작이 달라진다.





Editor는 Manager로 구성되고,
EventManager는 여러 Listener로 구성된다.
Editor는 파일을 열고 저장하는 본래 기능에 집중하고,
이벤트와 관련된 처리는 EventManager에 일임한다.
EventManager는 등록된 EventListener들에게 알림을 보내고,
각 Listener는 update()를 통해 실제 작업(이메일 전송, 로그 기록 등)을 수행한다.
즉, 이런 구조를 통해 객체들이 느슨하게 연결된다.

절대평가: 95점 A+/90점 A0