코드스테이츠 - OOP 기초3 (다형성, 추상화)

kwak woojong·2022년 5월 12일
0

코드스테이츠

목록 보기
13/36
post-thumbnail

다형성과 추상화는 매우 중요한 개념이다.

Spring 프레임워크나 Java도 그렇고 이 다형성과 추상화를 기본으로 설계돼 있다.

다형성은 코드를 유연하게 붙여줄 수 있는 놀라운 개념이고

추상화는 그 다형성을 활용하기 위한 인터페이스와 추상클래스를 제공한다.


1. 다형성

1. 다형성이란?

1개의 클래스는 상속과 확장을 통해 다른 클래스로 변모할 수 있다.

상위클래스인 녀석이 다른 하위클래스 녀석을 참조값으로 사용할 수 있다는 뜻이다.

Memer클래스를 만들고 이 Member를 저장하기 위한 인터페이스를 만든다고 가정하자

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    Optional<Member> findByAge(Integer age);
    List<Member> findAll();
}

아래 2. 추상화에서 설명하겠지만 인터페이스는 굳이 다 초기화 하거나 상세하게 설명할 필요는 없다. 대충 이런 메서드를 만들겠다 정도만 가지고 있으면 된다.

이 인터페이스를 확장해서 메모리에 저장하는 구현 클래스를 만들었다고 하자.

public class MemoryMemberRepo implements MemberRepository{

    private static Map<Long, Member> memberMap = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(sequence++);
        memberMap.put(member.getId(), member);
        return member;
    }
    // 이하 생략
}

안에 들어있는 내용은 별로 중요한게 아님. 하여튼 이런 클래스가 있다손 치자. 그리고 Jpa를 이용해서 Member를 저장하는 구현 클래스도 새로 만들었다.

public class JpaMemberRepo implements MemberRepository{

    private final EntityManager em;
    public JpaMemberRepo(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }
    // 이하 생략
 }

대충대충하자 어차피 예시니까 하여튼 여러 구현 클래스가 있는 상황이다.

근데 어차피 결국 웹을 실행할 땐 JPA를 쓰든 메모리를 쓰든 하나만 선택해야 한다.

이 때 MemberRepository로 인스턴스를 생성하는데 new JpaMemberRepo()를 박아주면!

걍 JpaMemberRepo로 동작한다.

@Configuration
@AllArgsConstructor
public class SpringConfig {

    EntityManager em;

    @Bean
    public  MemberRepository memberRepository() {
        return new JpaMemberRepo(em);
//        return new MemoryMemberRepo();
    }
}

Configuration에 MemberRepository를 리턴하는 메서드를 만들었는데 return을 JpaMemberRepo로 해도 먹는다.

심지어 주석처리 해둔 new MemoryMemberRepo도 먹는다. 상황에 따라 원하는 Repository를 취사선택 할 수 있다는거다!

처음에 이거 보고 얼마나 놀랐던지 ㅋㅋㅋㅋ

2. 캐스팅

어? 그거 int나 long 캐스팅 할 때 쓰는거 아닌가?

맞다 그거

근데 이게 부모클래스, 자식클래스의 변환을 말하는거라고도 볼 수 있다.

하위클래스가 상위클래스로 바뀌는건 생략이 가능한데, 그 역은 ()를 꼭 해줘야 한다.

다음 예시를 보자

@Slf4j
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    }
}

내가 필터 공부할 때 썼던 클래스의 일부다. 여기서 doFilter 메서드는 인자로

ServletRequest, ServletRespose를 받는다.

쟤들은 HttpServletRequest, HttpServletRespose의 상위클래스다.

HttpServletRequest httpRequest = (HttpServletRequest) request;

가 먹는다!!!

request 변수는 ServletRequest지만 그 하위 클래스인 HttpServletRequest로 캐스팅이 되는거다. 이걸 다운캐스팅이라고 한다.

  • 상위클래스가 하위클래스로 변환 -> 다운 캐스팅
  • 하위클래스가 상위클래스로 변환 -> 업 캐스팅

3. instaceOf

A가 B에 속해 있어용? 을 boolean으로 뱉어준다.

dog instanceof animal // true 이런식임
HttpServletRequest instanceof ServletRequest // true
animal instanceof dog // false
ServletRequest instanceof HttpServletRequest // false

역은 안된다. A가 B의 하위클래스인가용? 이라고 물어보는 놈이라 생각하자


2. 추상화

