GoF의 23가지 디자인 패턴
아래 코드는 IDE에서 실행하기에는 약간의 수정이 필요
패턴이 구조적으로 어떤지 이해를 위한 코드
공부하려고 작성해 두었음...
틀린 부분이 있을지도
생성 패턴
구조 패턴
행위 패턴
- State 패턴
- Strategy 패턴
- Observer 패턴
- Template method 패턴
- Command 패턴
- Interpreter 패턴
- Chain of Responsibility 패턴
- Iterator 패턴
- Mediator 패턴
- Memento 패턴
- Visitor 패턴
생성 패턴
Singleton 패턴
public class Singleton {
// private 접근 제어자로 생성자를 정의하여 외부에서 인스턴스를 직접 생성하지 못하도록 함
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
/*
static 접근 제어자로 getInstance() 메서드를 정의하여
Singleton 객체를 생성 또는 이미 생성된 객체를 반환
Singleton 클래스는 오직 한 번만 인스턴스를 생성할 수 있도록 하기 위해,
모든 객체가 getInstance() 메서드에서 반환된 동일한 instance 필드를 참조
*/
if (instance == null) {
// getInstance() 메서드는 instance 필드가 null인 경우에만 객체를 생성
instance = new Singleton();
}
return instance; // 이미 생성된 객체가 존재하는 경우에는 해당 객체를 반환.
}
}
/*
이렇게 구현된 Singleton 패턴은 어디서든 동일한 Singleton 객체를 참조할 수 있으므로,
객체의 일관성과 안정성을 보장할 수 있음
*/
Prototype 패턴
// 프로토타입 패턴은 객체를 생성하는 비용이 크거나, 과정이 복잡할 때, 객체를 복제하여 사용하는 패턴
public interface Prototype { // 복제할 객체를 인터페이스를 구현하여 생성
public Prototype clone();
}
public class ConcretePrototype1 implements Prototype {
public Prototype clone() {
return new ConcretePrototype1();
}
}
public class ConcretePrototype2 implements Prototype {
public Prototype clone() {
return new ConcretePrototype2();
}
}
public class Client {
private Prototype prototype;
public Client(Prototype prototype) {
this.prototype = prototype;
}
public Prototype operation() {
return prototype.clone();
}
}
/*
새로운 객체를 만들 때마다 새로운 객체를 생성하는 대신,
이미 존재하는 객체를 복제하여 새로운 객체를 만드는 패턴
*/
Builder 패턴
/*
빌더 패턴은 생성자가 매우 복잡한 객체를 생성할 때 유용하게 사용됨
복잡한 객체를 만들 때 필요한 여러 단계를 캡슐화
빌더 패턴은 두 가지 유형이 있음
첫 번째 유형은 객체를 생성하고 반환하는 메서드를 가진 빌더 객체를 사용하는 방법
두 번째 유형은 객체를 직접 생성하고 빌더 객체의 메서드를 사용하여 값을 설정하는 방법
다음 코드는 두 번째 유형의 빌더 패턴을 사용한 코드 예시
*/
public class Car {
public static class Builder {
private String brand;
private String model;
private String color;
private int year;
private int price;
public Builder setBrand(String brand) {
this.brand = brand;
return this;
}
public Builder setModel(String model) {
this.model = model;
return this;
}
public Builder setColor(String color) {
this.color = color;
return this;
}
public Builder setYear(int year) {
this.year = year;
return this;
}
public Builder setPrice(int price) {
this.price = price;
return this;
}
public Car build() {
return new Car(this);
}
}
private String brand;
private String model;
private String color;
private int year;
private int price;
private Car(Builder builder) {
this.brand = builder.brand;
this.model = builder.model;
this.color = builder.color;
this.year = builder.year;
this.price = builder.price;
}
}
/*
Car 객체를 생성하기 위해 여러 단계를 캡슐화함
메서드를 사용하여 각 값들을 설정한 후 build() 메서드를 호출하여 객체를 생성
이처럼 복잡한 객체를 생성하는 과정을 캡슐화하여 단순화, 유연성을 높일 수 있음
*/
Factory method 패턴
public interface Product {
// Product 인터페이스 정의
}
public class ConcreteProduct implements Product {
// 구체적인 Product 클래스 정의
}
public abstract class Creator {
// 추상 메서드로 정의
public abstract Product factoryMethod();
public void someOperation() {
// 팩토리 메서드를 호출하여 Product 객체를 생성, 사용
Product product = factoryMethod();
// ...
}
}
public class ConcreteCreator extends Creator {
public Product factoryMethod() {
// Creator 클래스에서 팩토리 메서드를 호출하면
// 상속받은 ConcreteCreator 클래스에서 구현된 팩토리 메서드가 실행되어
return new ConcreteProduct(); // ConcreteProduct 객체를 생성
}
}
/*
팩토리 메서드를 호출하는 코드에서는 생성될 객체의 클래스를 직접 명시하지 않고,
팩토리 메서드를 호출함으로써 객체 생성을 추상화할 수 있으므로,
객체 생성과 사용 사이의 의존성을 줄일 수 있음
*/
Abstract factory 패턴
/*
추상 팩토리 패턴은 관련성 있는 여러 개의 객체를 일관성 있게 생성하는 패턴
객체 간의 관련성을 높이고 객체 생성의 복잡도를 줄임
구체적인 객체 생성 방법을 추상화, 일관성 있는 객체 생성을 보장
*/
public interface GUIFactory { // 인터페이스를 정의, 메서드를 선언
Button createButton();
TextField createTextField();
}
public class MacFactory implements GUIFactory { // MacFactory 와 WindowsFactory 클래스로 구현
public Button createButton() {
return new MacButton();
}
public TextField createTextField() {
return new MacTextField();
}
}
public class WindowsFactory implements GUIFactory {
public Button createButton() {
return new WindowsButton();
}
public TextField createTextField() {
return new WindowsTextField();
}
}
public interface Button { // 버튼도 인터페이스로 정의
void paint();
}
public class MacButton implements Button { // 각각 운영체제에 맞게 클래스를 구현
public void paint() {
System.out.println("This is a Mac button.");
}
}
public class WindowsButton implements Button {
public void paint() {
System.out.println("This is a Windows button.");
}
}
public interface TextField { // 텍스트필드도 인터페이스로 정의
void paint();
}
public class MacTextField implements TextField { // 각각 운영체제에 맞게 클래스를 구현
public void paint() {
System.out.println("This is a Mac text field.");
}
}
public class WindowsTextField implements TextField {
public void paint() {
System.out.println("This is a Windows text field.");
}
}
// Application 클래스에서 각각 요소를 받아 creatUI() 메서드로 GUI요소들을 화면에 그림
public class Application {
private Button button;
private TextField textField;
public Application(GUIFactory factory) {
button = factory.createButton();
textField = factory.createTextField();
}
public void createUI() {
button.paint();
textField.paint();
}
}
public static void main(String[] args) {
Application app = new Application(new MacFactory());
app.createUI();
app = new Application(new WindowsFactory());
app.createUI();
}
구조 패턴
Composite 패턴
/*
컴포지트 패턴은 객체들을 트리 구조로 구성하여 부분-전체 계층을 나타내는 패턴
단일 객체와 복합 객체를 모두 동일하게 다룰 수 있게 하여,
클라이언트가 트리 구조 내의 모든 객체에 접근할 수 있도록함
각 객체는 공통적으로 사용할 수 있는 인터페이스를 구현, 부분-전체 계층 구조에서
부모와 자식 역할을 수행할 수 있음
*/
public interface FileSystem {
public void printName();
}
// FileSystem 인터페이스를 구현하는 클래스 File 을 정의
public class File implements FileSystem {
private String name;
public File(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
// Folder 클래스가 FileSystem 인터페이스를 구현하면서
// File 과 Folder 모두를 자식으로 가질 수 있는 컴포지트 객체가 됨
public class Folder implements FileSystem {
private List<FileSystem> children = new ArrayList<>();
private String name;
public Folder(String name) {
this.name = name;
}
public void add(FileSystem child) {
children.add(child);
}
public void remove(FileSystem child) {
children.remove(child);
}
public void printName() {
System.out.println(name);
for (FileSystem child : children) {
child.printName();
}
}
}
Decorator 패턴
/*
데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가할 수 있게 해주는 패턴
객체의 기능을 유연하게 확장, 상속을 이용한 기능 확장보다 더욱 유연한 방법을 제공
*/
public interface Pizza { // Pizza 인터페이스는 컴포넌트 역할
String getDescription();
double getCost();
}
public class PlainPizza implements Pizza { // PlainPizza 클래스는 구체적인 컴포넌트 역할
public String getDescription() {
return "Thin dough";
}
public double getCost() {
return 4.00;
}
}
// PizzaDecorator 추상 클래스는 데코레이터 역할
public abstract class PizzaDecorator implements Pizza {
protected Pizza decoratedPizza;
public PizzaDecorator(Pizza decoratedPizza) {
this.decoratedPizza = decoratedPizza;
}
public String getDescription() {
return decoratedPizza.getDescription();
}
public double getCost() {
return decoratedPizza.getCost();
}
}
// Cheese 클래스와 TomatoSauce 클래스는 구체적인 데코레이터 역할
public class Cheese extends PizzaDecorator {
public Cheese(Pizza decoratedPizza) {
super(decoratedPizza);
}
public String getDescription() {
return decoratedPizza.getDescription() + ", cheese";
}
public double getCost() {
return decoratedPizza.getCost() + 1.25;
}
}
public class TomatoSauce extends PizzaDecorator {
public TomatoSauce(Pizza decoratedPizza) {
super(decoratedPizza);
}
public String getDescription() {
return decoratedPizza.getDescription() + ", tomato sauce";
}
public double getCost() {
return decoratedPizza.getCost() + 0.50;
}
}
/*
다른 토핑을 추가하더라도 원래 피자 객체가 가지고 있는 getDescription() 메서드와
getCost() 메서드를 그대로 사용하면서 토핑을 추가할 수 있는 유연한 방법을 제공
*/
Facade 패턴
/*
퍼사드 패턴은 복잡한 서브시스템을 단순한 인터페이스로 대체하여 클라이언트와 서브시스템 간의
복잡한 상호작용을 단순화 하는 패턴
클라이언트는 퍼사드 객체만 호출하고 퍼사드 객체가 내부적으로 서브시스템 객체들을 조작, 책임
*/
class SubSystemA {
public void operationA() {
// 복잡한 A 기능
}
}
class SubSystemB {
public void operationB() {
// 복잡한 B 기능
}
}
class SubSystemC {
public void operationC() {
// 복잡한 C 기능
}
}
class Facade { // Facade 클래스가 서브시스템을 통합
private SubSystemA subsystemA;
private SubSystemB subsystemB;
private SubSystemC subsystemC;
public Facade() {
subsystemA = new SubSystemA();
subsystemB = new SubSystemB();
subsystemC = new SubSystemC();
}
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
subsystemC.operationC();
}
}
// Client 에서 Facade 객체를 생성하여 메서드를 호출하면
// Facade 내부에서 복잡한 서브시스템에 대한 요청을 처리
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation();
}
}
// 이처럼 클라이언트는 서브시스템의 내부 구조를 몰라도 Facade 클래스가
// 복잡한 서브시스템의 내부 구조와 상호작용을 책임을 지는 구조
Adapter 패턴
/*
어댑터 패턴은 기존의 클래스의 인터페이스를 클라이언트가 원하는 인터페이스로 변환하는 패턴
두 개의 인터페이스가 서로 호환되지 않을 때 유용
일반적으로 어댑터 패턴은 다음과 같이 구성
*/
public interface A { // 기존 인터페이스 A
void doSomething();
}
public interface B { // 새로운 인터페이스 B
void doSomethingElse();
}
public class existingObject implements A { // 기존 객체
public void doSomething() {
System.out.println("doSomething() in A");
}
}
// 기존 인터페이스와 클라이언트가 필요로 하는 새로운 인터페이스 사이에서 변환을 수행하는 객체
public class Adapter implements B {
private A a;
public Adapter(A a) {
this.a = a;
}
public void doSomethingElse() {
a.doSomething();
}
}
public class Client { // 어댑터 패턴을 이용하여 다른 인터페이스를 사용
public static void main(String[] args) {
A a = new existingObject();
B b = new Adapter(a);
b.doSomethingElse(); // "doSomething() in A"가 출력됨
}
}
/*
existingObject 클래스는 기존 인터페이스 A 를 구현
Adapter 클래스가 인터페이스 B를 구현하면서 A와의 어댑터 역할을 함
클라이언트 객체에서 B 인터페이스를 이용하여 A 객체의 기능을 사용할 수 있음
*/
Bridge 패턴
/*
브릿지 패턴은 객체의 인터페이스와 구현을 분리하여 서로 독립적으로 변경할 수 있게 하는 패턴
추상화와 구현을 분리하여 두 개의 클래스 계층 구조를 동시에 변화시키는 것을 가능하게함
패턴의 주요 요소로,
Abstraction : 기능의 정의와 구현부의 분리를 위한 추상화 클래스
Refined Abstraction : 추상화 클래스를 확장하여 구현하는 클래스
Implementor : Abstraction이 구현체에 의존할 수 있는 인터페이스
Concrete Implementor : Implementor를 구현하는 구현 클래스
*/
// Implementor
interface DrawingAPI {
public void drawCircle(double x, double y, double radius);
}
// Concrete Implementor 1
class DrawingAPI1 implements DrawingAPI {
public void drawCircle(double x, double y, double radius) {
System.out.printf("API1.circle at %f:%f radius %f\n", x, y, radius);
}
}
// Concrete Implementor 2
class DrawingAPI2 implements DrawingAPI {
public void drawCircle(double x, double y, double radius) {
System.out.printf("API2.circle at %f:%f radius %f\n", x, y, radius);
}
}
// Abstraction
interface Shape {
public void draw();
public void resizeByPercentage(double pct);
}
// Refined Abstraction
class CircleShape implements Shape {
private double x, y, radius;
private DrawingAPI drawingAPI;
public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
this.x = x;
this.y = y;
this.radius = radius;
this.drawingAPI = drawingAPI;
}
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
public void resizeByPercentage(double pct) {
radius *= (1.0 + pct/100.0);
}
}
/*
DrawingAPI 인터페이스와 CircleShape 추상화 클래스가 브릿지 패턴의 Implementor, Abstraction 역할
DrawingAPI1 과 DrawingAPI2 는 Implementor 를 구현하는 클래스,
CircleShape 을 확장한 CircleShape 가 Refined Abstraction 역할을 수행
*/
Flyweight 패턴
/*
플라이웨이트 패턴은 객체를 공유하여 메모리 사용을 최적화하는 패턴
많은 수의 비슷한 객체가 필요한 경우 개별적으로 만드는 것은 비효율적이기 때문에 사용됨
*/
public interface Flyweight {
void draw(int x, int y, int width, int height, Color color);
}
// Flyweight 인터페이스를 구현
public class Character implements Flyweight {
private char c;
private Font font;
public Character(char c, Font font) {
this.c = c;
this.font = font;
}
public void draw(int x, int y, int width, int height, Color color) {
System.out.println("Drawing character " + c + " with font " + font +
" at (" + x + "," + y + ") with color " + color);
}
}
// FlyweightFactory 클래스가 Flyweight 인터페이스를 구현한 객체를 관리하고 반환
public class FlyweightFactory {
private Map<Character, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(char c, Font font) {
Character character = new Character(c, font);
Flyweight flyweight = flyweights.get(character);
if (flyweight == null) {
flyweight = character;
flyweights.put(character, flyweight);
}
return flyweight;
}
}
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight('A', new Font("Arial", Font.BOLD, 12));
flyweight1.draw(10, 10, 20, 20, Color.RED);
Flyweight flyweight2 = factory.getFlyweight('B', new Font("Arial", Font.PLAIN, 14));
flyweight2.draw(30, 30, 40, 40, Color.BLUE);
Flyweight flyweight3 = factory.getFlyweight('A', new Font("Arial", Font.BOLD, 12));
flyweight3.draw(50, 50, 60, 60, Color.GREEN);
System.out.println(flyweight1 == flyweight3); // true
}
}
Proxy 패턴
/*
프록시 패턴은 객체의 대리자(Proxy)를 통해 객체에 접근하는 패턴
대리자는 실제 객체를 대신하여 사용될 수 있고,
실제 객체에 대한 접근을 제어, 대기, 캐싱 등의 기능을 추가할 수 있음
*/
public interface Image {
void display();
}
/*
RealImage 클래스는 실제 이미지를 로드하고 표시
ProxyImage 클래스는 RealImage 객체를 감싸서 이미지를 로드하지 않고 필요할 때만
RealImage 객체를 생성하고 로드
이미지 로드하는 데 오랜 시간이 걸릴 RealImage 객체 대신 ProxyImage 객체를 사용하면
ProxyImage 객체를 사용하는 동안 로딩 시간을 줄일 수 있음
*/
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename + " from disk");
}
}
public class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
행위 패턴
State 패턴
/*
State 패턴은 객체의 상태(State)에 따라 행위(Behavior)를 변경하는 디자인 패턴
객체가 특정 상태에 놓이면, 그 상태에 따라 행위를 수행,
이를 캡슐화하여 상태와 행위를 분리. 상태 변경에 따른 행위의 변화를 관리하기 쉽도록함
*/
public interface State { // State 인터페이스를 정의
public void handle();
}
public class ConcreteStateA implements State {
public void handle() {
// ConcreteStateA 에 대한 행위 처리
}
}
public class ConcreteStateB implements State {
public void handle() {
// ConcreteStateB에 대한 행위 처리
}
} // A 클래스와 B 클래스를 정의하여 상태에 따른 구체적인 행위를 정의
public class Context {
private State currentState; // 현재 상태를 저장
public void setState(State state) { // setState 메서드를 정의하여 객체의 상태를 변경
this.currentState = state;
}
public void request() { // request 메서드를 정의하여 현재 상태에 따른 행위를 처리
currentState.handle();
}
}
/*
스테이트 패턴은 Context 객체가 상태를 변경할 때마다 상태에 따른 행위를 처리할 수 있음
상태와 행위가 서로 분리되어 있기 때문에 새로운 상태와 행위를 추가, 변경할 때에도
기존 코드를 수정하지 않고, 추가할 수 있음
*/
Strategy 패턴
/*
스트래티지 패턴은 객체의 동작을 클래스로 캡슐화,
실행 중 동적으로 동작을 변경할 수 있도록 하는 디자인 패턴
*/
// Strategy 인터페이스는 executeStrategy() 메서드를 정의하여,
// 동적으로 변경할 수 있는 기능을 캡슐화
public interface Strategy {
public int executeStrategy(int num1, int num2);
}
// 각 클래스는 인터페이스를 구현, executeStrategy() 메서드를 구현하여 각각의 기능을 캡슐화
public class OperationAdd implements Strategy {
public int executeStrategy(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubtract implements Strategy {
public int executeStrategy(int num1, int num2) {
return num1 - num2;
}
}
public class OperationMultiply implements Strategy {
public int executeStrategy(int num1, int num2) {
return num1 * num2;
}
}
public class Context {
private Strategy strategy;
// 동적으로 Strategy 객체를 변경하여 기능을 변경
public Context(Strategy strategy) {
this.strategy = strategy;
}
// executeStrategy() 메서드를 구현하여
public int executeStrategy(int num1, int num2) {\
// Strategy 객체의 executeStrategy() 메서드를 호출
return strategy.executeStrategy(num1, num2);
}
}
/*
다양한 기능을 인터페이스로 캡슐화하고, Context 객체에서 동적으로 변경
각각의 기능은 서로 독립적으로 존재
추가, 수정이 필요한 경우에 해당 클래스만 변경하면 되기에 유연하고 확장성 있는 코드를 작성 가능
*/
Observer 패턴
/*
옵저버 패턴은 객체의 상태 변화를 관찰, 변화에 따라 객체들이 자동으로 업데이트되도록 하는 패턴
객체 간의 의존성을 줄이고, 유연한 코드를 작성할 수 있도록함
*/
// ConcreteSubject 클래스의 상태가 변경될 때 수행할 행위를 update() 메서드로 정의
public interface Observer {
public void update(Object obj);
}
// Subject 인터페이스를 통해 ConcreteSubject 객체를 관찰
public interface Subject {
public void attach(Observer observer);
public void detach(Observer observer);
public void notifyObservers();
}
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private Object state;
// 관찰자를 등록
public void attach(Observer observer) {
observers.add(observer);
}
// 관찰자를 제거
public void detach(Observer observer) {
observers.remove(observer);
}
// 변경 상태를 알림
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
// ConcreteSubject 객체의 상태를 State 필드에 저장,
// 상태를 변경할 때마다, notifyObservers() 메서드를 호출하여 변화를 알림
public void setState(Object state) {
this.state = state;
notifyObservers();
}
}
public class ConcreteObserver implements Observer {
public void update(Object obj) {
// ConcreteSubject의 상태가 변경될 때 수행할 행위 정의
}
}
/*
이 코드에서 옵저버 패턴은 ConcreteSubject 객체의 상태가 변경될 때마다,
attach() 메서드로 등록된 모든 ConcreteObserver 객체들의 update() 메서드를 자동으로 호출,
상태 변화를 알리고 처리. Subject 와 Observer 인터페이스로 각 객체들의 역할을 분리
*/
Template method 패턴
/*
템플릿 메서드 패턴은 상위 클래스에서 기능의 구조를 정의, 하위 클래스에서 구체적인 기능을 구현
이 패턴은 코드의 재사용성을 높이고 유지보수성을 개선할 수 있음
*/
// 상위 클래스인 Game 을 추상 클래스로 정의하고 각 기능을 추상 메서드로 선언
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
// 템플릿 메서드인 play() 에서 initialize(), startPlay(), endPlay() 메서드를 호출
public final void play() {
initialize();
startPlay();
endPlay();
}
}
/*
Cricket, Football 클래스에서 Game 추상 클래스를 상속받아 구현
각각의 클래스에서 initialize(), startPlay(), endPlay() 메서드를 구체적으로 구현
play() 메서드를 호출하면 각각의 게임에 맞는 기능을 수행할 수 있음
*/
public class Cricket extends Game {
public void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
public void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
public void endPlay() {
System.out.println("Cricket Game Finished!");
}
}
public class Football extends Game {
public void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
public void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
public void endPlay() {
System.out.println("Football Game Finished!");
}
}
Command 패턴
/*
커맨드 패턴은 요청을 객체로 캡슐화,
실행에 필요한 모든 정보를 객체에 저장,
요청을 여러 번 취소하거나 되돌릴 수 있도록 하는 디자인 패턴
따라서, 요청을 객체로 만들어 실행하는 것으로
요청을 발생시키는 객체와 요청을 처리하는 객체를 분리하여 결합도를 낮추고, 유연성을 높임
*/
// execute() 메서드를 정의하여 기능을 캡슐화
public interface Command {
public void execute();
}
// LightOnCommand, LightOffCommand 클래스가 Command 인터페이스를 구현,
// execute() 메서드를 구현하여 각각의 기능을 캡슐화
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.switchOn();
}
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.switchOff();
}
}
/*
RemoteControl 클래스에서 Command 객체를 멤버 변수로 가지고,
setCommand() 메서드를 구현하여 기능을 설정
pressButton() 메서드로 command 객체의 execute() 를 호출하여 기능을 실행
*/
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
/*
새로운 기능을 추가하거나 변경할 때,
해당 기능을 캡슐화한 Command 클래스만 수정하면 되므로
유지보수성과 확장성이 높은 코드를 작성할 수 있음
*/
Interpreter 패턴
/*
인터프리터 패턴은 언어나 문법에 대한 문제를 해결하기 위해 사용되는 패턴 중 하나
언어의 구문을 표현하고 해석하는 방법을 제공, 특정한 언어를 구현하기 위해 사용될 수 있음
보통 인터프리터 패턴은 다음과 같은 요소를 가지고 있음
AbstractExpression : 해석기의 추상적인 표현을 나타내는 인터페이스를 정의
TerminalExpression : 해석기에서 마지막 노드를 나타내는 클래스
이 클래스는 문장에서 단어를 해석하는 등의 기능을 수행
해석기에서 문장의 논리적인 구성 요소를 해석하는 클래스
NonTerminalExpression : 해석기에서 문장의 논리적인 구성 요소를 해석하는 클래스
또한 하위 표현식을 가지고 있으며, 하위 표현식을 해석하는 방법을 정의
Context : 해석기가 해석할 문장을 포함하고 있는 클래스
*/
// 이 코드는 인터프리터 패턴의 예시.
// 간단한 수식을 해석하기 위한 코드
interface Expression {
int interpret();
}
class NumberExpression implements Expression {
private final int number;
public NumberExpression(int number) {
this.number = number;
}
public int interpret() {
return number;
}
}
class PlusExpression implements Expression {
private final Expression left;
private final Expression right;
public PlusExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public int interpret() {
return left.interpret() + right.interpret();
}
}
class Context {
private final Map<String, Expression> variables = new HashMap<>();
public void setVariable(String name, Expression expression) {
variables.put(name, expression);
}
public Expression getVariable(String name) {
return variables.get(name);
}
}
public class InterpreterExample {
public static void main(String[] args) {
Context context = new Context();
context.setVariable("a", new NumberExpression(10));
context.setVariable("b", new NumberExpression(20));
Expression expression = new PlusExpression(context.getVariable("a"),
context.getVariable("b"));
System.out.println(expression.interpret());
}
}
/*
NumberExpression, PlusExpression 클래스로 Expression 인터페이스를 구현
NumberExpression 클래스는 숫자를, PlusExpression 클래스는 덧셈 기능을 수행
Context 클래스는 문장을 해석할 때 사용되는 변수를 관리
InterpreterExample 클래스에서 context 객체의 setVariable() 메서드로 변수를 설정,
PlusExpression 객체로 덧셈 연산을 수행하고 결과를 출력
이와같이 인터프리터 패턴을 이용하여 간단한 수식을 해석할 수 있음
*/
Chain of Responsibility 패턴
/*
책임 연쇄 패턴은 어떤 객체에서 요청을 받았을 때
객체가 직접 처리하지 않고 다른 객체에 요청를 전달하여 처리하는 패턴
각 객체는 다음 객체에 대한 참조를 가지고 있고,
객체는 처리를 수행한 후 다음 객체에게 처리를 전달 또는 중단하고 반환할 수 있음
객체들의 연결 리스트(Linked List)를 구성하고, 요청이 전달될 때마다
연결된 객체들을 순서대로 탐색하여 처리할 수 있음
*/
public abstract class Logger {
protected Logger next;
public void setNext(Logger nextLogger) {
this.next = nextLogger;
}
public void logMessage(LogLevel logLevel, String message) {
if (this.canHandle(logLevel)) {
this.write(message);
}
if (next != null) {
next.logMessage(logLevel, message);
}
}
protected abstract boolean canHandle(LogLevel logLevel);
protected abstract void write(String message);
}
public class DebugLogger extends Logger {
@Override
protected boolean canHandle(LogLevel logLevel) {
return logLevel == LogLevel.DEBUG;
}
@Override
protected void write(String message) {
System.out.println("[DEBUG] " + message);
}
}
public class InfoLogger extends Logger {
@Override
protected boolean canHandle(LogLevel logLevel) {
return logLevel == LogLevel.INFO;
}
@Override
protected void write(String message) {
System.out.println("[INFO] " + message);
}
}
public class ErrorLogger extends Logger {
@Override
protected boolean canHandle(LogLevel logLevel) {
return logLevel == LogLevel.ERROR;
}
@Override
protected void write(String message) {
System.out.println("[ERROR] " + message);
}
}
public enum LogLevel {
DEBUG,
INFO,
ERROR
}
public class Application {
private static Logger createLoggerChain() {
DebugLogger debugLogger = new DebugLogger();
InfoLogger infoLogger = new InfoLogger();
ErrorLogger errorLogger = new ErrorLogger();
// 각 객체들과 연결 리스트를 구성
debugLogger.setNext(infoLogger);
infoLogger.setNext(errorLogger);
return debugLogger;
}
public static void main(String[] args) {
Logger loggerChain = createLoggerChain();
loggerChain.logMessage(LogLevel.DEBUG, "Debug message");
loggerChain.logMessage(LogLevel.INFO, "Info message");
loggerChain.logMessage(LogLevel.ERROR, "Error message");
}
}
/*
각각의 Logger 객체에서 자신의 책임 범위 내에서 요청을 처리할 수 없다면,
요청을 처리할 때까지 책임 연쇄를 타고 다음 객체에게 전달됨
이로 인해 객체는 기능 범위 내에서 책임을 질 수 있음
*/
Iterator 패턴
/*
이터레이터 패턴은 컬렉션 내부 구조를 노출하지 않고,
내부의 원소들을 반복 처리할 수 있도록 하는 패턴
컬렉션 클래스에서는 Iterator 인터페이스를 구현한 객체를 반환,
반복 처리 로직에서는 Iterator 인터페이스를 통해 컬렉션 내부의 원소들을 접근
이로인해 컬렉션 클래스와 반복 처리 클래스 간의 결합도를 낮추고, 유지 보수나 확정성이 향상됨
보통 이터레이터 패턴은 다음 요소로 이루어져 있음
Iterator : 컬렉션 내부의 원소들을 순서대로 접근할 수 있는 메서드를 정의
ConcreteIterator : Iterator 인터페이스를 구현한 구체적인 클래스로,
컬렉션 내부의 원소들을 순서대로 접근할 수 있는 구체적인 방법을 구현
Aggregate : Iterator 인터페이스를 반환하는 메서드를 정의
ConcreteAggregate: Aggregate 인터페이스를 구현한 구체적인 클래스로,
Iterator 인터페이스를 구현한 객체를 반환하는 방법을 구현
*/
public class IteratorExample {
public static void main(String[] args) {
ArrayList<String> myList = new ArrayList<String>();
myList.add("A");
myList.add("B");
myList.add("C");
Iterator<String> iter = myList.iterator();
while (iter.hasNext()) {
String str = iter.next();
System.out.println(str);
}
}
}
/*
ArrayList 컬렉션 객체를 생성, Iterator 패턴을 사용하여 각 원소에 접근하고 출력
myList.iterator() 메서드를 호출하여 Iterator 객체를 생성한 후
while 문을 사용하여 모든 원소를 순회
hasNext() 메서드로 다음 원소를 존재를 확인하고 next() 메서드로 다음 원소를 가져와서 출력
Iterator 패턴의 목적인 컬렉션 내부(ArrayList<String> myList 객체) 구조를
노출하지 않고, 내부의 원소들을 반복하여 출력할 수 있음
*/
Mediator 패턴
/*
미디에이터 패턴은 객체간의 복잡한 상호작용을 조정하는 역할을 하는 중재자(mediator) 객체를 사용,
객체 간의 결합도를 낮추는 디자인 패턴
*/
public interface ChatRoom {
void sendMessage(String message, User user);
}
public class ChatRoomImpl implements ChatRoom {
@Override
public void sendMessage(String message, User user) {
System.out.println(user.getName() + " says: " + message);
}
}
public abstract class User {
protected ChatRoom chatRoom;
protected String name;
public User(ChatRoom chatRoom, String name) {
this.chatRoom = chatRoom;
this.name = name;
}
public abstract void sendMessage(String message);
public void receiveMessage(String message) {
System.out.println(name + " receives: " + message);
}
public String getName() {
return name;
}
}
public class BasicUser extends User {
public BasicUser(ChatRoom chatRoom, String name) {
super(chatRoom, name);
}
@Override
public void sendMessage(String message) {
chatRoom.sendMessage(message, this);
}
}
public class Client {
public static void main(String[] args) {
ChatRoom chatRoom = new ChatRoomImpl();
User user1 = new BasicUser(chatRoom, "User 1");
User user2 = new BasicUser(chatRoom, "User 2");
User user3 = new BasicUser(chatRoom, "User 3");
user1.sendMessage("Hello, everyone!");
user2.sendMessage("Hi, User 1!");
user3.sendMessage("Nice to meet you all.");
}
}
/*
이 코드에서 ChatRoom(중재자) 객체를 ChatRoomImpl(채팅방) 객체로 구현하여
User 객체 끼리 상호작용하는 것이 아닌, ChatRoom 객체와만 상호작용하여
User <-> ChatRoom <-> User 이렇게 메시지를 주고 받는 동작을 수행
Mediator 패턴을 적용하여 객체 간의 직접적인 의존성을 줄일수 있고,
객체간의 통신을 관리할 수 있음
*/
Memento 패턴
/*
메멘토 패턴은 객체의 상태를 저장, 복원하기 위한 디자인 패턴 중 하나
객체의 상태를 저장하고 복원하기 위해서는 객체 자체에 저장할 상태가 있어야 하지만
상태 정보가 객체 외부에 존재해야 하는 경우가 있음. 이러한 경우에 사용됨
메멘토 패턴은 크게 Originator, Memento, Caretaker 세 가지 객체로 구성
Originator : 상태를 저장, 복원 하는 객체
Memento : Originator 객체의 상태를 저장하기 위한 객체
Caretaker : Memento 객체를 관리하는 역할
필요에 따라 Memento 객체를 Originator 객체에 전달하여 상태를 복원
*/
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
public String getState() {
return state;
}
}
public class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento state) {
mementoList.add(state);
}
public Memento get(int index) {
return mementoList.get(index);
}
}
public class Main {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State 1");
originator.setState("State 2");
caretaker.add(originator.saveStateToMemento());
originator.setState("State 3");
caretaker.add(originator.saveStateToMemento());
originator.setState("State 4");
System.out.println("Current state: " + originator.getState());
originator.getStateFromMemento(caretaker.get(0));
System.out.println("First saved state: " + originator.getState());
originator.getStateFromMemento(caretaker.get(1));
System.out.println("Second saved state: " + originator.getState());
}
}
/*
Originator 객체의 상태 State 1 -> State 2 이 상태가 Caretaker 객체에 저장됨
Caretaker 객체에 저장되면서 add() 메서드를 통해 mementoList 에 저장
Originator 객체의 상태 State 2 -> State 3
Caretaker 객체의 상태 State 2 -> State 3 mementoList 에 저장
Originator 객체의 상태 State 3 -> State 4
이후 출력을 해보면
Current state: State 4
First saved state: State 2
Second saved state: State 3 가 출력됨
*/
Visitor 패턴
/*
비지터 패턴은 객체 구조에서 구체적인 요소를 분리하는 디자인 패턴
객체 구조와 처리를 분리시켜 새로운 동작을 추가하기 쉬운 유연한 구조를 제공
비지터 패턴은 크게 두 가지의 역할을 가진 인터페이스와 그 인터페이스를 구현하는 클래스로 구성
Visitor 인터페이스는 구조체 내부의 요소들을 방문하는 visit() 메서드들을 선언
이 visit() 메서드들은 구조체 요소를 매개변수로 요소의 종류에 따라 다른 동작을 수행
구조체 요소들은 모두 Element 인터페이스를 구현, 자신이 방문할 수 있는 accept() 메서드를 제공
accept() 메서드는 방문자를 매개변수로 자신의 요소 정보를 매개변수로 넘겨줌
ConcreteVisitor 클래스는 Visitor 인터페이스를 구현, 실제 동작을 수행
ConcreteElement 클래스는 Element 인터페이스를 구현,
정보를 전달받은 ConcreteVisitor 객체를 통해 동작을 수행
이런 식으로 객체 구조를 생성하고 visit() 메서드에서 요소에 대한 구체적인 연산을 수행
*/
public interface Visitor {
void visit(ElementA elementA);
void visit(ElementB elementB);
}
public interface Element {
void accept(Visitor visitor);
}
public class ConcreteVisitor implements Visitor {
@Override
public void visit(ElementA elementA) {
System.out.println("Visiting ElementA");
// ElementA의 동작 수행
}
@Override
public void visit(ElementB elementB) {
System.out.println("Visiting ElementB");
// ElementB의 동작 수행
}
}
public class ElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return "ElementA";
}
}
public class ElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getValue() {
return 42;
}
}
public class Client {
public static void main(String[] args) {
ElementA elementA = new ElementA();
ElementB elementB = new ElementB();
Visitor visitor = new ConcreteVisitor();
elementA.accept(visitor);
elementB.accept(visitor);
}
}
'Java' 카테고리의 다른 글
[JAVA] Arrays.sort() (0) | 2023.05.17 |
---|---|
[JAVA] 정렬 여러가지 (0) | 2023.04.26 |