λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
  • μž₯원읡 κΈ°μˆ λΈ”λ‘œκ·Έ
πŸ’Š Java & Kotlin & Spring/- Java & kotlin

λ‹€ν˜•μ„±μ„ μœ„ν•œ instanceof λ₯Ό Generic 으둜 μ œκ±°ν•˜λŠ” 방법

by Wonit 2022. 4. 3.

λͺ©μ°¨

 

  • μ„œλ‘ 
  • instanceof λž€?
  • μ½”λ“œμ—μ„œ instanceof λ₯Ό 느껴보자
  • Generic 을 μ΄μš©ν•˜μ—¬ μš°μ•„ν•˜κ²Œ μ œκ±°ν•˜κΈ°
  • κ·ΈλŸΌμ—λ„ μ‘΄μž¬ν•˜λŠ” 문제점
  • ν•˜μ§€λ§Œ?

 

μ„œλ‘ 

 

μžλ°”μ—μ„œ λ‹€ν˜•μ„±μ„ μ΄μš©ν•œ 객체지ν–₯적 ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λ‹€λ³΄λ©΄ μ’…μ’… νŠΉμ • 객체가 μ§€μ •λœ μœ ν˜•μ˜ μΈμŠ€ν„΄μŠ€μΈμ§€ 확인해야 ν•˜λŠ” κ²½μš°κ°€ μžˆλ‹€.

κ·Έ 경우 μš°λ¦¬λŠ” μ—¬λŸ¬κ°€μ§€ 선택지가 μžˆμ§€λ§Œ, μ˜€λŠ˜μ€ instanceof μ— λŒ€ν•΄ μ§‘μ€‘ν•˜κ³  이야기해보렀 ν•œλ‹€.

 

λ‹€ν˜•μ„±μ„ μ΄μš©ν•œ ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λ‹€ 보면 νŠΉμ • ν˜•μœΌλ‘œ λ³€ν™˜ν•˜κΈ° μœ„ν•΄ λͺ‡κ°€μ§€ μž‘μ—…μ„ ν•΄μ•Ό ν•˜λŠ”λ°, 그쀑 λŒ€ν‘œμ μΈ 것이 λ°”λ‘œ instanceof μ—°μ‚°μžμ΄λ‹€.

 

instanceof λž€?

 

μ•žμ„œ λ§ν–ˆλ“― Java μ—μ„œλŠ” μ–΄λ–€ 객체의 νŠΉμ • type 에 λŒ€ν•΄μ„œ λ™μΌν•œ type 인지 확인할 수 μžˆλŠ” 연산을 instanceof μ—°μ‚°μžλ₯Ό ν†΅ν•΄μ„œ μˆ˜ν–‰ν•˜κ³  μžˆλ‹€.

 

예λ₯Ό λ“€μ–΄μ„œ λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œμ˜ μ½”λ“œλ₯Ό 보자

 

User λŠ” 총 3κ°€μ§€μ˜ ν˜•νƒœ(Guest, Member, Admin)κ°€ μ‘΄μž¬ν•œλ‹€.

 

public interface User {
    // User μΈν„°νŽ˜μ΄μŠ€
}

// 게슀트
public class Guest implements User {}

// 멀버, 일반 μœ μ €
public class Member implements User {}

// κ΄€λ¦¬μž
public class Admin implements User {}

public class Testing {
    @Test
    void test() {
        User admin = new Admin();

        boolean actual = admin instanceof Admin;

        assertThat(actual).isTrue();
    }
}

 

λ‹Ήμ—°ν•˜κ²Œλ„ μœ„μ˜ μ½”λ“œλŠ” μ„±κ³΅ν•˜κ²Œ λœλ‹€.

 

ν•˜μ§€λ§Œ μΌκ°μ—μ„œλŠ” μ΄λŸ¬ν•œ instanceof 의 μ‚¬μš©μ„ λ‚˜μœ λƒ„μƒˆλΌκ³  ν•΄μ„ν•˜κΈ°λ„ ν•˜λ©° Anti-Pattern μ΄λΌλŠ” μ˜κ²¬λ„ μ‘΄μž¬ν•œλ‹€.

 

