반응형

클래스 잘 설계하기

 

목차

1. 캡슐화되어야 한다

2. 단일 책임 원칙

3. 낮은 결합도, 높은 응집도

4. 변경하기 쉬워야 한다


1. 캡슐화되어야 한다

캡슐화 : 객체의 실제 구현을 외부로부터 감추는 방식

 

2. 단일 책임 원칙

클래스는 작아야 한다.

클래스가 맡은 책임이 한 개인가

  - 함수와 마찬가지로 클래스도 작아야 한다.

  - 함수는 라인 수로 크기를 측정했는데, 클래스는 맡은 책임의 수로 크기를 측정한다.

  - 클래스 설명은 단일(if), 그리고(and), 하며(or), 하지만(but)을 사용하지 않고 25단어 내외로 가능해야 한다.

     -> 책임이 한 가지여야 한다.

 

단일 책임 원칙(SRP) 중요성

  - 자잘한 단일 클래스가 많아지면 큰 그림을 이해하기 어렵다고 우려한다. 하지만 작은 클래스가 많은 시스템이든 큰 클래스가 몇 개뿐인 시스템이든 돌아가는 부품은 그 수가 비슷하다.

  - 큼직한 다목적 클래스 몇 개로 이뤄진 시스템은(변경을 가할 때) 당장 알 필요가 없는 사실까지 들이밀어 독자를 방해한다.

  - 작은 클래스는 각자 맡은 책임은 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다.

 

3. 낮은 결합도, 높은 응집도

 * 결합도는 낮을수록 응집도는 높을수록 유지보수성이 좋다.

 

문제점 

결합도가 높은 클래스의 문제점 :

  - 연관된 클래스가 변경되면 수정이 필요하다.

  - 결헙도가 높으면 연관된 클래스들을 모두 이해해야 한다.

응집도가 낮은 클래스의 문제점 :

  - 여러 기능이 있으므로 이해하기 어렵다.

  - 재사용하기 어렵다.

 

 

