topgaren / DevStudy

0 stars 0 forks source link

Software Design Pattern #7

Closed topgaren closed 4 years ago

topgaren commented 4 years ago

Software Design Pattern

위키백과 - 소프트웨어 디자인 패턴

소프트웨어 디자인 패턴(Software Design Pattern)이란 소프트웨어 공학에서 소프트웨어 디자인 시 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책을 말한다. 소스나 기계 코드로 바로 전환될 수 있는 완성된 디자인은 아니며, 다른 상황에 맞게 사용될 수 있는 문제들을 해결하는데에 쓰이는 서술이나 템플릿이다. 디자인 패턴은 프로그래머가 어플리케이션이나 시스템을 디자인할 때 공통된 문제들을 해결하는데에 쓰이는 형식화 된 가장 좋은 관행이다.

대표적인 디자인 패턴

생성 패턴(Creational Patterns) : 추상 객체 인스턴스화

구조 패턴(Structural Patterns) : 객체 결합

행위 패턴(Behavioral Patterns) : 객체 간 커뮤니케이션

topgaren commented 4 years ago

싱글톤(Singleton) 패턴

개요

싱글톤 패턴은 딱 한 개의 인스턴스만 만들어 사용하기 위한 패턴이다. DBCP(Database Connection Pool), Thread Pool, 디바이스 설정 객체 등의 경우 여러 개의 인스턴스를 생성하여 사용하면 자원을 낭비하게 되거나 버그를 발생시킬 수 있다. 따라서 오직 하나만 생성하고 해당 인스턴스를 사용하도록 하는 것이 이 패턴의 목적이다.

싱글톤 패턴 사용 시 주의할 점

싱글톤 인스턴스가 너무 많은 일을 하거나, 많은 양의 데이터를 공유할 경우 다른 인스턴스들과의 결합도가 높아져 "개방-폐쇄 원칙"을 위배하게 된다. 즉 객체 지향 설계 원칙에 어긋나게 되고, 수정 및 테스트가 어려워진다.

싱글톤 패턴의 구현

  1. 가장 간단한 형태

    public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if(instance == null) {  // (1) Null Check Point
            instance = new Singleton(); // (2) Create Instance Point
        }
        return instance;
    }
    }
  2. 멀티 쓰레드 환경에서 안전하도록 변경한 형태

    public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {  // (1) Null Check Point
            instance = new Singleton(); // (2) Create Instance Point
        }
        return instance;
    }
    }
  3. 중첩 클래스를 사용한 형태

    public class Singleton {
    
    private Singleton() {}
    
    public static class SingletonHolder {
        public static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    }
topgaren commented 4 years ago

팩토리 메소드(Factory Method) 패턴

개요

팩토리 메소드 패턴은 객체를 생성하는 부분을 서브 클래스에 위임하는 패턴이다. 객체를 직접 생성하지 않고 팩토리 메소드 클래스를 통해 객체를 대신 생성시키고 그 객체를 반환 받아 사용하기 때문에 결합도를 낮추고, 효율적인 코드 제어를 할 수 있어 유지보수가 용이하다는 장점이 있다.

팩토리 메소드 구현의 예

factory_method_pattern

Coffee.java

public abstract class Coffee {

    public abstract String getName();
}

VanilaCoffee.java

public class VanilaCoffee extends Coffee {

    @Override
    public String getName() {
        return "Vanila Coffee";
    }
}

MokaCoffee.java

public class MokaCoffee extends Coffee {

    @Override
    public String getName() {
        return "Moka Coffee";
    }
}

Factory.java

public abstract class Factory {

    public abstract Coffee makeCoffee(String coffeeName);
}

CoffeeFactory.java

public class CoffeeFactory extends Factory {

    @Override
    public Coffee makeCoffee(String coffeeName) {

        switch(coffeeName) {
            case "Vanila": return new VanilaCoffee();
            case "Moka":   return new MokaCoffee();
        }
        return null;
    }
}
topgaren commented 4 years ago

빌더(Builder) 패턴

이펙티브 자바(Effective-Java)

규칙 2. 생성자 인자가 많을 때는 빌더(Builder) 패턴 적용을 고려하라

객체 생성

점층적 생성자 패턴

다음의 객체를 대상으로 생성자를 생성한다고 가정한다.

public class NutritionFacts {
    // Required parameters(필수 인자)
    private final int servingSize;
    private final int servings;

    // Optional parameters(선택 인자)
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;
}

자바빈 패턴

자바빈 패턴은 setter를 사용하여 생성코드의 가독성을 높인다.

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohdydrate(27);

빌더 패턴

// Effective Java의 Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters(필수 인자)
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

빌더 패턴으로 다음과 같이 객체 생성이 가능하다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
    .calories(100).sodium(35).carbohydrate(27).build();

Reference Effective Java 3rd Edition, Joshua Bloch, 2018 https://johngrib.github.io/wiki/builder-pattern/

topgaren commented 4 years ago

템플릿 메소드(Template Method) 패턴

개요

상위 클래스에서 처리의 흐름을 제어하고, 하위 클래스에서 처리의 내용을 구체화시키는 패턴. 여러 클래스에서 공통적인 사항을 상위 추상 클래스에 구현하고 각각의 상세 부분은 하위 클래스에서 구현함으로써 코드의 중복을 줄이고, 리팩토링에 유리한 패턴이다. 상속을 이용한 확장 개발 방법으로써 전략 패턴(Strategy Pattern)과 함께 가장 많이 사용되는 패턴 중 하나.

템플릿 메소드 패턴 적용 예시 : 커피와 차를 만드는 알고리즘

public class Coffee {

    public void makeCoffee() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }

    public void boilWater() { System.out.println("물을 끓인다."); }
    public void brewCoffeeGrinds() { System.out.println("커피를 우려낸다."); }
    public void pourInCup() { System.out.println("컵에 따른다."); }
    public void addSugarAndMilk() { System.out.println("설탕과 우유를 추가한다."); }
}
public class Tea {