“instanceof”, Why And How To Avoid It In Code article

 

μ™œ 이것이 ν”Όν•΄μ•Όν•  λŒ€μƒμΈμ§€ μ½”λ“œλ‹¨μœΌλ‘œ ν•œ 번 μ•Œμ•„λ³΄μž

 

μ½”λ“œμ—μ„œ instanceof λ₯Ό 느껴보자

 

μ½”λ“œμ—μ„œ instanceof λ₯Ό 느껴보자. μ™œ 이λ₯Ό Anti-Pattern 이라고 ν•˜κ³  λ‚˜μœ λƒ„μƒˆλΌκ³  ν•˜λŠ”μ§€.

 

μš°μ„  문제 상황은 μ΄λŸ¬ν•˜λ‹€.

 

μ‚¬μš©μžμ˜ μœ ν˜•μ— 따라 ν•  수 μžˆλŠ” ν–‰λ™μ˜ λ²”μœ„κ°€ λ‹¬λΌμ§€λŠ”λ°, 이λ₯Ό 확인할 수 μžˆλŠ” μ–΄λ– ν•œ κΈ°λŠ₯을 λ§Œλ“€μ–΄λ³΄λ € ν•œλ‹€.

 

μœ„μ˜ κΈ°λŠ₯을 κ΅¬ν˜„ν•œλ‹€λ©΄ μ•„λž˜μ™€ 같이 μ„ΈλΆ€ λ‚΄μš© 및 νŠΉμ§•κ³Ό μ œμ•½μ„ 정리할 수 μžˆμ„ 것이닀.

 

μ‚¬μš©μž

 

  • μ‚¬μš©μžλŠ” 3κ°€μ§€μ˜ μœ ν˜•κ³Ό 각각의 ν–‰μœ„κ°€ μ‘΄μž¬ν•œλ‹€.
    • Guest : 방문자
      • κ°€λŠ₯ν•œ ν–‰μœ„ : κΈ€ 읽기
    • Member : νšŒμ›
      • κ°€λŠ₯ν•œ ν–‰μœ„ : κΈ€ 읽기, κΈ€ μ“°κΈ°
    • Admin : κ΄€λ¦¬μž
      • κ°€λŠ₯ν•œ ν–‰μœ„ : κΈ€ 읽기, κΈ€ μ“°κΈ°, κΈ€ μ‚­μ œ

 

Actuator

 

 

  • Actuator μ— μ˜ν•΄μ„œ 각각 μ‚¬μš©μžμ˜ ν–‰μœ„λ₯Ό 좜λ ₯
    • μ‚¬μš©μž μœ ν˜•μ— 따라 Actuator λ₯Ό 각기 λ‹€λ₯΄κ²Œ κ΅¬ν˜„
      • GuestActuator : 1개의 ν–‰μœ„λ₯Ό 좜λ ₯
      • MemberActuator : 2개의 ν–‰μœ„λ₯Ό 좜λ ₯
      • AdminActuator : 3개의 ν–‰μœ„λ₯Ό 좜λ ₯
    • Composit νŒ¨ν„΄μ„ μ΄μš©ν•˜μ—¬ ActuatorContainer μ—μ„œ Actuatorλ₯Ό ν•©μ„±ν•˜μ—¬ μ†Œμœ 

 

전체적 섀계

 

μœ„μ˜ λ‚΄μš©λ“€μ„ ν•©μΉ˜λ©΄ μ•„λž˜μ™€ 같은 섀계가 λ‚˜μ˜€κ²Œ λœλ‹€.

 

 

그럼 μœ„μ˜ 그림을 ν† λŒ€λ‘œ μ‹€μ œ κ΅¬ν˜„μ„ 해보도둝 ν•˜μž

 

User νŒ¨ν‚€μ§€

 