4. 변경하기 쉬워야 한다

  - 추상화를 잘 활용해야 한다.

  - 기존의 클래스는 건들지 않고 수정되어야 한다. (OCP 원칙 위반되지 않게)

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 12  (0) 2022.03.28
[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 09  (0) 2022.03.21
[Clean Code] Chapter 08  (0) 2022.03.20
[Clean Code] Chapter 07  (0) 2022.03.17
반응형

커맨드 패턴(Command Pattern)

  - 커맨드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

장점

  - 커맨드 패턴은 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계한다.

  - 실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자 클래스와 실제 기능을 실행하는 수신자 클래스 사이의 의존성을 제거한다.

 

코드

 

  - Command Interface :

    실제 기능을 수행하는 클래스들을 캡슐화 하기위한 Interface

 

public interface Command {

    public String run();

}

 

  - 디지니 구현 Class :

    실제 구현 클래스

 

public class DisneyCommand implements Command {

    @Override
    public String run() {
        return "Disney";
    }
}

 

  - 넷플릭스 구현 Class

 

public class NexflixCommand implements Command{

    @Override
    public String run() {
        return "Netflix";
    }
}

 

  - 호출자 Class

 

public class OttService {

    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public String displayOtt() {
        return command.run();
    }

}

 

  - 커맨더 패턴 Test Class

 

@SpringBootTest
class OttServiceTest {

    @Test
    void CommandPatternTest() {

        Command disneyCommand = new DisneyCommand();
        Command netflixCommand = new NexflixCommand();

        OttService ottService = new OttService();
        ottService.setCommand(disneyCommand);
        assertEquals(ottService.displayOtt(), "Disney");

        ottService.setCommand(netflixCommand);
        assertEquals(ottService.displayOtt(), "Netflix");

    }

}

 

 

정리

어떠한 객체를 공용으로 사용하는데 동작만을 객체마다 분리해서 사용하고 싶을 경우에 적합한 패턴인 것 같다.

1. 공용(위 예제의 OttService.java)으로 사용하고 있는 객체에 command라는 인터페이스를 구현한다.

2. 객체의 동작(DisneyCommand.java, NetflixCommand.java)을 호출하는 클래스에 각각 기능에 맞추어 command를 구현한다.

 

Java의 다형성을 이용해서 Command Interface를 구현한 클래스는 호출자 수정 없이 기능 추가가 가능하다.

이는 OCP(Open Close Principle) 원칙에 위배되지 않게 기능을 추가할 수 있게 하는 패턴이다.

 

참조 

  - https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

728x90
반응형
반응형

책임 사슬 패턴(Chain of Responsibility Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

  - 책임 사슬 패턴은 특정한 객체가 담당하는 일반적인 방법과 다르게 객체를 연결리스트와 같은 사슬 방식으로 연결한 후에 요청을 수행하지 못하는 객체라면 다음 객체로 책임을 넘기는 형태의 패턴이다.

 

장점

  - 여러 클래스간에 걸쳐 이루어지는 일이기 때문에 구조가 다른 클래스에 대해서 낮은 결합도로 동일한 이벤트 핸들링이 가능하다. 

  - 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거 할 수 있다. 상황에 따라 동적으로 핸들러를 추가하거나 제거할 수 있으며, 이러한 변화가 전체구조에 아무런 영향을 주지 않는다는 점에서 객체지향적인 목적을 달성한다고 볼 수 있다.

 

코드

  * 한라봉의 무게에 따라 다른 박스로 분리되는 예시

 

  - 한라봉 Chain Interface :

    책임 사슬 패턴의 핵심 Interface

    setNextChain 메소드를 통해서 다른 체인을 이어준다.

public interface HanrabongChain {

    void setNextChain(HanrabongChain nextChain);
    void weightFilter(int weight);

}

 

  - 한라봉 Class

 

public class Hanrabong {

    private int weight;

    public Hanrabong(int weight) {
        this.weight = weight;
    }

    public int getWeight() {
        return this.weight;
    }
}

 

  - 한라봉 10kg Filter Class

 

public class Hanrabong10k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 10) {
            System.out.println("IN 10kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 5kg Filter Class

 

public class Hanrabong5k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 5) {
            System.out.println("IN 5kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 한라봉 3kg Filter Class

 

public class Hanrabong3k implements HanrabongChain{

    private HanrabongChain hanrabongChain;

    @Override
    public void setNextChain(HanrabongChain nextChain) {
        this.hanrabongChain = nextChain;
    }

    @Override
    public void weightFilter(int weight) {
        if(weight >= 3) {
            System.out.println("IN 3kg box!");
        } else {
            this.hanrabongChain.weightFilter(weight);
        }
    }
}

 

  - 책임 사슬 패턴 Test Class

@SpringBootTest
class HanrabongChainTest {

    @Test
    void hanrabongFilterTest() {

        HanrabongChain hanrabongChain = getHanrabongChain();

        Hanrabong hanrabong5 = new Hanrabong(5);
        Hanrabong hanrabong12 = new Hanrabong(12);
        Hanrabong hanrabong7 = new Hanrabong(7);
        Hanrabong hanrabong4 = new Hanrabong(4);
        Hanrabong hanrabong1 = new Hanrabong(1);

        List<Hanrabong> rabongList = Arrays.asList(hanrabong5, hanrabong12, hanrabong7, hanrabong4, hanrabong1);

        for (Hanrabong hanrabong : rabongList) {
            hanrabongChain.weightFilter(hanrabong.getWeight());
        }


    }
    
    // 다음 객체 연결
    private HanrabongChain getHanrabongChain() {
        HanrabongChain hanrabong10k = new Hanrabong10k();
        HanrabongChain hanrabong5k = new Hanrabong5k();
        HanrabongChain hanrabong3k = new Hanrabong3k();

        hanrabong10k.setNextChain(hanrabong5k);
        hanrabong5k.setNextChain(hanrabong3k);
        return hanrabong10k;
    }

}

 

정리

* 책임 사슬 패턴의 주의점

위 테스트 예제에서 무게가 1kg인 한라봉이 들어온다면 에러가 발생되거나 필터에 걸리지 않는다.

책임 사슬 패턴은 요청이 반드시 수행된다는 보장이 없기 때문에, 체인이 적절하게 구성되어 프로세스가 처리 될 수 있도록 구성하여야 한다. 추가적으로 요청을 처리하는데 걸리는 시간을 예측하기 힘들다는 단점도 있어 시간예측이 중요한 경우 해당 패턴의 적용을 검토해봐야 한다.

 

참조 

  - https://www.nextree.co.kr/p2533/

728x90
반응형
반응형

템플릿 메서드 패턴(Template Method Pattern)

  - 템플릿 메서드 패턴은 행위패턴(Behavioral Pattern) 중 하나이다.
  - 행위 패턴은 객체 사이에 알고리즘이나 책임 분배에 관련된 패턴이다.

  - 행위 패턴은 결합도를 최소화하는 것에 목표로 한다.

 

목적

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.

템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]

 

쉽게 이야기하면 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다.

이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.

결국 상속오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

 

장점

  - 알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의하는데 유용하다.

  - 알고리즘이 단계별로 나누어 지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른 형태로 사용이 필요한 경우 유용하다.

 

코드

  * FactoryTemplate이라는 공장 템플릿 추상화 클래스를 이용해서 자동차, 폰 공장에서는 상품생산 프로세스만 다르게 구성한다고 설정

 

  - FactoryTemplate.java (추상화 클래스)

 

public abstract class FactoryTemplate {

    public final void buildProduct() {
        baseMaterial();
        productProcess();
        finishProduct();
    }

    private void baseMaterial() {
        System.out.println("원/부자재 수입");
    }

    public abstract void productProcess();

    private void finishProduct() {
        System.out.println("제품 완성");
    }

}

 

  - 자동차 공장 Class

 

public class CarFactory extends FactoryTemplate{

    @Override
    public void productProcess() {
        System.out.println("자동차 공장 프로세스 가동!");
    }

}

 

  - 핸드폰 공장 Class

 

public class PhoneFactory extends FactoryTemplate {

    @Override
    public void productProcess() {
        System.out.println("핸드폰 공장 프로세스 가동!");
    }
}

 

  - 테스트 Class

 

@SpringBootTest
class FactoryTemplateTest {

    @Test
    void templatePatternTest() {

        FactoryTemplate factory;
        factory = new CarFactory();
        factory.buildProduct();

        System.out.println("=======================");

        factory= new PhoneFactory();
        factory.buildProduct();

    }


}

 

결과 :

원 부자재 수입
자동차 공장 프로세스 가동!
제품 완성
=======================
원 부자재 수입
핸드폰 공장 프로세스 가동!
제품 완성

 

 

정리

 - 추상화 클래스에서 메서드에 알고리즘 골격을 정의하고, 소스코드의 중복을 줄이기 위해서 하위 클래스에서 공통적으로 나타나는 부분에 대해서 추상클래스에 정의해서 사용한다.

 * 핵심 템플릿 클래스abstract 추상 클래스임을 인지하자

 

참조 

  - https://niceman.tistory.com/142

  - https://yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/

728x90
반응형
반응형

프록시 패턴(Proxy Pattern)

  - Proxy는 대리자, 대변인 이라는 뜻이다. 

  - 프록시 패턴은 구조패턴(Structural Pattern) 중 하나이다.
  - 구조 패턴은 작은 클래스들을 상속과 합성을 이용하여 더 큰 클래스를 생성하는 방법을 제공하는 패턴이다.

  - 인터페이스나 구현을 복합하는 것이 아니라 객체를 합성하는 방법을 제공한다.

    이는 컴파일 단계에서가 아닌 런타임 단계에서 복합 방법이나 대상을 변경할 수 있다는 점에서 유연성을 갖는다.

 

프록시의 주요 기능

프록시를 통해서 할 수 있는 일은 크게 2가지로 나뉜다.

1. 접근 제어 (권한에 따른 접근 차단, 캐싱 등)

2. 부가 기능 추가 (다른 로직을 추가하는 등의 부가 기능을 수행, 예) 실행 시간을 측정해서 로그를 남긴다.)

 

* GOF 디자인 패턴에서는 의도에 따라서 프록시 패턴과, 데코레이터 패턴으로 구분한다.

  - 프록시 패턴 : 접근 제어가 목적

  - 데코레이터 패턴 : 새로운 기능 추가가 목적

 

프록시 패턴의 장점

  - 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.

  - 객체에 대하여 접근할 때에 Wrapper Class를 두어 접근에 대한 통제를 위해 사용된다.

 

코드

  * 더하기 빼기 계산기 로직을 구현 예제 (프록시 패턴을 이용해서 계산 시간을 출력)

 

  - 계산기 interface

 

public interface Calculator {

    public int plus(int left, int right);
    public int subtract(int left, int right);

}

 

  - 계산기 interface 구현 service

 

public class CalculatorService implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        result = left + right;
        return result;
    }

    @Override
    public int subtract(int left, int right) {
        result = left - right;
        return result;
    }
}

 

  - 프록시 패턴을 사용하지 않은 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorService() {

        Calculator calculatorService = new CalculatorService();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorService.plus(left, right);
        int subtractResult = calculatorService.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }

}

 

  - 프록시 패턴 클래스

 

