반응형

어댑터 패턴(Adapter Pattern)

  - 어댑터 패턴은 구조패턴(Structural Pattern) 중 하나이다.
  - 구조 패턴은 클래스를 상속과 합성을 이용해서 더 큰 클래스를 생성하는 방법을 제공하는 패턴이다.

  - 구조 패턴을 사용하면 서로 독립적으로 개발한 클래스를 하나인 양 사용할 수 있다.

  - 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴

 

장점

  - 어댑터 패턴은 연결할 수 없는 두 개의 호환되지 않는 인터페이스 사이의 커넥터 역할을 수행한다.

  - 클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 변환시킨다.

  - 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용할 수 있다.

  - 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  - 새 클래스가 생겨 복잡도가 증가할 수 있다. 경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택이 될 수도 있다.

 

코드

  • 최종 변경해 줘야 하는 객체 타켓 (Target)
  • 변환의 대상이 되는 객체 어댑티 (Adaptee)
  • 변경을 도와주는 객체 어댑터 (Adapter)

 * 예시) BugattiVeyron의 속도를 시속 마일(MPH) 단위에서 킬로미터(km/h) 단위로 반환해야 하는 예제

 

BugattiVeyron.java

 

public class BugattiVeyron implements Movable{

    @Override
    public double getSpeed() {
        return 268;
    }
}

 

Movable.java

 

public interface Movable {

    double getSpeed();
}

 

MovableAdapter.java

  - Adapter interface

 

public interface MovableAdapter {

    double getSpeed();
}

 

MovableAdapterImpl.java

  - Adapter 실제 구현체 convertMPHtoKMPH 마일단위를 킬로미터로 변환한다.

 

public class MovableAdapterImpl implements MovableAdapter{

    private Movable luxuryCars;

    // standard constructors
    MovableAdapterImpl(Movable luxuryCars) {
        this.luxuryCars = luxuryCars;
    }

    @Override
    public double getSpeed() {
        return convertMPHtoKMPH(luxuryCars.getSpeed());
    }

    private double convertMPHtoKMPH(double mph) {
        return mph * 1.60934;
    }
}

 

AdapterPatternTest.java

 

@SpringBootTest
class AdapterPatternTest {

    @Test
    void 어댑터_테스트() {
        Movable bugattiVeyron = new BugattiVeyron();
        MovableAdapter bugattiVeyronAdapter = new MovableAdapterImpl(bugattiVeyron);

        assertEquals(bugattiVeyronAdapter.getSpeed(), 431.30312, 0.00001);

    }


}

 

정리

 - 어댑터 패턴은 호환되지 않는 두 개의 객체를 호환되게 하기 위해서 하나의 Adapter라는 인터페이스를 추가해서 변환하는 로직을 추가한다.

기존 클래스의 소스코드를 수정해서 인터페이스에 맞추는 작업보다 기존 클래스의 소스코드의 수정을 하지 않고 타겟 인터페이스에 맞춰서 동작을 가능하게 한다.

 

실무 사용 사례

- java에서 collections(Arrays.asList, Collections.enumeration(), Collections.list())

- java에서 IO code

- Spring에서 Spring Security에 UserDetails

- Spring에 HandlerAdapter

728x90
반응형
반응형

싱글톤 패턴(Singleton Pattern)

  - 싱글톤 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 싱글톤 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 싱글톤 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 하나의 객체만을 생성해 이후에 호출된 곳에서는 생성된 객체를 반환하여 프로그램 전반에서 하나의 인스턴스만을 사용하게 하는 패턴이다.

 

장점

  - 한번의 객체 생성으로 재사용이 가능하여 메모리 낭비를 방지한다.

  - 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 생기는 문제를 미연에 방지할 수 있다.

 

문제점

  - 멀티 스레드 환경에서 안전하지 않다. (대처가 가능하다.)

  - 객체간의 결함도가 높아지고 변경에 유연하게 대처할 수 없다. 싱글톤 객체가 변경되면 이를 참조하고 있는 모든 값들이 변경되어야 한다.

  - 객체 상태를 유지하면 큰 문제가 발생한다.(stateful)

코드

  * 여러가지 싱글턴 구현 방법이있다.

  1. 기본 Singleton
  2. 이른 초기화(Eager Initialization)
  3. synchronized 사용
  4. static inner SIngleton(권장하는 방법)
  5. enum SIngleton(권장하는 방법)

 

1. 기본 Singleton

 - 단점이 Thread Safe 하지 못하다.

 

public class SingletonStatic {

    private static SingletonStatic instance;

    private SingletonStatic() {}

    public static SingletonStatic getInstance() {
        if(instance == null) {
            instance = new SingletonStatic();
        }
        
        return instance;
    }

}

 

 

2. Eager Initialization

 - Thread Safe

 - 단점이 인스턴스를 미리 만들어 놓는다는 단점이 있다.

 

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

}

 