상기 다형성 코드에서 인터페이스라는 놈을 봤다. 코드를 좀 보면, 그냥 선언만 디따 하고 별 내용도 동작코드도 없다. 대충 추상적으로 뭔갈 계획했다 -> 추상화를 했다. 라고 생각하자.

추상화와 관련된 녀석들로는 인터페이스, 추상클래스가 있다.

인터페이스는 구현부가 없다. 변수와 메서드의 선언만 있고
추상 클래스는 최소한 1개의 변수나 메서드가 선언만 있다. (다 구현되어 있으면 걍 클래스지 뭐)

1. 인터페이스

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    Optional<Member> findByAge(Integer age);
    List<Member> findAll();
}

얘가 인터페이스다. 앞에 인터페이스라고 떡하니 붙어 있쥬?

저렇게 구현이 하나도 없이 변수나 메서드만 때려 박은 것을 인터페이스라고 한다.

물론 구현된 변수는 있을 수 있는데 그녀석은 public static final을 붙여서 상수로 만들어줘야 한다. fianl 안붙여도 내려오면 상수됨.

애초에 확장을 위해 만들어진 녀석임.

기본 기획을 인터페이스로 다 해두고 이를 하나하나 구현해야 하겠다.

이 때 저 인터페이스를 상속받으려면 implements 라는 단어를 쓰면 된다.

public class MemoryMemberRepo implements MemberRepository{

    private static Map<Long, Member> memberMap = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(sequence++);
        memberMap.put(member.getId(), member);
        return member;
    }
    // 이하 생략
}

MemberRepository를 상속 받아서 MemoryMemberRepo를 만들었다. 여기서 중요한 점은 인터페이스를 상속받아 클래스를 만들면, 추상적으로 만든 메서드들을 죄~다 구현해줘야 한다. (오버라이딩)

implements는 여러개를 받을 수 있다. 다양한 기본 인터페이스를 만들고 1개의 구현체를 만드는 방법도 있다. 그리고 그게 OOP에는 더 알맞는 방법임

2. 추상클래스

추상클래스는 abstract를 붙이고 클래스를 만들면 된다.

abstract class Animal {
    private String name;

    Animal(){}
    Animal(String name){
        this.name = name;
    }

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

    abstract void cry();
    void myName(){System.out.println("이름은 " + getName());}
}
// void cry()는 추상 메서드임. 하위클래스에서 재정의해서 강제로 사용해야함.
// void myName()은 강제성이 없음. 일반 메서드랑 같다.
// 그래서 하위클래스에서 그냥 사용이 가능함.

상기 코드는 작년에 공부할 때 테스트로 내가 작성한 코드임

abstract 클래스를 직접 만들었는데, 인터페이스와 확연히 다른 점은 맨 아래 void myName()이 구현되어 있다는 점이다.

인터페이스는 구현된 메서드가 없는데 여긴 있을 수 있음. 이걸 상속 받아서 클래스를 만들면 abstract void cry()는 꼭 구현을 해줘야 한다.

추상 클래스는 인터페이스와 다르게 extends로 받아온다.

클래스를 만들 때, 여러 인터페이스를 확장할 수 있는 implements와 다르게 extends는 단 1개만 가능

class Student extends Person implements Allowance, Train{

    Student(String name, int age, int weight){
        super(name, age, weight);
    }

    public void in(int price, String name){
        System.out.println(price + "원 을" + name + "에게 받았습니다.");
    }
    public void out(int price, String name){
        System.out.println(price + "원 을을" + name + "에 썼습니다.");
    }
    public void train(int price, String name){
        System.out.printf("%s(을)를 %d원에 공부했다.", name, price);
    }
}

두둥 이것도 혼자 공부할 때 만든 클래스 ㅋㅋㅋ

Student 클래스는 추상클래스 Person을 받고, 인터페이스 Allowance, Train을 받는다.


개인적으로 객체지향 프로그래밍의 가장 핵심이 이 추상화와 다형성인거 같다. 이 덕에 미칠듯한 확장성과 유지보수에 매우 유리하다.

인터페이스를 만들고 어떤 클래스를 개발을 했다. 근데 정책이 바뀌어서 클래스 전체를 바꾸기보단, 인터페이스로 또 다른 클래스를 새로 개발을 한 뒤에 바꿔서 껴주면 되니까! (다형성 덕에 쌉가능)

물론 설계할 때 타입을 인터페이스로 지정해두고 하긴 해야겠지만 ㅎㅎ

profile
https://crazyleader.notion.site/Crazykwak-36c7ffc9d32e4e83b325da26ed8d1728?pvs=4<-- 포트폴리오

0개의 댓글