public class CalculatorServiceProxy implements Calculator{

    private int result;

    @Override
    public int plus(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left + right;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println(" plus 계산 시간(m) : "+secDiffTime);

        return result;
    }

    @Override
    public int subtract(int left, int right) {
        long beforeTime = System.currentTimeMillis();

        result = left - right;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long afterTime = System.currentTimeMillis();
        long secDiffTime = (afterTime - beforeTime)/1000;
        System.out.println("subtract 계산 시간(m) : "+secDiffTime);

        return result;
    }
}

 

- 프록시 패턴을 사용한 테스트

 

@SpringBootTest
class CalculatorServiceTest {

    @Test
    void calculatorServiceProxy() {

        Calculator calculatorServiceProxy = new CalculatorServiceProxy();

        int left = 10000;
        int right = 5000;

        int plusResult = calculatorServiceProxy.plus(left, right);
        int subtractResult = calculatorServiceProxy.subtract(left, right);

        assertEquals(plusResult, left+right);
        assertEquals(subtractResult, left-right);

    }


}

 

결과 : plus 계산 시간(m) : 1
        subtract 계산 시간(m) : 2

 

 

정리

 - 프록시 패턴의 의도 : 다른 객체에 접근을 제어하기 위해 대리자를 제공

 - 테스트 코드를 보면 생성 객체만 바꿈으로서 Calculator interface는 영향을 받지 않게 Proxy 클래스를 통해서 원하는 목적에 맞게 흐름을 조정할 수 있었다. 해당 패턴은 Spring 프레임워크에 AOP 동작원리에 사용되어지고 있다.

 * 중요한 점은 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다.

 