3. Thread Safe Singleton(synchronized 사용)

  - synchronized를 통해서 여러 쓰레드에서 동시에 접근하는 것을 막을 수 있다.(Thread Safe)

  - 동기화 처리하는 작업 때문에 성능에 불이익이 생길 수 있다.

 

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton() {}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) { instance = new ThreadSafeSingleton();}
        return instance;
    }
    
}

 

4. static inner SIngleton(권장하는 방법)

  - SingletonHelper 클래스는 SIngleton 클래스가 Load 될 때에도 Load 되지 않다가 getInstance()를 호출할 때 jvm 메모리에 로드되어 인스턴스를 생성한다. synchronized를 사용하지 않기 때문에 synchronized 키워드 자체에 대한 성능 저하 문제를 해결할 수 있다.

  - 단점으로 자바 리플렉션을 이용하면 싱글톤 패턴이 깨진다.

 

public class BillPughSIngleton {

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
    
}

 

객체 상태를 유지했을 경우 주의 (stateful)

BillPughSIngleton.java

 

public class BillPughSIngleton {

    private String apartment;

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }

    public String getApartment() {
        return apartment;
    }

    public void setApartment(String apartment) {
        this.apartment = apartment;
    }

}

 

SIngletonTest.java

 

    @Test
    void singleTonStatefulTest() {

        BillPughSIngleton instance1 = BillPughSIngleton.getInstance();
        BillPughSIngleton instance2 = BillPughSIngleton.getInstance();

        instance1.setApartment("아크로빌 아파트");
        instance2.setApartment("한남더힐 아파트");

        System.out.println(instance1.getApartment());
        System.out.println(instance2.getApartment());


    }

  - stateful하게 코드를 작성하게 되면 위와 같이 객체의 상태가 바뀌는 문제가 발생하게 된다.

무결성하게(stateless) 코드를 작성하여야 한다.

 

BillPughSIngleton.java

  - setApartment 값을 바로 return 한다.

 

public class BillPughSIngleton {

    private String apartment;

    private BillPughSIngleton(){}

    private static class SingletonHelper{
        private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
    }

    public static BillPughSIngleton getInstance(){
        return SingletonHelper.INSTANCE;
    }

    public String getApartment() {
        return apartment;
    }

    public String setApartment(String apartment) {
        this.apartment = apartment;
        return apartment;
    }

}

 

SIngletonTest.java

 

   @Test
    void singleTonStatefulTest() {

        BillPughSIngleton instance1 = BillPughSIngleton.getInstance();
        BillPughSIngleton instance2 = BillPughSIngleton.getInstance();

        String apartment1 = instance1.setApartment("아크로빌 아파트");
        String apartment2 = instance2.setApartment("한남더힐 아파트");

        System.out.println(apartment1);
        System.out.println(apartment2);
        
    }

 

5. enum Singleton(권장하는 방법)

 - 4번(static inner Singleton)과 같이 권장하는 방법으로 enum 클래스로 사용하는 방법이다.

 - 자바 리플랙션을 이용해도 enum 클래스는 newInstance를 이용해서 새로운 인스턴스를 생성할 수 없어서 더 안전하다.

 - 단점으로는 상속을 사용하지 못하고, 사용하지 않아도 미리 만들어 놓는다는 단점이 있다.  

 

public enum ThreadSafeV5 {

    INSTANCE;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

실무 사용 사례

1. 자바 java.lang.Runtime 클래스가 싱글톤 패턴으로 구현되어 있다.

2. java Application 개발에서 많이 사용되는 스프링 프레임워크는 스프링 빈 컨테이너의 도움을 받아 싱글톤 스코프로 관리된다.

스프링 프레임워크를 사용하면 싱글톤 패턴의 문제점들을 보완하면서 장점을 누릴수 있다.  

3. 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.

 

728x90
반응형
반응형

빌더패턴(Builder Pattern)

  - 빌더 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

  - 별도의 Builder 클래스를 생성해서 필수 값에 대해서는 생성자를 통해, 선택적인 값들에 대해서는 메소드를 통해 값을 입력받은 후에 build() 메서드를 통해 최종적으로 하나의 인스턴스를 리턴하는 방식이다.

 

장점

  - 필요한 데이터만 설정할 수 있다.

  - 유연성을 확보할 수 있다.

  - 가독성을 높일 수 있다.

  - 변경 가능성을 최소화할 수 있다.

 

코드

House.java

 

public class House {

    private Long houseId;
    private String address;
    private String type;
    private Long price;
    private String phone;

    public Long getHouseId() {
        return houseId;
    }
    public String getAddress() {
        return address;
    }
    public String getType() {
        return type;
    }
    public Long getPrice() {
        return price;
    }
    public String getPhone() {
        return phone;
    }

    private House(HouseBuilder builder) {
        this.houseId = builder.houseId;
        this.address = builder.address;
        this.type = builder.type;
        this.price = builder.price;
        this.phone = builder.phone;
    }