    public void makeTea() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    public void boilWater() { System.out.println("물을 끓인다."); }
    public void steepTeaBag() { System.out.println("차를 우려낸다."); }
    public void pourInCup() { System.out.println("컵에 따른다."); }
    public void addLemon() { System.out.println("레몬을 추가한다."); }
}

커피와 차를 만드는 과정에서 공통적인 부분은 다음과 같다.

  1. 물을 끓인다.
  2. 컵에 따른다.
  3. 대상을 우려낸다. (커피와 차에 따라 대상이 달라진다.)
  4. 첨가물이 있다. (커피와 차에 따라 첨가물이 달라진다.)

공통적인 부분을 추상화 시켜 상위 클래스를 생성하고, 커피와 차에 따라 달라지는 코드는 하위 클래스에서 구체화 할 수 있도록 코드를 아래와 같이 변경할 수 있다.

template_method_pattern

public abstract class CaffeineBeverage {

    public void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    public abstract void brew();
    public abstract void addCondiments();

    public void boilWater() { System.out.println("물을 끓인다."); }
    public void pourInCup() { System.out.println("컵에 따른다."); }
}
public class Coffee extends CaffeineBeverage {

    @Override
    public void brew() { System.out.println("커피를 우려낸다."); }

    @Override
    public void addCondiments() { System.out.println("설탕과 우유를 추가한다."); }
}
public class Tea extends CaffeineBeverage {

    @Override
    public void brew() { System.out.println("차를 우려낸다."); }

    @Override
    public void addCondiments() { System.out.println("레몬을 추가한다."); }
}

Reference https://jusungpark.tistory.com/24

topgaren commented 4 years ago

데코레이터(Decorator) 패턴

개요

데코레이터 패턴이란 객체를 동적(dynamic)으로 서브 클래스를 이용하여 기능을 확장하는 방법이다.

계방-폐쇄 원칙(OCP; Open-Closed Principle) 클래스는 확장에 대해서는 열려 있어야 하지만, 코드 변경에 대해서는 닫혀 있어야 한다.

데코레이터 패턴 클래스 다이어그램

decorator_pattern

데코레이터 패턴 적용 예시 : 치즈 피자에 토핑 얹기

피자 추상 클래스와 이를 구현한 치즈 피자를 정의

public abstract class Pizza {

    public abstract String getName();
}
public class CheesePizza extends Pizza {

    @Override
    public String getName() { return "치즈 피자"; }
}

토핑 추상 클래스와 이를 구현한 페퍼로니 토핑, 베이컨 토핑 클래스를 정의

public abstract class Topping extends Pizza {

    protected Pizza pizza;

    public Topping(Pizza pizza) {
        this.pizza = pizza;
    }

    @Override
    public abstract String getName();
}
public class PepperoniTopping extends Topping {

    public PepperoniTopping(Pizza pizza) {
        super(pizza);
    }

    @Override
    public String getName() {
        return "페퍼로니 " + pizza.getName(); 
    }
}
public class BaconTopping extends Topping {

    public BaconTopping(Pizza pizza) {
        super(pizza);
    }

    @Override
    public String getName() {
        return "베이컨 " + pizza.getName();
    }
}

아래 코드는 데코레이터 패턴을 사용하여 치즈 피자에 토핑을 얹는 것을 구현한 것이다.

