자바 정규 표현식 문법

https://hbase.tistory.com/160

 

[Java] 정규표현식 사용법 및 예제 - Pattern, Matcher

자바에서 정규표현식(Regular Expression)'을 사용해보자. 1. 정규표현식(Regular Expression) 정규표현식 혹은 정규식은 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용되는 언어다. 정규 표현식은

hbase.tistory.com

https://coding-factory.tistory.com/529

 

[Java] 자바 정규 표현식 (Pattern, Matcher) 사용법 & 예제

정규표현식(Regular Expression)이란 컴퓨터 과학의 정규언어로부터 유래한 것으로 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 쓰이는 형식언어 입니다. 개발을 하다보면 전화번호, 주민등

coding-factory.tistory.com

 

정규 표현식 예제

https://www.wrapuppro.com/programing/view/MIw5kPB3ao2YJVx

 

[Javascript] 비밀번호 정규식 - 8자 이상의 문자열, 하나 이상 숫자, 문자, 특수문자 포함하기 : WRAPUP

웹취약점과 관련하여 비밀번호 변경 규칙을 지정하는 것은 필수입니다.사용자 패스워드 변경에 대한 규칙이 없으면 계정관리에 취약점이 생겨 해킹에 위험성이 높아집니다.또한, 이전에 사용

www.wrapuppro.com

 

정규 표현식 테스트 사이트

https://coding-factory.tistory.com/819

 

[Web] 정규표현식 테스트 사이트 모음 총정리

정규표현식을 사용하다 보면 이 문법이 맞는지 아닌지 헷갈리는 경우가 있습니다. 또 언어마다 정규표현식이 조금씩 다른 부분도 있어 테스트를 많이 해야 합니다. 개발을 할 때 자체 테스트로

coding-factory.tistory.com

 

|  Enum이란?

- Enumeration Type : 열거체를 말한다.

- 핵심 : 사용자 지정 타입이며 실제 값은 0,1,2,3... 순번으로 출력된다.

- 부가 : 타입에 괄호()를 넣어 그 타입에 대해 설명할 수 있다.

 

|  Enum 문법

- 기본적으로 아래와 같이 열거하여 사용하는데

public enum AnimalType {
    CAT, DOG, HAMSTER, CHICKEN, SHEEP, LAMAR;
}

- 상수타입 옆에 괄호()를 넣어 상숫값을 명시할 수 있다.

public enum AnimalType {

    CAT("포유류","고양이"),
    DOG("포유류","개"),
    HAMSTER("포유류","햄스터"),
    CHICKEN("조류","닭"),
    SHEEP("포유류","양"),
    LAMAR("포유류","라마");

    private String species;
    private String kind;

    AnimalType(String species, String kind) {
        this.species = species;
        this.kind = kind;
    }

    public String getSpecies() {
        return species;
    }

    public String getKind() {
        return kind;
    }
}

- Main에서 테스트한 결과

import type.AnimalType;

import java.util.Optional;

public class Main2 {

    static class Patient {

        private Long id;
        private String patientNo;
        private AnimalType animalType;
        private String name;

        public Patient(Long id, String patientNo,
                       AnimalType animalType, String name) {
            this.id = id;
            this.patientNo = patientNo;
            this.animalType = animalType;
            this.name = name;
        }

        @Override
        public String toString() {
            return id + ".환자번호(" + patientNo + ") : "
                    + name + " (" + animalType.getSpecies()
                    + " " + animalType.getKind() + ")";
        }
    }

    public static void main(String[] args) {
        Patient patient = new Patient(1L, "1000101",
                AnimalType.CAT, "봄");

        Optional<Patient> patient2 = Optional.ofNullable(patient);
        System.out.println(patient2); // Optional[1.환자번호(1000101) : 봄 (포유류 고양이)]
    }
}

 

[ 참조 및 출처 ]

http://www.tcpschool.com/java/java_api_enum

'Language > Java' 카테고리의 다른 글