참조 

 - https://readystory.tistory.com/132?category=822867

728x90
반응형
반응형

깨끗한 테스트 코드

 

목차

1. 테스트 코드의 중요성

2. 테스트의 종류

3. Unit Test 작성

4. FIRST 원칙

5. 오픈소스 속 Unit Test


1. 테스트 코드의 중요성

  - 테스트 코드는 실수를 바로잡아준다.

  - 테스트 코드는 반드시 존재해야하며, 실제 코드 못지 않게 중요하다.

  - 테스트 케이스는 변경이 쉽도록 한다. 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위테스트다.

  - 테스트 케이스가 있으면 변경이 두렵지 않다. 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다. 테스트 커버리지가 높을수록 버그        에 대한 공포가 줄어든다.

  - 지저분한 테스트 코드는 테스트를 안하니만 못하다.

 

<Effective Unit Test 책에서>

'테스트는 실사용에 적합한 설계를 끌어내준다.'

'테스트를 작성해서 얻게 되는 가장 큰 수확은 테스트 자체가 아니다. 작성 과정에서 얻는 깨달음이다.'

 

'테스트트 자동화되어야 한다.' - 매번 배포할 때마다 실행되어야 한다.

 

2. 테스트의 종류

Unit Test : 프로그램 내부의 개발 컴포넌트의 동작을 테스트한다. 배포하기 전에 자동으로 실행되도록 많이 사용한다.

Integration Test : 프로그램 내부의 개별 컴포넌트들을 합쳐서 동작을 테스트한다. Unit Test는 각 컴포넌트를 고립시켜 테스트하기 때문에 컴포넌트의 interaction을 확인하는 Integration Test가 필요하다.

E2E Test : End to End Test. 실제 유저의 시나이로대로 네트워크를 통해 서버의 Endpoint를 호출해 테스트한다.

 

3. Unit Test 작성

'테스트 라이브러리를 사용하자' (실무에서 JUnit5 + mockito를 많이 사용한다.)

 

Test Double

  * 테스트에서 원본 객체를 대신하는 객체

 

Stub :   - 원래의 구현을 최대한 단순한 것으로 대체한다.  - 테스트를 위해 프로그래밍된 항목에만 응답한다.

 

Spy :

  - Stub의 역할을 하면서 호출에 대한 정보를 기록한다.

  - 이메일 서비스에서 메시지가 몇 번 정송되는지 확인할 때

 

Mock :

  - 행위를 검증하기 위해 가짜 객체를 만들어 테스트하는 방법

  - 호출에 대한 동작을 프로그래밍할 수 있다.

  - Stub은 상태를 검증하고 Mock은 행위를 검증한다.

 

'given, when, then 패턴을 사용하자'

  - given : 테스트를 위한 pre-condition

  - when : 테스트하고 싶은 동작 호출

  - then : 테스트 결과 확인