public static void main(String[] args) {

    // 기본적인 치즈 피자
    Pizza cheesePizza = new CheesePizza();
    System.out.println(cheesePizza.getName());

    // 치즈 피자 + 페퍼로니 토핑
    Pizza cheesePizzaWithPepperoni = new PepperoniTopping(cheesePizza);
    System.out.println(cheesePizzaWithPepperoni.getName());

    // 치즈 피자 + 베이컨 토핑
    Pizza cheesePizzaWithBacon = new BaconTopping(cheesePizza);
    System.out.println(cheesePizzaWithBacon.getName());

    // 치즈 피자 + 베이컨 토핑 + 페퍼로니 토핑
    Pizza cheesePizzaWithBaconAndPepperoni 
        = new BaconTopping(new PepperoniTopping(cheesePizza));
    System.out.println(cheesePizzaWithBaconAndPepperoni.getName());
}
치즈 피자
페퍼로니 치즈 피자
베이컨 치즈 피자
베이컨 페퍼로니 치즈 피자

Reference https://jusungpark.tistory.com/9?category=630296 https://jdm.kr/blog/78

topgaren commented 4 years ago

컴포지트(Composite) 패턴

개요

컴포지트 패턴이란 여러 개의 객체들로 구성된 복합 객체와 단일 객체를 클라이언트에서 구별 없이 다루게 해주는 방법이다. 전체-부분 관계를 갖는 객체를 정의하기에 적합하다. 클라이언트는 전체와 부분을 구분하지 않고 동일한 인터페이스 를 사용할 수 있다는 특징이 있다.

컴포지트 패턴의 클래스 다이어그램

composite_pattern

컴포지트 패턴 적용 예시 : 파일 시스템 구현

파일 시스템을 구현하기 위해 다음과 같이 파일에 해당하는 클래스와 디렉토리에 해당하는 클래스를 각각 정의하였다.

public class File {

    private String name;
}
public class Directory {

    private String name;
    private List<File> fileList;

    public void addFile(File file) {
        //...
    }
}

위에서 정의한 디렉토리는 addFile() 메소드를 통해 디렉토리 내에 파일을 추가할 수 있다. 그러나 실제로 디렉토리는 파일 뿐만 아니라 다른 디렉토리도 저장할 수 있다. 위의 디렉토리 구조로는 이 기능을 만족시키지 못한다. 물론 멤버 변수로 Directory 클래스의 리스트를 선언하고 addDirectory() 메소드를 추가하는 방법으로 구현할 수 있다. 그러나 추가/삭제/조회 연산 시 동일한 기능을 파일과 디렉토리에 대해 각각 2번씩 수행해야 하므로 중복되는 코드가 생기고 유지 보수가 어려워지게 된다.

컴포지트 패턴을 적용하기 위해 파일과 디렉토리의 공통 인터페이스를 다음과 같이 정의한다.

public interface Node {

    public String getName();
}

그리고 파일과 디렉토리를 Node를 구현한 형태로 다음과 같이 정의한다.

publc class File implements Node {

    private String name;

    @Override
    public String getName() { return name; }
}
public class Directory implements Node {

    private String name;
    private List<Node> children;

    @Override
    public String getName() { return name; }

    public void addChild(Node node) {
        children.add(node);
    }
}

디렉토리와 파일의 관계는 전체-부분 관계로 해석될 수 있으며, 이 때 컴포지트 패턴을 적용할 수 있다. 디렉토리와 파일을 일관성 있게 다루기 위해 추상화 시켰다. 이를 통해 디렉토리는 파일 뿐만 아니라 자기 자신을 자식으로 가질 수 있도록 구현할 수 있다.

public class FileSystem {

    public static void testFileSystem() {

        Directory root = new Directory();
        root.addChild(new File());
        root.addChild(new Directory());

        Directory bin = new Directory();
        root.addChild(bin);
    }
}

Reference https://jdm.kr/blog/228 https://gmlwjd9405.github.io/2018/08/10/composite-pattern.html

topgaren commented 4 years ago

전략(Strategy) 패턴

개요

전략 패턴은 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴이다. 여기서 특정 행위를 전략(Strategy)이라고 하며 문제를 해결하는 알고리즘 및 비즈니스 로직을 담고 있다. 특히 게임 프로그래밍에서 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동 방식을 변경하고 싶을 때 전략 패턴이 유용하게 사용된다.

템플릿 메소드 패턴과의 비교

템플릿 메소드 패턴은 같은 문제의 해결책으로 상속을 이용한 확장 방법을 이용한다. 반면 전략 패턴은 각각의 행위를 클래스로 정의하고 객체 주입을 통한 행위 변경 방법을 이용한다.

전략 패턴의 클래스 다이어그램

strategy_pattern