[이펙티브 자바] 객체의 파괴  (0) 2022.10.03
[이펙티브 자바] 객체의 생성  (0) 2022.09.29
JAVA 라이브러리 - Optional<T> 클래스  (0) 2022.09.15
SOLID 원칙  (0) 2022.08.29
JAVA 라이브러리 - 컬렉션  (0) 2022.08.11

|  Optional<T> 클래스란?

- 자바에서 모든 객체는 Referece Type으로 nullable하다. * null이 발생할 수 있는 타입

  > Referece Type의 객체는 null을 발생시킬 수 있다. -- NullPointerException

- Optional<T>은 객체를 nullable하게 쓸 수 있도록 하는 Wrapper class로, 객체 사용 시 null을 명시적으로 처리하게 한다.

  * 참고로 코틀린의 경우, nullable한 타입을 구분하는 코드가 별도로 존재한다.

Optional은 주로 "결과 없음"을 명확하게 나타내야 하고, 
null을 사용하면 오류가 발생할 수 있는 메소드 반환 유형으로 사용된다. 
Optional 변수는 null일 수 없으며 항상 Optional 인스턴스를 가리켜야한다. -- Oracle Docs

- 장점 : null을 직접 핸들링하지 않음, null 여부를 타입만으로 나타낼 수 있음, 체이닝을 통한 중간 및 종단 처리

  Optional<T> Optional.empty()
  T null
더보기

- Optional의 생성자를 보면, 아래와 같이 Objects.requireNonNull()을 통해

  감싸려는 객체 데이터가 null일 때, NullPointException을 발생시키도록 한다.

- 따라서 Optional로 감싼다고 하여도 NullPointException이 발생하지 않는 것은 아니다.

private Optional() {
    this.value = null;
}

private Optional(T value) {
    this.value = Objects.requireNonNull(value); // null이면 NullPointException 발생
}
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

 

 

 

|  Optional 객체의 맴버 변수와 생성자

private static final Optional<?> EMPTY = new Optional<>();

private final T value;

private Optional() {
    this.value = null;
}

 

|  Optional 객체의 메소드

isPresent(): boolean null인지 아닌지 여부를 boolean으로 반환
of() 데이터를 Optional<T>로 감싼다.
ofNullable() of()와 동일하나 null이 발생할 때 처리 **null 발생 시 꼭 이 메소드 사용
get() null이 아닌 경우, 해당값 반환 **null일 때 예외 발생시키므로, 사용 시 주의요함
orElse(<T>) null일 경우, 특정 데이터를 반환
orElseGet(() -> getMethod()) null일 경우, 함수형 메소드 실행
orElseThrow(()->new Exception()) null일 경우, 지정한 예외 발생
filter() 조건식에 따라 필터링
map() 조건식에 따라 데이터를 매핑
flatMap() map과 동일하나 null처리가 다름

 

(1) of()와 ofNullable()의 차이

- ofNullable()의 경우 null이 발생할 때 empty() 메소드를 사용하는데, empty() 메소드를 쓰면 Empty 맴버 변수를 반환한다.

private static final Optional<?> EMPTY = new Optional<>();

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

(2) isPresent(), ifPresent() 

public boolean isPresent() {
    return value != null;
}

public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

(3) get(), orElse(), orElseGet(), orElseThrow()

- get()은 사용을 지양하는 편이 좋다고 한다.* 감싼 데이터값이 null일 때, NoSuchElementException이 발생

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

public T orElse(T other) {
    return value != null ? value : other;
}


public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