User νŒ¨ν‚€μ§€λŠ” User μ„œλ‘œ λ‹€λ₯Έ user 의 κ΅¬ν˜„μ²΄λ“€μ΄ μ‘΄μž¬ν•˜λŠ” 곳이닀.

 

3κ°€μ§€μ˜ User Type 을 봐보자

 

// UserType Enum
public enum UserType {
    GUEST, ADMIN, MEMBER
}

// User Interface
public interface User {
    UserType getType();
    void readArticle();
}

// Guest Class
public class Guest implements User {
    @Override
    public UserType getType() {
        return UserType.GUEST;
    }

    @Override
    public void readArticle() {
        System.out.println("Guest λŠ” Article 을 읽을 수 μžˆμŠ΅λ‹ˆλ‹€.");
    }
}

// Member Class
public class Member implements User {

    @Override
    public UserType getType() {
        return UserType.MEMBER;
    }

    @Override
    public void readArticle() {
        System.out.println("Member 은 Article 을 읽을 수 μžˆμŠ΅λ‹ˆλ‹€.");
    }

    public void createArticle() {
        System.out.println("Member 은 Article 을 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.");
    }
}

// Admin Class
public class Admin implements User {

    @Override
    public UserType getType() {
        return UserType.ADMIN;
    }

    @Override
    public void readArticle() {
        System.out.println("Admin 은 Article 을 읽을 수 μžˆμŠ΅λ‹ˆλ‹€.");
    }

    public void createArticle() {
        System.out.println("Admin 은 Article 을 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.");
    }

    public void deleteArticle() {
        System.out.println("Admin 은 Article 을 μ‚­μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€.");
    }
}

 

μ•žμ„œ μ΄μ•ΌκΈ°ν–ˆλ˜ 바와 같이 3κ°€μ§€μ˜ μ„œλ‘œ λ‹€λ₯Έ μ‚¬μš©μž μœ ν˜•μ€ μ„œλ‘œ λ‹€λ₯Έ κΆŒν•œμ„ 가지고 있고, User λ₯Ό μƒμ„±ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈμ—μ„œλŠ” μ μ ˆν•œ 상황에 μ μ ˆν•œ User ν•˜μœ„ κ΅¬ν˜„μ²΄λ“€μ„ μƒμ„±ν•΄μ€˜μ•Ό ν•œλ‹€.

 

이제 User λ₯Ό μ‚¬μš©ν•˜λŠ” μͺ½μ„ κ°€λ³΄μž

 

Actuatorλ“€

 

Actuator μ—μ„œλŠ” User λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„μ„œ User κ°€ ν•  수 μžˆλŠ” 행동듀을 μ·¨ν•œλ‹€.

 

3개의 UserType 이 μ‘΄μž¬ν•˜λ―€λ‘œ 3개의 Actuator λ₯Ό λ§Œλ“€μ–΄λ³΄μž.

 

public interface UserActuator {
    void describeActions(User user);
}

public class GuestActuator implements UserActuator {
    @Override
    public void describeActions(User user) {

        if (!(user instanceof Guest)) {
            throw new IllegalArgumentException("Guest κ°€ μ•„λ‹™λ‹ˆλ‹€.");
        }

        user.readArticle();
    }
}

public class MemberActuator implements UserActuator {

    @Override
    public void describeActions(User user) {
        if (!(user instanceof Member)) {
            throw new IllegalArgumentException("Member κ°€ μ•„λ‹™λ‹ˆλ‹€.");
        }

        Member member = new Member();

        member.readArticle();
        member.createArticle();
    }
}

public class AdminActuator implements UserActuator {
    @Override
    public void describeActions(User user) {
        if (!(user instanceof Admin)) {
            throw new IllegalArgumentException("Admin 이 μ•„λ‹™λ‹ˆλ‹€.");
        }
        Admin admin = (Admin) user;

        admin.readArticle();
        admin.createArticle();
        admin.deleteArticle();
    }
}

 

  • GuestActuator
  • MemberActuator
  • AdminActuator

 

