ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라
    Java/이펙티브 자바 2022. 10. 28. 10:47

    장점

    - 이름을 가질 수 있다. ( 동일한 시그니처의 생성자를 두개 가질 수 없다.)

    - 호출될 떄마다 인스턴트를 새로 생성하지 않아도 된다. (Boolean.valueOf)

    - 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. ( 인터페이스 기반 프레임워크, 인터페이스에 정적 메소드 )

    - 입력 매개 변수가 따라 매번 다른 클래스의 객체를 반환할 수 있다. (EnumSet)

    - 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다. ( 서비스 제공자 프레임워크 )

    단점

    - 상속을 하려면 public이나 protected 생성하기 필요하니 정적 팩토리 메서드만 제공

    - 정적 팩터리 메서드는 프로그래머가 찾기 힘들다.

     


    // 잘못된 예시
    public class Order {
        private boolean prime;
        private boolean urgent;
        private Product product;
        
        public Order(Product product, boolean prime) {
            this.product = product;
            this.prime = prime;
        }
    
        public Order(Product product, boolean urgent) {
            this.product = product;
            this.urgent = urgent;
        }   
    }

    - 생성자의 시그니처가 파라미터의 타입까지 보기에 완전한 동일한 시그니처의 생성자는 존재할 수 없다.

    - 생성자는 이름이 고정이다.

     

    정적팩터리메서드 장점1 : 표현을 더 잘할 수 있다.

    // 정적팩터리메서드 장점1 : 표현을 더 잘할 수 있다.
    public class Order {
        private boolean prime;
        private boolean urgent;
        private Product product;
        
        public static Order primeOrder(Product product){
            Order order = new Order();
            order.prime = true;
            order.product = product;
            return order;
        }
        public static Order urgentOrder(Product product){
            Order order = new Order();
            order.urgent = true;
            order.product = product;
            return order;
        }
    }

    - 표현을 더 잘할 수 있다.

    - 생성자 시그니처가 중복될 경우에 고려해보자.

     

    정적팩터리메서드 장점2 : 인스턴스 컨트롤(싱글톤)

    // 정적팩터리메서드 장점2 : 인스턴스 컨트롤(싱글톤)
    public class Settings {
        private boolean useAutoSteering;
        private boolean useABS;
        private Difficulty difficulty;
    
        private Settings() {
        }
    
        private static final Settings SETTINGS = new Settings();
        public static Settings newInstance() {
            return SETTINGS;
        }
    }

    - 생성자로는 인스턴스의 생성을 컨트롤 할 수 없다.

        ㆍ생성자를 public하게 제공하는 순간부터 그 클래스는 원하는 만큼 생성하게 된다.

    - ex) Boolean.valueOf();

    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
    
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

        ㆍ매개변수에 따라 각각 다른 인스턴스를 리턴하는 기능

    - 플라이웨이트 패턴 ( Flyweight pattern )

        ㆍ자주 사용하는 인스턴스 ( ex. font ) 를 캐싱해서 넣어두고 꺼내서 쓰는 패턴

        ㆍ인스턴스를 통제하는 방법

     

    정적팩터리메서드 장점3 : 인터페이스 타입을 사용할 수 있다.

    // 정적팩터리메서드 장점3 : 인터페이스 타입을 사용할 수 있다.
    public class HelloServiceFactory {
        public static HelloService of(String lang){
            if(lang.equals("ko")){
                return new KoreanHelloService();
            }else{
                return new EnglishHelloService();
            }
        }
    }
    
    // 인터페이스에 static 메서드 선언 가능
    // 팩터리메서드에 굳이 따로 별도로 만들어서 정적팩터리메서드를 많이 만들지 않아도 된다.
    public interface HelloService {
        String Hello();
    
        static HelloService of(String lang){
            if(lang.equals("ko")){
                return new KoreanHelloService();
            } else {
                return new EnglishHelloService();
            }
        }
    }

    - 클래스의 하위 클래스를 리턴할 수 있다.

     

    * class에서 아무것도 붙이지 않으면 패키지 private 레벨

    * interface에서 아무것도 붙이지 않으면 public

     

    정적팩터리메서드 장점4 : 구현체를 숨길 수 있다.

    // 정적팩터리메서드 장점4 : 구현체를 숨길 수 있다
    public static void main(String[] args) {
        HelloService ko = HelloServiceFactory.of("ko");
    }

     

     

    정적팩터리메서드 장점5 : 정적팩터리메서드를 작성하는 시점에 굳이 메서드가 없어도 된다. (구현체가 없어도 된다.)

    // 정적팩터리메서드 장점5 : 정적팩터리메서드를 작성하는 시점에 굳이 메서드가 없어도 된다.
    //                         (구현체가 없어도 된다.)
    public static void main(String[] args) {
        // 자바가 기본으로 제공해주는 정적팩터리메서드
        ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
        // load가 등록되어있는 구현체를 다 가지고 온다.
        Optional<HelloService> helloServiceOptional = loader.findFirst();
        helloServiceOptional.ifPresent(h -> {
            System.out.println(h.Hello());
        });
    
    }
    public static void main(String[] args) {
        // ChineseHelloService는 라이브러리로 추가
    	HelloService helloService = new ChineseHelloService();
    }

    - 위 코드는 구현체에 의존적이지 않다. 하지만 아래 코드는 구현체에 의존적이다.

    - 어떤 구현체가 올지모르지만 인터페이스 기반으로 코딩을 할 때 사용한다.

    - ex) jdbc 드라이버 ( 어떤 드라이버든 호환되는 드라이버가 있다면 동작하도록 하는 패턴 )

        ㆍ실질적으론 ServiceLoader가 사용되지는 않음.


    정적팩터리메서드 단점1 : 상속하기 어렵다.

    // 정적팩터리메서드 단점1 : 상속을 하기 어렵다.
    public class Settings {
        private boolean useAutoSteering;
        private boolean useABS;
        private Difficulty difficulty;
    
        private Settings() {    // private 이므로 상속 X
        }
    
        private static final Settings SETTINGS = new Settings();
        public static Settings newInstance() {
            return SETTINGS;
        }
    }

    - 정적 메서드만 사용하도록 만들기 위해 기본 생성자를 private 처리 시 상속 허용 X

    // 상속을 이용하지 않아도 Settings의 기능 사용가능
    public class AdvancedSettings {
        Settings settings;
    }
    // 정적팩터리메서드를 만들지만 생성자를 허용하는 경우 : List
    
    static <E> List<E> of() {
        return ImmutableCollections.emptyList();
    }
    
    // 실질적으로 구현체가 ArrayList가 아닐 수 있다.
    static <E> List<E> of(E e1, E e2, E e3, E e4) {
        return new ImmutableCollections.ListN<>(e1, e2, e3, e4);
    }

     

    정적팩터리메서드 단점2 : 생성자가 없고 정적팩토리만 있다면 javaDoc으로 확인 시 인스턴스 만드는 방법을 찾기 힘들다.

    - 해결방안1 : naming 패턴을 제한

        ㆍof : 매개변수를 받아서 생성

        ㆍgetInstance : 이미만들어진 인스턴스 사용

        ㆍnewInstance : 새로 생성

     

    - 해결방안2 : 문서화를 해라.


    p9, 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다

    p9, 같은 객체가 자주 요청되는 상황이라면 플라이웨이트 패턴을 사용할 수 있다.

    p10, 자바8부터는 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 없다.

    p11, 서비스 제공자 프레임워크를 만드는 근간이 된다.

    p12, 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.

    p12, 브리지 패턴

    p12, 의존 객체 주입 프레임워크

     

    완벽공략 1. 열거타입 ( Enumeration )

    - 상수 목록을 담을 수 있는 데이터 타입

    - 특정한 변수가 가질 수 있는 값을 제한할 수 있다. 타입-세이브티 ( Type-Safety )를 보장할 수 있다

        ㆍex) int 로 type값을 정의한다면 type값이외 값이 들어갔을때 valid check를 해줘야한다.

    - 싱글톤 패턴을 구현할 때 사용하기도 한다.

    - 질문1) 특정 enum 타입이 가질 수 있는 모든 값을 순회하며 출력하라

    public static void main(String[] args) {
        Arrays.stream(OrderStatus.values()).forEach(System.out::println);
    }

    - 질문2) enum은 자바의 클래스처럼 생성자, 메소드, 필드를 가질 수 있는가?

    public enum OrderStatus {
        PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3);
    
        private int number;
    
        OrderStatus(int number) {
            this.number = number;
        }
    }

    - 질문3) enum의 값은 ==연산자로 동일성을 비교할 수 있는가?

    public static void main(String[] args) {
        Order order = new Order();
        if(order.orderStatus == OrderStatus.DELIVERED){ // equals를 쓴다면 nullpointException이 발생
            System.out.println("delivered");
        }
    }

    - 과제) enum을 key로 사용하는 Map을 정의하세요. 또는 enum을 담고있는 Set을 만들어 보세요.

        ㆍ힌트 : EnumMap, EnumSet

     

    완벽 공략2. 플라이웨이트 패턴 Flyweight (가벼운 체급)

    - 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴

    - 자주 변하는 속성(또는 외적인 속성, extrinsit)과 변하지 않는 속성(또는 내적인 속성, intrinsit)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.

    public class FontFactory {
        private Map<String, Font> cache = new HashMap<>();
        
        public Font getFont(String font) {
            if(cache.containsKey(font)) {
                return cache.get(font);
            } else {
                String[] split = font.split(":");
                Font newFont = new Font(split[0], Integer.parseInt(split[1]));
                cache.put(font, newFont);
                return newFont;
            }
        }
    }

    - 정적팩터리메서드를 이용한다면 하나의 인스턴스를 공유하므로 객체의 메모리 사용량을 줄일 수 있다.

     

    완벽 공략3. 인터페이스에 정적메서드 : 자바8과 9에서 주요 인터페이스의 변화

    static String hi(){
        prepareMessage();
        return "hi";
    }
    default String bye() {  // 기본메서드 : 이 인터페이스를 구현한 클래스들의 인스턴스들이 모두 사용가능
        return "bye";
    }
    
    static private void prepareMessage() {
    
    }

    - 기본 메서드(default method)와 정적 메소드를 가질 수 있다

    - 기본 메서드

        ㆍ인터페이스에서 메소드 선언 뿐 아니라, 기본적인 구현체까지 제공할 수 있다

        ㆍ기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다

        ㆍ이 인터페이스를 구현한 클래스들의 인스턴스들이 모두 사용 가능

     

    - 정적 메서드

        ㆍ자바 9부터 private static 메서드도 가질 수 있다

        ㆍ단, private 필드는 아직도 선언할 수 없다

    - 질문1) 내림차순으로 정렬하는 Comparator를 만들고 List<Integer>를 정렬

    - 질문2) 질문1에서 만든 Comparator를 사용해서 오름차순으로 정렬하라.

    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(100);
        numbers.add(20);
        numbers.add(44);
        numbers.add(3);
    
        Comparator<Integer> desc = (o1,o2) -> o2-o1;
    
        // Collections.sort(numbers, desc);
        numbers.sort(desc);
        numbers.sort(desc.reversed());
    }

     

    완벽 공략4. 서비스 제공자 프레임워크 : 확장 가능한 애플리케이션을 만드는 방법

    - 주요 구성 요소

        ㆍ서비스 제공자 인터페이스 (SPI)와 서비스 제공자 (서비스 구현체)

        ㆍ서비스 제공자 등록 API (서비스 인터페이스의 구현체를 등록하는 방법)

        ㆍ서비스 접근 API (서비스의 클라이언트가 서비스 인터페이스의 인스턴스를 가져올 때 사용하는 API )

    - 다양한 변형

        ㆍ브릿지 패턴 : 구체적인 것과 추상적인 것을 분리

    public static void main(String[] args) {
        Champion kda아리 = new 아리(new KDA());
        kda아리.skillQ();
        kda아리.skillW();
    
        Champion poolParty아리 = new 아리(new PoolParty());
        poolParty아리.skillR();
        poolParty아리.skillW();
    }

        ㆍ의존 객체 주입 프레임워크

        ㆍjava.util.ServiceLoader

     

    완벽 공략5. 리플렉션 (reflection)

    - 클래스로더를 통해 읽어온 클래스 정보(거울에 반사된 정보)를 사용하는 기술

    - 리플렉션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메소드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다

    - 언제 사용할까?

        ㆍ특정 애노테이션이 붙어있는 필드 또는 메소드 읽어오기 (JUnit, Spring)

        ㆍ특정 이름 패턴에 해당하는 메소드 목록 가져와 호출하기 (getter, setter)

     

    * 문자열로 인스턴스 만들기

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName("me.whiteship.hello.Service");
        Constructor<?> constructor = aClass.getConstructor();
        Object o = constructor.newInstance();
    }

     

    댓글

Designed by Tistory.