4. FIRST 원칙

Fast : 빠르게

  - 테스트는 빨리 돌아야 한다. 자주 돌려야 하기 때문이다.

Independent : 독립적으로

  - 각 테스트를 독립적으로 작성한다. 서로에게 의존하면 실패한 원인을 찾기 어려워진다.(다른 테스트의 샐패로 인한건지, 코드 오류인지)

Repeatable : 반복가능하게

  - 테스트는 어떤 환경에서도 반복 가능해야 한다. 실제 환경, QA 환경, 모든 환경에서 돌아가야 한다.

Self-Validating : 자가검증하는

  - 테스트는 bool 값으로 결과를 내야 한다.

Timely : 적시에

  - 테스트하려는 실제 코드를 구현하기 직전에 구현한다.

 

4. 오픈소스 속 Unit Test

Trino(PrestoSQL) 프로젝트 코드

  - 책에서는 하나의 테스트에 하나의 assert를 사용하라고 했다.

  - 테스트하려는 인자가 많은 경우는 하나에 몰아서 하는 경우도 많다.

 

public class TestTypeCalculation
{
    @Test
    public void testBasicUsage()
    {
        assertEquals(Long.valueOf(42), calculateLiteralValue("42", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("NULL", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("null", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("x", ImmutableMap.of("x", 42L)));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(42)", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("(NULL)", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(x)", ImmutableMap.of("x", 42L)));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("42 + 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 - 55), calculateLiteralValue("42 - 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 * 55), calculateLiteralValue("42 * 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 / 6), calculateLiteralValue("42 / 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55 * 6), calculateLiteralValue("42 + 55 * 6", ImmutableMap.of()));
        assertEquals(Long.valueOf((42 + 55) * 6), calculateLiteralValue("(42 + 55) * 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(2), calculateLiteralValue("min(10,2)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("min(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(20), calculateLiteralValue("max(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("max(10,2)", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("x + y", ImmutableMap.of("x", 42L, "y", 55L)));
    }
}

 

code from - https://github.com/trinodb/trino/blob/master/core/trino-parser/src/test/java/io/trino/type/TestTypeCalculation.java

 

Junit5 Samples

  - @DisplayName은 테스트 클래스나 메서드에 보여질 이름을 입력하는 것이다. 테스트의 목적을 명확하게 작정할수 있다.

  - @ParameterizedTest는 하나의 테스트 메서드로 여러 가지 paramter를 테스트할 수 있다. @CsvSource의 값을 parameter로 넘       긴다.

  - JUnit5가 테스트에 관한 유용한 기능을 많이 가지고 있기 때문에 실무에서 많이 사용한다.

 

class CalculatorTests {

	@Test
	@DisplayName("1 + 1 = 2")
	void addsTwoNumbers() {
		Calculator calculator = new Calculator();
		assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@ParameterizedTest(name = "{0} + {1} = {2}")
	@CsvSource({
			"0,    1,   1",
			"1,    2,   3",
			"49,  51, 100",
			"1,  100, 101"
	})
	void add(int first, int second, int expectedResult) {
		Calculator calculator = new Calculator();
		assertEquals(expectedResult, calculator.add(first, second),
				() -> first + " + " + second + " should equal " + expectedResult);
	}
}

 

code from - https://github.com/junit-team/junit5-samples/blob/main/junit5-jupiter-starter-gradle/src/test/java/com/example/project/CalculatorTests.java

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 08  (0) 2022.03.20
[Clean Code] Chapter 07  (0) 2022.03.17
[Clean Code] Chapter 06  (0) 2022.03.13
반응형

모호한 경계를 적당히 구분짓기

 

목차

1. 경계란

2. 경계 짓기 (1) 우리 코드를 보호하기

3. 경계 짓기 (2) 외부 코드와 호환하기

4. 외부 라이브러리 테스트하기 - Learning Test


1. 경계란

 여기서 말하는 경계는 우리 코드와, 외부 코드(오픈소스, 라이브러리)의 사이의 말한다.

  - 오픈 소스, 라이브러리를 안쓰는 프로젝트는 없다.

  - 우리가 만든 코드에 외부에서 들어온 코드를 병합해야 한다.

  - 외부 코드는 외부에서 만든 코드인데, 외부 시스템과 호출하거나 단순히 외부에서 만들어진 코드일 수 있다.

  - 우리 코드와 외부 코드를 깔끔하게 통합시키기 위해 경계를 잘 지어야 한다.

 

2. 경계 짓기 (1) 우리 코드를 보호하기

  캡슐화(Encapsulaltion) :

    객체의 실제 구현을 외부로부터 감추는 방식, 간단하게 말해서 'TMI하지 말아야한다.' 외부에 말할 필요가 없는 건 private 한다.

 

3. 경계 짓기 (2) 외부 코드와 호환하기

 외부 코드를 호출할 때 우리가 원하는 방식으로 사용하고 싶으면 어댑터 패턴(Adapter Pattern) 사용한다.

 

4. 외부 라이브러리 테스트하기 - Learning Test

   라이브러리를 '사용'하는 내가 라이브러리 테스트를 함으로서 생기는 장점

 

   외부 코드를 배우고, 안정성도 미리 검증할 수 있다.

    - 학습 테스트는 이해도를 높인다.

    - 외부 코드의 버전이 변경됐을 때, 우리 코드와 호환되는 지 확인할 수 있다.

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 09  (0) 2022.03.21
[Clean Code] Chapter 07  (0) 2022.03.17
[Clean Code] Chapter 06  (0) 2022.03.13
[Clean Code] Chapter 05  (0) 2022.03.12
반응형

추상 팩토리 패턴(Abstract Factory Pattern)

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

  - 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 화      보할 수 있습니다.

 

장점

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

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

  - 추상 팩토리 패턴에서는 팩토리 클래스에서 서브 클래스를 생성하는 데에 있어서 필요한 if-else, switch 문을 걷어냅니다.

 

팩토리 메소드 패턴 vs 추상 팩토리 패턴

- 둘 다 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다.

 

팩토리 메소드 패턴 

추상 팩토리 패턴

관점 "팩토리를 구현하는 방법"에 초점 "팩토리를 사용하는 방법"에 초점
목적 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적

 

코드

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

 

  - 중개 정책 interface 생성 :

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

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

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;
    }


}

 

  - 추상 팩토리 Interface Class :

     추상 팩토리 역할을 하는 인터페이스 생성

 

public interface BrokeragePolicyAbstractFactory {

    public BrokeragePolicy createBrokerageRule();

}

 

- 팩토리 Interface를 구현하는 클래스(매매 중개 수수료) :

  작성한 팩토리 인터페이스의 createBrokerageRule() 메소드의 리턴 타입이 super class인 BrokeragePolicy이다.

  이는 자바의 다형성을 잘 활용한 방식이다.

public class PurchaseBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new PurchaseBrokeragePolicy();
    }
}

 

- 팩토리 Interface를 구현하는 클래스(임대차 중개 수수료)

 

public class RentBrokerageFactory implements BrokeragePolicyAbstractFactory{