총 3κ°€μ§€μ˜ Actuator 둜 User λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„μ„œ ν•΄λ‹Ή User 객체가 μ§„μ§œ Actuator κ°€ μ›ν•˜λŠ” νƒ€μž…μΈμ§€ λ¨Όμ € ν™•μΈν•˜λŠ” μ½”λ“œκ°€ μ‘΄μž¬ν•œλ‹€.

 

이 κ³Όμ •μ—μ„œ μ½”λ“œμ˜ λƒ„μƒˆκ°€ λ‚˜κΈ° μ‹œμž‘ν•œλ‹€.

 

νŠΉμ • Actuator μž…μž₯μ—μ„œλŠ” μ™œ κ³„μ†ν•΄μ„œ User κ°€ μ˜¬λ°”λ₯Έμ§€ 확인해야 ν• κΉŒ?

 

ActuatorλŠ” μ• μ΄ˆμ— Userλ₯Ό 받을 λ•Œ νƒ€μž…μ— λ§žλŠ” User 만 λ°›μœΌλ©΄ λ˜μ§€ μ•Šμ„κΉŒ?

 

일단 문제점이라고 μƒκ°λ˜λŠ” 뢀뢄을 μ°Ύμ•˜μœΌλ‹ˆ λ§ˆμ§€λ§‰ 남은 ActuatorContainer λ₯Ό κ΅¬ν˜„ν•΄μ£Όμž.

 

public class ActuatorContainer implements UserActuator {
    private final Map<UserType, UserActuator> map;

    public ActuatorContainer() {
        this.map = new HashMap<>();

        map.put(UserType.GUEST, new GuestActuator());
        map.put(UserType.MEMBER, new MemberActuator());
        map.put(UserType.ADMIN, new AdminActuator());
    }


    @Override
    public void describeActions(User user) {
        UserType type = user.getType();
        UserActuator userActuator = map.get(type);

        userActuator.describeActions(user);
    }
}

ActuatorContainer λŠ” 합성을 μ΄μš©ν•΄μ„œ κ΅¬ν˜„ν•˜μ˜€λ‹€.

 

μ•žμ„œ μ§€μ ν–ˆλ˜ 문제λ₯Ό ν•œ 번 ν•΄κ²°ν•΄λ³΄μž

 

Generic 을 μ΄μš©ν•˜μ—¬ μš°μ•„ν•˜κ²Œ μ œκ±°ν•˜κΈ°

 

μ•žμ—μ„œ μ§€μ ν•œ λ¬Έμ œκ°€ λ­μ˜€λŠ”κ°€?

 

λ‹€μ‹œ μ •ν™•νžˆ μ •μ˜ν•˜μžλ©΄ 이것이닀.

 

Actuator λŠ” 컴파일 μ‹œμ μ— μ˜¬λ°”λ₯΄μ§€ μ•Šμ€ νƒ€μž…μ˜ User κ°€ λ“€μ–΄μ˜€λŠ” 것을 μ œν•œν•œλ‹€

 

이λ₯Ό μœ„ν•΄μ„œ Generic 을 μ΄μš©ν•  수 μžˆλ‹€.

 

UserActuator μ—μ„œ μ œλ„€λ¦­ μ‚¬μš©ν•˜κΈ°

// Actuator
public interface UserActuator <T extends User> {
    void describeActions(T user);
}

 

μš°μ„  Actuator μ—μ„œ User νƒ€μž…λ§Œ λ°›λŠ”λ‹€λŠ” 것을 λͺ…ν™•νžˆ ν•˜κΈ° μœ„ν•΄ Generic Expression 을 μ΄μš©ν•œλ‹€.

 

User λ₯Ό μƒμ†ν•œ T 만 받도둝 ν•˜μž

 

각각의 Actuator κ΅¬ν˜„μ²΄μ— νƒ€μž… μ§€μ •ν•˜κΈ°

 

그리고 각각의 Actuator μ—μ„œ κ΅¬ν˜„μ²΄μ˜ νƒ€μž…μ„ μ œλ„ˆλ¦­μœΌλ‘œ μ§€μ •ν•΄μ£Όμž.

 