    public static class HouseBuilder{

        private Long houseId;
        private String address;
        private String type;
        private Long price;
        private String phone;

	// 필수값 생성자
        public HouseBuilder(Long houseId, String address, String type) {
            this.houseId = houseId;
            this.address = address;
            this.type = type;
        }

        public HouseBuilder price(Long price) {
            this.price = price;
            return this;
        }

        public HouseBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public House build() {
            return new House(this);
        }

    }


}

HouseTest.java

@SpringBootTest
class HouseTest {

    @Test
    void HouseBuilderTest() {

        String houseAddress = "서울특별시 관악구";
        String houseType = "매매";
        Long housePrice = 10000000L;

        House house =  new House.HouseBuilder(1L, houseAddress, houseType)
                                .price(housePrice)
                                .build();

        assertThat(house.getAddress(), is(houseAddress));
        assertThat(house.getType(), is(houseType));
        assertThat(house.getPrice(), is(housePrice));

    }


}

 

실무 사용 사례

- java에 StringBuilder

- java에 Stream API

- Spring Lombok

- Spring UriComponents

 

정리

 - 인스턴스를 생성할 때 생성자만을 통해서 생성하면 칼럼이 변경될 때마다 많은 소스코드에 영향이 가는 고통스러운 상황이 발생할 것이다. 빌더 패턴을 이용해서 유연성을 확보할 수 있다.

 

참조

 - https://readystory.tistory.com/121

 

 

728x90
반응형
반응형

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

  - 팩토리 메소드 패턴은 생성패턴(Creational Pattern) 중 하나이다.
  - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다.
  - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.

 

장점

  - 확장에 열려있고 변경에 닫혀있는 객체 지향 원칙을 적용해서 기존에 인스턴스를 만드는 과정의 로직을 건드리지 않고, 새로운 인스턴스를 다은 방법으로 확장이 가능하다.

  - 팩토리 메소드 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.

  - 팩토리 메소드 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.

 

단점

  - 각자의 역할을 나누어서 생성하다 보니 클래스 개수가 늘어난다.

코드

  * 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)

 

  - 중개 정책 interface 생성 :

     interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.

     java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)

     java9 이후로는 interface에 priave 메소드도 사용이 가능하다.

public interface BrokeragePolicy {

    BrokerageRule createBrokerageRule(Long price);

     default Long calculate(Long price) {
        BrokerageRule rule = createBrokerageRule(price);
        return rule.calcMaxBrokerage(price);
    }
}

 

  - 매매 중개 수수료 class :

    java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상

 

public class PurchaseBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.6, 250_000L);
        } else if(price < 200_000_000) {
            rule = new BrokerageRule(0.5, 800_000L);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else if(price < 900_000_000) {
            rule = new BrokerageRule(0.5, null);
        }else{
            rule = new BrokerageRule(0.9, null);
        }
        return rule;
    }


}

 

  - 임대차 중개 수수료 class

 

public class RentBrokeragePolicy implements BrokeragePolicy{

    public BrokerageRule createBrokerageRule(Long price) {
        BrokerageRule rule;
        if(price < 50_000_000) {
            rule = new BrokerageRule(0.5, 200_000L);
        } else if(price < 100_000_000) {
            rule = new BrokerageRule(0.4, 300_000L);
        }else if(price < 300_000_000) {
            rule = new BrokerageRule(0.3, null);
        }else if(price < 600_000_000) {
            rule = new BrokerageRule(0.4, null);
        }else{
            rule = new BrokerageRule(0.8, null);
        }
        return rule;
    }


}

 

  - Factory Class (핵심 클래스)

 

public class BrokeragePolicyFactory {

    public static BrokeragePolicy of(ActionType actionType) {
        switch (actionType) {
            case RENT:
                return new RentBrokeragePolicy();
            case PURCHASE:
                return new PurchaseBrokeragePolicy();
            default:
                throw new IllegalArgumentException("해당 actionType에 대한 정책이 존재하지 않습니다.");
        }
    }

}

 

- 중개 수수료 계산 api

 

public Long calcBrokerage(@RequestParam ActionType actionType,
                          @RequestParam Long price) {
        // 타입 정의 - 매매 / 임대차
        BrokeragePolicy policy = BrokeragePolicyFactory.of(actionType);
        return policy.calculate(price);
}

 

실무 사용 사례

- Spring에서 BeanFactory

- Java Calendar 

728x90
반응형

'JAVA > Design Pettern' 카테고리의 다른 글

[Design Pattern] 프록시 패턴  (0) 2022.03.22
[Design Pattern] 추상 팩토리 패턴  (0) 2022.03.19
[Design Pattern] 어댑터 패턴  (0) 2022.03.16
[Design Pattern] 싱글톤 패턴  (0) 2022.03.15
[Design Pattern] 빌더 패턴  (0) 2022.03.14

+ Recent posts