- orElse()과 orElseGet()의 차이점은 인자로 함수(ex. getRandomNumber())를 전달할 때 

  > orElse()의 경우 null이 발생하지 않아도 함수를 실행한 후 결과값을 orElse(//결과값//)에 전달하는 한 편,

  > orElseGet()을 쓰면 Supplier라고 하는 함수형 인터페이스를 통해

                                    null이 발생할 때에만 함수를 실행한뒤 결과값을 전달한다.

// 가정: optionalUser.findById(userId)는 null X

// getRandomUser() 실행 --> 결과값이 orElse()안에 담김
optionalUser.findById(userId).orElse(getRandomUser());

// getRandomUser() 실행 X
optionalUser.findById(userId).orElseGet(() -> getRandomUser());

- orElseThrow()는 null 발생 시, 지정한 Exception을 반환하게 하는 메소드이다.

(4) filter(), map(), flatMap()

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

 

|  언제쓸까? - 값이 없을 수도 있을 때

(1) DB를 조회할 때

// JPA을 통해 Optional<T> 타입의 반환값이 null일 경우 처리하는 법
Optional<User> user = userRepository.findById(userId)
                                    .orElseThrow(() -> new Exception());

String userName = userRepository.findUserNameById(userId)
				.map(user -> user.getUserName())
				.orElse("이름 없음");

(2) 조회한 객체에 특정 칼럼이 비어있을 때

@Entity(name = "user")
public class User{
    @Id
    @GeneratedValue
    private Long id;
    
    private String userId;
    private String password;
    
    private String thumbnail;
    
    public Optional<String> getThumbnail(){
    	return Optional.ofNuallable(thumbnail);
    }
    
}

(3) 기존 시스템의 리턴 값이 null을 리턴할 때

Optional.ofNuallable(userRepository.findByName(userName))
		.ifPresent(user -> list.add(user));

(4) 필드에 Optional은 지양하기

- Optional은 함수 반환을 목적으로 만들어졌다.

- Serializable을 구현하지 않아 직렬화를 하면 이상한 값이 나온다.

[직렬화란]

메모리는 가상 메모리와 물리 메모리로 나뉘어져 있다.
가상 주소는 논리적인 주소일 뿐이지, 실제 저장되는 위치는 다를 수 있다.
객체를 그대로 저장하게 되면, 가상 주소까지 저장되게 되는데, 메모리 주소는 다른 시스템에 전달해도 의미가 없다.
따라서 불필요한 정보는 제외하고 타입 정보, 값 정보를 Byte형태로 만들어 보내는데 이를 직렬화라 한다.
직렬화를 하려면 클래스가 serializable를 구현해야한다.

- Optional.empty()를 빠뜨릴 확률이 높다. (휴먼 에러)

 

|  Optional을 제대로 쓰기

(1) 비어있을 컬렉션을 표현할 땐 Collections.emptyList()를 사용한다.

Optional<List<User>> findAll();  // !!사용하지 말아야 한다.

Collections.emptyList(); // Collections의 emptyList()를 사용한다.

- 컬렉션 요소에 Optional은 절대 금지

// 사용자가 Optional을 매번 판단해야하는 상황발생
List<Optional<User>> findAll();  // !!사용하지 말아야 한다. 

// ----사용시
for (Optional<User> opt : list) {

    // 매번 optional을 객체로 변환
    User user = opt.get(); // 불필요하다.

}

// User타입 그대로 반환
List<User> findAll();

(2) Optional을 파라미터로 넘기지 않는다.

(3) Optional 값을 가져올 때 .get()은 되도록 지양한다.

- get()을 쓰면, null일 때, NoSuchElementException을 발생시킨다.

- optional의 중단, 종단 메소드를 체이닝하여 사용하는게 낫다.

(4) Optional의 중간, 종단 메소드를 쓰기 위해 불필요한 옵셔널을 하지 않는다.

 

 

[ 참고 및 출처 ]

부트 캠프 수업을 들은 후 정리

https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/

http://www.tcpschool.com/java/java_stream_optional

orElse()와 orElseGet()의 차이 : https://kdhyo98.tistory.com/40

'Language > Java' 카테고리의 다른 글

[이펙티브 자바] 객체의 생성  (0) 2022.09.29
JAVA 유용한 타입 - Enum  (0) 2022.09.15
SOLID 원칙  (0) 2022.08.29
JAVA 라이브러리 - 컬렉션  (0) 2022.08.11
JAVA 라이브러리 - 제네릭 클래스  (0) 2022.08.10

|  OOP의 핵심

[ 지난 번 OOP 관련 포스팅 내용 요약 ]
- OOP는 객체 간 상호 관계에 포커스를 맞춘 방법론으로, 추상화, 상속, 다형성, 캡슐화 4가지의 주요 특징이 있다.
- 추상화는 모델링이고, 상속은 확장과 재사용성이며, 다형성은 사용 편의, 캡슐화는 정보 은닉에 해당된다.
- 추상화 단계에서는 클래스 내 응집도를 높이는 게 필요하고, 상속 시에는 클래스 간 결합도에 주의해야 한다.

- OOP는 풀면 참 복잡했는데, 오늘 배운 수업에서는 분류교체라는 단어로 그 핵심을 단순화해 설명했다.

* 현업에 오래 종사했던 강사분은 스파게티 소스에 대해 언급하며,

  소프트웨어를 유연하게 만들려면 객체지향 방식이 유용하다 얘기주셨다.

분류한다 코드를 적절히 잘 분류한다. -> 클래스
교체한다 필요에 따라서 특정 모듈을 통째로 교체하기도 한다. -> DBMS 업체나 라이브러리를 통째로 교체하기도 한다.

※ 라이브러리와 프레임워크와 아키텍처 간의 상관관계 바로가기

 

|  SOLID 원칙

- 로버트 C. 마틴에 의해 만들어진 원칙

SRP 단일 책임 원칙(분류)    Simple Responsibility principle
OCP 개방 폐쇄 원칙(교체) Open/Closed principle
LSP 리스코프 치환 법칙(교체) Liskov subsituation principle
ISP 인터페이스 분리 원칙(분류) Interface segregation principle
DIP 의존성 역전 원칙(교체) Dependency inversion principle

 

※ 참조 : 실제 현장에서 사용하는 코드는 아래와 달라 보입니다. 원리를 이해하기 위한 코드로 봐주시면 감사하겠습니다.

 

1. SRP : 한 클래스는 단일의 책임만 가져야 한다.

 

SRP가 지켜지지 않은 코드 

class PaymentService{
    public void pay(Customer customer, Product product) {

        if (!isValidatedPrice(customer, product)) {
            throw new IllegalArgumentException("charge your money");
        }

        customer.setMoney(customer.getMoney() - product.getPrice());
    }

    public boolean isValidatedPrice(Customer customer, Product product) {
        if (customer.getMoney() < product.getPrice()) {
            return false;
        }

        return true;
    }

    public void sendSMS(Customer customer, Product product) {
        System.out.println("phone number : " + customer.getPhone());
        System.out.println("상품 " + product.getName() + "(" 
                          + product.getProduct_no() + ")이 결제되었습니다.");
    }
}

- 고객이 상품을 주문할 때, PaymentService라는 클래스에서 아래와 같이 세가지의 기능을 제공한다고 할 때에

  (1) 결제하기 - pay()

  (2) 결제가능 여부 확인하기 - isValidatedPrice()

  (3) 결제완료 SMS(메세지) 전송하기 - sendTMS()

- 이 세가지 기능은 엄밀히 말하면 서로 다른 기능이라 볼 수 있다. 

- 전체 scope이 작은 경우에는 세가지 기능이 묶일 수도 있겠지만, 만약 기능이 더 복잡해지고 확장된다면 세가지를 분리할 수 있다.

 

클래스를 분리

class PaymentService{
    public void pay(Customer customer, Product product) {

        if (!PaymentValidation.isValidatedPrice(customer, product)) {
            throw new IllegalArgumentException("charge your money");
        }

        customer.setMoney(customer.getMoney() - product.getPrice());
    }
}
class PaymentValidation{
    public static boolean isValidatedPrice(Customer customer, Product product) {
        if (customer.getMoney() < product.getPrice()) {
            return false;
        }

        return true;
    }
}
class SMSService{
    public void sendSMS(Customer customer, Product product) {
        System.out.println("phone number : " + customer.getPhone());
        System.out.println("상품 " + product.getName() + "(" 
                           + product.getProduct_no() + ")이 결제되었습니다.");
    }
}

 

2. OCP : 확장에는 열려있고, 변경에는 닫혀있다.

* if-else의 반복적인 케이스가 보일 때에는 클래스를 분리하는 것이 효과적일 수 있다.

 

OCP가 지켜지지 않은 코드

class PointService {
    public double getPointRate(String category) {
        // 구매 카테고리별로 포인트 비율을 다르게 지급
        if (category.equals("옷")){
            return 0.05;
        } else if (category.equals("잡화")) {
            return 0.02;
        } else if (category.equals("기타")) {
            return 0.01;
        }

        return 0;
    }

    public double getVIPRate(String category){
        // 구매 카테고리 별로 VIP 여부에 따라서 비율을 다르게 지급
        if (category.equals("잡화")){
            return 0.12;
        } else if (category.equals("도서")) {
            return 0.08;
        } else if (category.equals("기타")) {
            return 0.01;
        }

        return 0;
    }
}

- 만약에 포인트 제도가 추가되어, 구매 카테고리 별로 서로 다르게 포인트를 지정한다고 하자.

- 이럴때 카테고리가 위와 같이 한정적이라면 상관이 없겠지만 카테고리가 계속 늘어난다면 문제가 발생할 수 있다.

- 더불어 getPoint()와 getVIPRate()의 카테고리명과 순서가 다른 걸 볼 수 있다.

- 이런 경우 로직이 통일적이지 않아지기 때문에 결과적으로 문제가 발생할 수 있다.

 

인터페이스를 통한 OCP 확보

interface PointServiceIF{
    public abstract double getPointRate(String category);
    public abstract double getVIPRate(String category);
}

class Clothe implements PointServiceIF{

    @Override
    public double getPointRate(String category) {
        return 0.05;
    }

    @Override
    public double getVIPRate(String category) {
        return 0.00;
    }
}

class Book implements PointServiceIF{

    @Override
    public double getPointRate(String category) {
        return 0.12;
    }

    @Override
    public double getVIPRate(String category) {
        return 0.25;
    }
}

- 인터페이스를 통해서 메소드를 프로토타입으로 만들어 준 후에, 각각의 카테고리를 클래스로 만들어 메소드를 구현하면 OCP를 확보할 수 있다.

 

3. LSP : 서브타입은 언제나 기반타입으로 교체할 수 있어야 한다.

- 이 부분은 상위 클래스(또는 인터페이스)의 기능과 동일한 기능을 하위 클래스가 상속(또는 구현)하는 가를 말한다.

상속 is kind of  하위 클래스는 상위 클래스의 한 분류이다.
구현 is able to 하위 클래스는 상위 인터페이스를 구현할 수 있다.

- 여기서는 예제를 만들지 않고 대신 예제 링크만 가져왔다.

https://blog.itcode.dev/posts/2021/08/15/liskov-subsitution-principle

- 위 예제에서는, 직사각형 > 정사각형으로 LSP를 설명하는데, 정사각형은 직사각형의 메소드를 모두 동일하게 가져가지 않기 때문에, 사각형 클래스를 만들고 직사각형과 정사각형이 그것을 상속받게 했다.

더보기

- 실무에서는 상속을 많이 사용하지 않는다고 한다.

왜냐하면, 오버라이딩을 했는지 안 했는지 매번 확인하기가 어렵고,

오버라이딩 잘못하면 로직 충돌 (Fragile base class문제)이 일어나기도 하며, 

기능을 너무 확장하거나 변경하면 재활용성이 낮아지기 때문이라고 한다.

 

- 그래서 상속의 대안 및 상속을 잘하기 위해서는

(1) 상위 클래스를 한 클래스만 상속하거나

(2) 클래스 상속이 아닌, 인터페이스로 대체하거나

(3) 상위 클래스와 상호 치환(=동일 기능을 갖도록)이 가능하도록 상속을 해야한다. 

 

4. ISP : 인터페이스도 단일 책임을 갖도록 분리해야 한다.

* 인터페이스를 너무 크게 만들면, 사용하지 않는 메소드들도 구현하도록 해야 한다.

* 실무에서 보면 단일 메소드를 가진 인터페이스들이 있다. > 세분화된 인터페이스로 쪼개서 단일 책임을 갖게 한 것

 

- 이 부분은 별도로 내용을 추가하지 않고, 바로 앞의 예시로 이야기하겠다.

interface PointServiceIF{
    public abstract double getPointRate(String category);
    public abstract double getVIPRate(String category);
}

class Clothe implements PointServiceIF{

    @Override
    public double getPointRate(String category) {
        return 0.05;
    }

    @Override
    public double getVIPRate(String category) {
        return 0.00;
    }
}

class Book implements PointServiceIF{

    @Override
    public double getPointRate(String category) {
        return 0.12;
    }

    @Override
    public double getVIPRate(String category) {
        return 0.25;
    }
}

- 바로 위의 코드를 보면 "옷"의 경우 안 쓰는 메소드인 getVIPRate()를 구태어 어거지로 구현한 걸 볼 수 있는데,

  이유는 인터페이스 안에 메소드를 두 개를 넣어 두어서 두 가지를 모두 구현해야 하기 때문이다.

- 이렇듯 상속은 주의해서 사용하는 게 좋다. 필요하지 않은 메소드까지 구현해야할 수 있고,

  너무 많이 확장되고 재사용되다보면 최초에 목적하고 있던 기능에서 많이 동떨어진 메소드가 만들어질 수 있기 때문이다.

- 이러한 문제를 해결하기 위해서는 단일 인터페이스, 곧 단일 메소드를 가진 인터페이스를 만드는 것이 좋다.

interface GeneralPointServiceIF{
	public abstract double getPointRate(String category);
}

interface VIPPointServiceIF{
	public abstract double getVIPRate(String category);
}

class Clothe implements GeneralPointServiceIF{
    @Override
    public double getPointRate(String category) {
        return 0.05;
    }
}

class Book implements GeneralPointServiceIF, VIPPointServiceIF{
    @Override
    public double getPointRate(String category) {
        return 0.12;
    }

    @Override
    public double getVIPRate(String category) {
        return 0.25;
    }
}

 

5. DIP : 하위 모듈의 변경이 상위 모듈의 변경을 요구하는 의존성을 끊어내야 한다.

- 개발을 하다보면 보안상의 이슈가 발생한 라이브러리를 다른 라이브러리로 교체해야할 때가 있다고 한다.

- 또는 외주업체에 맡겨 서비스를 추가하는 경우도 있는데, 업체를 변경할 때에도 기존 서비스를 변경해야할 때가 있다고 한다.

- 만약 호텔에 Deposit 금액으로 20만원을 넣어두고, 호텔의 서비스를 사용할 때마다 deposit 금액을 차감시킨다고 하자.

[1] 호텔 비용 정산 클래스 - HotelPaymentService

[2] 고객 deposit에 연결하는 클래스 - HotelDepositAdapter

class HotelPaymentService{
    String pay(Customer customer, ServiceRequest serviceRequest){
    	HotelDepositAdapter hotelDepositAdapter = new HotelDepositAdapter();
        int payResult = hotelDepositAdapter.useService(customer, serviceRequest);
    	
        if (payResult == -1){
        	return "Fail";
        }
        
        return "Success : left deposit - " + payResult + "(원)"; 
    }
}

class HotelDepositAdapter{  
    int useService(Customer customer, ServiceRequest serviceRequest){
    	int possibleDeposit = customer.getDeposit() - serviceRequest.getServicePrice();
        
        if (possibleDeposit < 0) {
        	return -1;
        }
        
        return possibleDeposit;
    }
}

- 이 상태에서, 만약 호텔측에서 Deposit 금액 외에 카드를 맡기면 추가적으로 결제가 가능하게 해달라는 요청을 한다면

[1] 호텔 비용 정산 클래스 - HotelPaymentService

[2] 고객 deposit에 연결하는 클래스 - HotelDepositAdapter

[3] 고객 card에 연결하는 클래스 - HotelCardAdapter

- 위와 같이 card를 연결하는 어뎁터 클래스를 하나 더 추가하게 될 수 있다.

- 이런 경우 HotelPaymentService이 이미 HotelDepositAdapter에 의존적이라 코드를 전반적으로 수정해주어야 한다.

- 앞으로 비용 정산 방식이 추가적으로 바뀌게 되면 이 방식은 비효율적이다

class HotelPaymentService{
    String pay(Customer customer, ServiceRequest serviceRequest, PaymentType type){
    	
        if (type.getType() < 0 || type.getType() >= 2) {
        	return "PaymentRequest Error : write correct PaymentType";
        }
        
        int payResult = -1;
        
        if (type.getType() == 0){
        	HotelDepositAdapter hotelDepositAdapter = new HotelDepositAdapter();
        	payResult = hotelDepositAdapter.useService(customer, serviceRequest);
        } else if (type.getType() == 1){
        	HotelCardAdapter hotelCardAdapter = new HotelCardAdapter();
        	payResult = hotelCardAdapter.useService(customer, serviceRequest);
        } 
    	
        if (payResult == -1){
        	return "Fail";
        }
        
        return "Success : left deposit - " + payResult + "(원)"; 
    }
}

- 이런 경우 아래와 같이 어뎁터 자체가 인터페이스를 구현하도록 하고,

  HotelPaymentService가 인터페이스를 바라보도록 하면 해결된다.

enum HotelPaymentType{
    DEPOSIT, CARD
}

class HotelPaymentService{

    private final HotelDepositAdapter hotelDepositAdapter = new HotelDepositAdapter();
    private final HotelCardAdapter hotelCardAdapter = new HotelCardAdapter();

    String pay(Customer customer, ServiceRequest serviceRequest){
    
    	HotelPaymentIF hotelPaymentIF;
        
        if (serviceRequest.getPaymentType() == HotelPaymentType.DEPOSIT){
        	hotelPaymentIF = hotelDepositAdapter;
        } else {
        	hotelPaymentIF = hotelCardAdapter;
        }
    
    	int payResult = hotelPaymentIF.useService(customer, serviceRequest);
    	
        if (payResult == -1){
        	return "Fail";
        }
        
        return "Success : left deposit - " + payResult + "(원)"; 
    }
}

interface HotelPaymentIF{
	int useService(Customer customer, ServiceRequest serviceRequest);
}

class HotelDepositAdapter implements HotelPaymentIF{  
    int useService(Customer customer, ServiceRequest serviceRequest){
    	int possibleDeposit = customer.getDeposit() - serviceRequest.getServicePrice();
        
        if (possibleDeposit < 0) {
        	return -1;
        }
        
        return possibleDeposit;
    }
}

class HotelCardAdapter implements HotelPaymentIF{  
    int useService(Customer customer, ServiceRequest serviceRequest){
    	if (CardService.useCard(customer, serviceRequest) < 0) {
        	return -1;
        }
        
        return serviceRequest.getServicePrice();
    }
}

 

 

[ 출처 및 참조 ]

부트캠프 강의를 들은 후 정리한 내용입니다.

[스프링 입문을 위한 자바 객체지향의 원리와 이해]

SOLID https://bottom-to-top.tistory.com/27

위키백과 https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84) 

 

+ Recent posts