// Guest Actuator
public class GuestActuator implements UserActuator<Guest> {
    @Override
    public void describeActions(Guest user) {
        user.readArticle();
    }
}

// Member Actuator
public class MemberActuator implements UserActuator<Member> {

    @Override
    public void describeActions(Member user) {
        user.readArticle();
        user.createArticle();
    }
}

// Admin Actuator
public class AdminActuator implements UserActuator<Admin> {
    @Override
    public void describeActions(Admin user) {
        user.readArticle();
        user.createArticle();
        user.deleteArticle();
    }
}

 

그럼 μ–΄λ–€κ°€?

 

μ•žμ„œ λ³΄μ•˜λ˜ if/else κ΅¬λ¬Έμ„ κΉ”λ”ν•˜κ²Œ 없앨 수 μžˆλ‹€.

 

κ·ΈλŸΌμ—λ„ μ‘΄μž¬ν•˜λŠ” 문제점

 

ν•˜μ§€λ§Œ μœ„μ˜ λ°©μ‹μ—λŠ” ν•œκ°€μ§€ 큰 문제점이 μ‘΄μž¬ν•œλ‹€.

 

λ°”λ‘œ, Raw use of parameterized class μ΄λ‹€.

 

μ½”λ“œλ‘œ 봐보자

 

public class ActuatorContainer implements UserActuator {
    private final Map<UserType, UserActuator> map;

    public ActuatorContainer() {
        this.map = new HashMap<>();

        map.put(UserType.GUEST, new GuestActuator());
        map.put(UserType.MEMBER, new MemberActuator());
        map.put(UserType.ADMIN, new AdminActuator());
    }


    @Override
    public void describeActions(User user) {
        UserType type = user.getType();
        UserActuator userActuator = map.get(type);

        userActuator.describeActions(user);
    }
}

 

μœ„ μ½”λ“œμ—μ„œ 보면 2번째 λΌμΈμ—μ„œ map 으둜 container λ₯Ό 생성할 λ•Œ, Raw Type 의 UserActuator κ°€ λ“€μ–΄κ°€κ²Œ λ˜λŠ” 것이닀.

 

이 말은 Generic Expression 을 μ‚¬μš©ν•œ 클래슀λ₯Ό ν˜ΈμΆœν•  λ•Œ, λͺ…ν™•ν•œ νƒ€μž…μ„ 지정해주지 μ•Šμ•„μ„œ λ°œμƒν•˜λŠ” λ¬Έμ œμ΄λ‹€.

 

그럼 νƒ€μž…μ„ 지정해주면 λ˜μ§€ μ•ŠλŠλƒ? λΌκ³  ν•  수 μžˆμ§€λ§Œ νƒ€μž…μ„ μ§€μ •ν•΄λ²„λ¦¬λŠ” μˆœκ°„ νŠΉμ • κ΅¬ν˜„μ— κ²°μ •λ˜μ–΄ 버린닀.

 

즉, UserActuator 의 μ œλ„ˆλ¦­μ„ UserType.MEMBER λ‘œ μ§€μ •ν•˜λŠ” μˆœκ°„ map.put(UserType.GUEST, new GuestActuator()) μ—μ„œ 컴파일 μ—λŸ¬κ°€ λ°œμƒν•˜κ²Œ λœλ‹€.

 

끝으둜

 

아직 λ§ˆμ§€λ§‰ 문제λ₯Ό ν•΄κ²°ν•˜μ§€ λͺ»ν•˜κ³  이와 λΉ„μŠ·ν•œ μƒν™©μ—μ„œλŠ” Raw Type 을 μ‚¬μš©ν•΄λ²„λ¦¬κ³  μžˆλ‹€.

 

좔후에 이와 κ΄€λ ¨ν•˜μ—¬ 더 자료λ₯Ό 찾아보고 κ³ λ―Όν•΄λ³Έ λ’€ 글을 완성해보겠닀.

 

... TBD

λŒ“κΈ€