전략 패턴 구현 예시 : 소총과 수류탄을 사용하는 군인 구현

소총과 수류탄을 사용하는 행위를 전략으로 구현한다.

public interface Strategy {

    public void runStrategy();
}
public class RiffleStrategy implements Strategy {

    @Override
    public void runStrategy() {
        System.out.println("Todadadadadadadadadadadada!!!");
    }
}
public class GrenadeStrategy implements Strategy {

    @Override
    public void runStrategy() {
        System.out.println("Fire in the hole!!!");
    }
}

소총 및 수류탄 전략을 사용하는 군인 클래스를 정의한다. 또한 전략을 동적으로 교체할 수 있도록 setter 메소드를 구현한다.

public class Soldier {

    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void runContext() {
        System.out.println("Mission Start");
        strategy.runStrategy();
        System.out.println("Mission Complete\n");
    }
}

위에서 정의한 군인 클래스는 전략 패턴에서 Context에 해당한다. 전략을 교체하며 Context를 사용하는 Client 객체를 다음과 같이 구현한다.

public class CommandOfficer {

    public static void giveAnOrder() {
        Soldier soldier = new Soldier();

        // 소총 및 수류탄 전략 생성
        Strategy riffleStrategy = new RiffleStrategy();
        Strategy grenadeStrategy = new GrenadeStrategy();

        // 소총 전략 사용
        soldier.setStrategy(riffleStrategy);
        soldier.runContext();

        // 수류탄 전략 사용
        soldier.setStrategy(grenadeStrategy);
        soldier.runContext();
    }
}
Mission Start
Todadadadadadadadadadadada!!!
Mission Complete

Mission Start
Fire in the hole!!!
Mission Complete

Reference https://limkydev.tistory.com/84 https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html

topgaren commented 4 years ago

옵저버(Observer) 패턴

개요

옵저버 패턴은 한 객체의 상태 변화에 따라 다른 객체의 상태도 연동되도록 일대다 객체 의존 관계를 구성 하는 패턴을 말한다. 데이터의 변경이 발생했을 경우 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용하다.

옵저버 패턴의 클래스 다이어그램

observer_pattern

옵저버 패턴 구현 예시 : 뉴스 구독

뉴스를 구독하는 NewsSubscriber 클래스와 뉴스를 전달하는 NewsPublisher 클래스를 설계한다. 뉴스 데이터는 제목(title)과 내용(content)으로 구성되어 있다.

public interface Publisher {

    public void add(Observer observer);
    public void delete(Observer observer);
    public void notifyObservers();
}
public interface Observer {

    public void update(String title, String content);
}
public class NewsPublisher implements Publisher {

    private List<Observer> observerList;
    private String title; 
    private String content;

    public NewsPublisher() {
        observerList = new ArrayList<>();
    }

    @Override
    public void add(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void delete(Observer observer) {
        int delIndex = observerList.indexOf(observer);
        observerList.remove(delIndex);
    }

    @Override
    public void notifyObservers() {
        for(Observer observer : observerList) {
            observer.update(title, content);
        }
    }

    public void setNewsInfo(String title, String content) {
        this.title = title;
        this.content = content;
        notifyObservers();
    }
}
public class NewsSubscriber implements Observer {

    private String newsString;
    private Publisher publisher; 

    public NewsSubscriber(Publisher publisher) {
        this.publisher = publisher;
        publisher.add(this);
    }

    @Override
    public void update(String title, String content) {
        newsString = new StringBuilder("").
                append("title : ").append(title).append("\ncontent : ").append(content).toString();

        displayNews();
    }

    public void displayNews() {
        System.out.println("=== News ===\n" + newsString);
    }

    public void withdraw() {
        publisher.delete(this);
    }
}

다음은 옵저버 패턴을 적용하여 뉴스를 구독하고 탈퇴하는 것을 코드로 구현한 것이다.

public class NewsSubscribe {

    public static void newsSubscribe() {

        NewsPublisher sbc = new NewsPublisher();

        NewsSubscriber subscriber1 = new NewsSubscriber(sbc);
        NewsSubscriber subscriber2 = new NewsSubscriber(sbc);

        sbc.setNewsInfo("Greeting", "Hello World");

        subscriber1.withdraw(); 

        // 탈퇴한 subscriber1은 해당 뉴스를 받지 못한다.
        sbc.setNewsInfo("Farewell", "Goodbye");
    }
}
=== News ===
title : Greeting
content : Hello World
=== News ===
title : Greeting
content : Hello World
=== News ===
title : Farewell
content : Goodbye

Reference https://gmlwjd9405.github.io/2018/07/08/observer-pattern.html https://flowarc.tistory.com/entry/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4Observer-Pattern