    @Override
    public BrokeragePolicy createBrokerageRule() {
        return new RentBrokeragePolicy();
    }
}

 

- 서브 클래스들을 생성하기 위해서 if-else 없이 분개 처리해주는 클래스

 

public class BrokeragePolicyFactory {


    public static BrokeragePolicy getBrokeragePolicy(BrokeragePolicyAbstractFactory policyAbstractFactory) {
        return policyAbstractFactory.createBrokerageRule();
    }

}

 

- 중개 수수료 계산 api

 

  // 매매 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragepur")
    public Long calcBrokeragePurchase(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new PurchaseBrokerageFactory());
        return policy.calculate(price);
    }
  // 임대차 중계수수료 계산 메소드
    @GetMapping("/api/calc/brokeragerent")
    public Long calcBrokerageRent(@RequestParam ActionType actionType,
                              @RequestParam Long price) {
        BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new RentBrokerageFactory());
        return policy.calculate(price);
    }

 

 

실무 사용 사례

Spring에서 FactoryBean과 그 구현체

 

정리

 - 객체 생성을 담당 및 처리하는 팩토리 클래스를 생성하여서 객체 생성에 관한 확장도 쉽게 구성할 수 있다. 

    팩토리 패턴과 다르게 분개 처리하는 부분이 사라진다.

 

728x90
반응형

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

[Design Pattern] 템플릿 메서드 패턴  (0) 2022.03.23
[Design Pattern] 프록시 패턴  (0) 2022.03.22
[Design Pattern] 어댑터 패턴  (0) 2022.03.16
[Design Pattern] 싱글톤 패턴  (0) 2022.03.15
[Design Pattern] 빌더 패턴  (0) 2022.03.14

+ Recent posts