본문 바로가기
코드프레소 체험단/Java 프로그래밍

[Java 프로그래밍 초급] 객체지향 고급 (2)

by 의정부핵꿀밤 2022. 1. 21.
728x90

✨ 이 글은 [ 코드프레소 Java 웹 개발 체험단 활동 ] 내용입니다 ✨

💜 코드프레소 이러닝 강의 수강 중 - Java 프로그래밍 초급 💜

😎 아래의 링크를 통해 프리미엄 IT 교육 서비스, 코드프레소를 확인해보세요 😎

https://www.codepresso.kr/

 

프리미엄 IT 교육 서비스 - 코드프레소

 

www.codepresso.kr


클래스는 사용자 정의 타입(자료형)

  • 변수를 선언할 때 변수명 앞에 자료형을 명시한다
    • int x, double pi ...
  • Java 클래스는 사용자 정의 타입(User Defined Type)으로 자료형으로 사용된다
    • PaidUser luke; -> PaidUser가 자료형이다!

 

 

Java의 타입 체킹

  • Java는 타입간의 호환에 매우 엄격한 언어이다
  • 기본적으로 서로 다른 타입의 객체를 참조할 수 없다
    •  
  • 그러나 부모 타입의 참조 변수는 그 자식 타입의 객체를 참조할 수 있다
    •  
  • 용어 정리를 하면 아래와 같다
  • 차이는 다음과 같다
  • 만약 자식 객체를 생성하면 아래와 같이 모든 멤버 변수에 접근이 가능하다
  • 부모 타입의 참조변수를 자식 객체로 생성하면, 자식 객체만 갖고 있는 멤버 변수에는 접근이 불가능하다!

 

 

 

다형성(polymorphism)

  • 사전적 정의 : 다양한 형태를 가지는 성질
  • 다형성은 객체 지향의 주요 특성 중 하나이다
  • 비유하자면 동일한 사물이 때에 따라 서로 다른 기능을 할 수 있는 것이다
    • ex) 리모콘 -> mode에 따라서 TV, 에어컨, 라디오 의 리모콘으로 동작 가능!
  • 즉, 하나의 타입이 떄에 따라 여러 종류의 객체의 특성을 가질 수 있는 것이다
    • 여러 종류의 객체를 참조할 수 있는 것이다
  • 같은 형태의 메소드가 때에 따라 다양한 동작을 수행할 수 있다
  • Java에서는 다음 특성을 이용하여 다형성을 구현할 수 있다
    • 부모 클래스의 참조 변수가 자식 클래스의 객체를 참조할 수 있다
    • 메소드 오버라이딩

 

 

다형성의 동작

  • 아래와 같이 하나의 User 객체만을 생성해서 4개의 자식 객체의 메소드를 사용할 수 있게 된다!
  • 메소드를 오버라이딩하게 되면 부모 객체가 자식 객체를 참조해도 해당 메소드는 사용이 가능하고, 서로 다르게 동작을 하도록 할 수 있다!

 

 

왜 다형성을 사용하는가?

  • sw 요구 사항 변경이나 추가에 대해 유연한 설계가 가능하다
  • 요구 사항이 추가되었을 때 기존 코드의 변경이 최소화된다
    • 원래 동일한 코드를 자식 객체마다 작성해야 할 것을 부모 객체로만 해두면 수정될 필요가 없어지는 것이다!

 

 

 

💡 다형성 정리!

  • 부모 타입의 참조 변수자식 타입의 객체를 참조할 수 있다
  • 다형성다양한 형태를 가질 수 있는 성질이다
  • Java 의 다형성은 특정 타입의 참조 변수이기 때문에 다양한 객체를 참조할 수 있다
  • 다형성을 활용하면 요구사항 변경 시에도 기존 코드의 수정을 최소화하고 확장 가능한 SW를 만들 수 있다

다형성 실습

원래 실습은 따로 기록을 안하려고 했지만, 이번 실습 내용이 이해하기 너무 좋아서 한 번 정리해두려고!

 

자 우선 코드에 다형성을 적용하기 전에 현재 파일의 구조는 다음과 같다

여기서 NewTubeSystem이 메인이고, User가 부모 객체이다

그리고 EnterpriseUser와 Administartor, PaidUser 모두 User를 상속받는 자식 객체이다 (이것만 볼거임)

 

 

이제 클래스 하나하나 코드를 보자

먼저 User 클래스이다

public class User {
    protected String email;
    protected String password;
    protected String nickname;

    public User() {
        System.out.println("Init User");
    }

    public User(String email, String password, String nickname) {
        System.out.println("Init User with 3 parameters");
        this.email = email;
        this.password = password;
        this.nickname = nickname;
    }

    public void printUserInfo() {
        System.out.println("- email : " + this.email);
        System.out.println("- password : " + this.password);
        System.out.println("- nickname : " + this.nickname);
    }

    public void login() {
        System.out.println("Hello" + this.nickname + "!");
        System.out.println("Login Succeed!");
    }
}

여기서 볼 점은 email, password, nickname 멤버 변수를 갖고 있다

메소드는 사용자 정보를 출력하는 메소드인 printUserInfo()와 로그인을 하는 login() 메소드가 있다

 

 

이번엔 EnterpriseUser 클래스이다

public class EnterpriseUser extends User {
    private String company;

    public EnterpriseUser(String email, String password, String nickname, String company) {
        super(email, password, nickname);
        this.company = company;
    }

    public void login() {
        System.out.println("Hello " + this.nickname + "!");
        System.out.println("Enter the " + this.company + " Enterprise Page!");
    }

}

User로 부터 상속을 받는 객체이고, EnterpriseUser의 특별한 멤버 변수로 company를 갖는다

그리고 login() 메소드를 오버라이딩 하고 있다

 

 

다음으로는 Administrator 클래스이다

public class Administrator extends User {
    private String adminLevel;

    public Administrator(String email, String password, String nickname, String adminLevel) {
        super(email, password, nickname);
        this.adminLevel = adminLevel;
    }

    public void login() {
        System.out.println("Hello " + this.nickname + "!");
        System.out.println("Please verify your permission : " + this.adminLevel);
    }
}

여기도 마찬가지로 User 클래스를 상속받고 있고, 특별한 멤버 변수인 adminLevel을 갖는다

그리고 login() 메소드를 오버라이딩하고 있다

 

 

마지막으로 PaidUser 클래스이다

public class PaidUser extends User {
    public String membership;

    public PaidUser(String email, String password, String nickname, String membership) {
        super(email, password, nickname);
        this.membership = membership;
    }

    public void login() {
        System.out.println("Hello " + this.nickname + "!");
        System.out.println("Enter the " + this.membership + " Membership Page!");
    }
}

이 또한 User 클래스의 자식 클래스이고, specific 멤버 변수 membership을 가지며, login() 메소드를 오버라이딩한다

 

 

이제 NewTubeSystem 클래스에서 다형성 실습을 진행해보자

public class NewTubeSystem {
    public static void main(String[] args) {
        //PaidUser 객체
        User user = new PaidUser("luke@codepresso.kr", "1111", "Luke", "Pro");
        user.login();
        
        //EnterpriseUser 객체
        user = new EnterpriseUser("jin@codepresso.kr", "2222", "Codepresso");
        user.login();
        
        //Administrator 객체
        user = new Administrator("amy@codepresso.kr", "3333", "Amy", "Lv2");
        user.login();

    }
}

현재 User 객체인 user에 순서대로 Paiduser, EnterpriseUser, Administrator를 참조한다

이 떄 부모 클래스인 User 는 자식 객체들을 참조할 수 있게 된다!

아무튼 이렇게 선언하고 login() 메소드를 호출하면 각각의 객체에 맞게 오버라이딩된 함수가 출력된다

 

결과 화면

 

지금 앞에서 한 건 '하나의 타입이 다른 객체를 가리킬 수 있다' 라는 것을 보여준 예시였다

이번엔 다형성이 어떻게 코드 관리를 용이하게 하는지 보도록 하자

 

 

이를 위해서 몇 가지의 클래스들을 추가 및 변경해보자!

 

먼저 User 클래스에 enterPage( )라는 메소드를 추가한다

public class User {
    protected String email;
    protected String password;
    protected String nickname;


    public User(String email, String password, String nickname) {
        //System.out.println("Init User with 3 parameters");
        this.email = email;
        this.password = password;
        this.nickname = nickname;
    }

    public void login() {
        System.out.println("Hello" + this.nickname + "!");
        System.out.println("Login Succeed!");
    }

    public void enterPage() {
        System.out.println("Enter the default page!");
    }
}

이제 메소드가 login 메소드와 enterPage로 구분되었다!

 

 

그리고 PaidUser 클래스도 마찬가지로 enterPage 메소드를 추가하여 오버라이딩 한다

public class PaidUser extends User {
    public String membership;

    public PaidUser(String email, String password, String nickname, String membership) {
        super(email, password, nickname);
        this.membership = membership;
    }

    public void login() {
        System.out.println("Hello " + this.nickname + "!");
        System.out.println("Enter the " + this.membership + " Membership Page!");
    }

    public void enterPage() {
        System.out.println("Enter the " + this.membership + "Membership page!");
    }
}

 

 

Enterprise 클래스도 똑같이 오버라이딩 해준다

public class EnterpriseUser extends User {
    private String company;

    public EnterpriseUser(String email, String password, String nickname, String company) {
        super(email, password, nickname);
        this.company = company;
    }

    public void login() {
        System.out.println("Hello " + this.nickname + "!");
        System.out.println("Enter the " + this.company + " Enterprise Page!");
    }

    public void enterPage() {
        System.out.println("Enter the " + this.company + "Enterprise page!");
    }

}

 

 

Administartor 클래스에도 오버라이딩~

public class Administrator extends User {
    private String adminLevel;

    public Administrator(String email, String password, String nickname, String adminLevel) {
        super(email, password, nickname);
        this.adminLevel = adminLevel;
    }

    public void login() {
        System.out.println("Checking email and password and permission");
        System.out.println("Hello " + this.nickname + "!");
    }

    public void enterPage() {
        System.out.println("Please verify your permission " + this.adminLevel);
        System.out.println("Enter the " + this.adminLevel + "Admin page!");
    }
}

 

 

그리고 LoginManager 클래스를 새로 생성한다!

이는 실제로 로그인 처리를 하는 클래스인데, 진짜 넣으면 어려우니까 대충 한다~ 하자

public class LoginManager {
    public void processLogin(PaidUser paidUser) {
        System.out.println("process something before login");
        paidUser.login();
        paidUser.enterPage();
        System.out.println("process something after login");
    }

    public void processLogin(EnterpriseUser enterpriseUser) {
        System.out.println("process something before login");
        enterpriseUser.login();
        enterpriseUser.enterPage();
        System.out.println("process something after login");
    }

    public void processLogin(Administrator administrator) {
        System.out.println("process something before login");
        administrator.login();
        administrator.enterPage();
        System.out.println("process something after login");
    }
}

지금 위 코드는 다형성을 이용하지 못했을 떄의 코드이다!!

 

 

자 이제 메인인 NewTubeSystem의 코드를 보자

public class NewTubeSystem {
    public static void main(String[] args) {
        LoginManager loginManager = new LoginManager();

        PaidUser luke = new PaidUser("luke@codepresso.kr", "1111", "Luke", "Pro");
        loginManager.processLogin(luke);

        EnterpriseUser jin = new EnterpriseUser("jin@codepresso.kr", "2222", "jin", "Codepresso");
        loginManager.processLogin(jin);

        Administrator amy = new Administrator("amy@codepresso.kr", "3333", "Amy", "Lv2");
        loginManager.processLogin(amy);

    }
}

지금 LoginManager에 processLogin이 파라미터마다 동작이 달라지는 오버로딩이 되고 있기 떄문에 위처럼 객체를 따로 만들어서 호출하는 것이다

 

 

근데 생각해보면 파라미터로 들어오는 객체가 다를 뿐이고 겹치는 코드가 많은데 너무 비효율적인지 않나??

게다가 객체가 또 추가되면 코드가 계속해서 길어진턴데,,,, 거참,,,

 

이 때 다형성을 사용하면 간단하게 처리가 가능하다는 거지~~!~~!~

 

자 이제 LoginManager 클래스에서 다형성을 적용한 클래스를 보자!

이는 LoginManagerImproved 클래스라고 하겠다!

public class LoginManagerImproved {
    public void processLogin(User user) {
        System.out.println("process something before login");
        user.login();
        user.enterPage();
        System.out.println("process something after login");
    }
}

짠! 여기서는 파라미터를 좀 더 일반화된 클래스인 부모 클래스 User를 파라미터로 갖게 된다

 

 

이걸 사용하려면 NewTubeSystem에서 사용 방법을 바꿔주면 된다!

public class NewTubeSystem {
    public static void main(String[] args) {
        LoginManagerImproved loginManagerImproved = new LoginManagerImproved();

        User user = new PaidUser("luke@codepresso.kr", "1111", "Luke", "Pro");
        loginManagerImproved.processLogin(user);

        user = new EnterpriseUser("jin@codepresso.kr", "2222", "jin", "Codepresso");
        loginManagerImproved.processLogin(user);

        user = new Administrator("amy@codepresso.kr", "3333", "Amy", "Lv2");
        loginManagerImproved.processLogin(user);

    }
}

자 여기서 부모타입의 참조 변수를 사용해서 자식 객체들을 만들어 줬다

(User 하나 만들어서 PaidUser, EnterpriseUser, Admimistrator 를 만들기!)

이렇게 되면 각각 다른 객체이지만 사실상 파라미터로는 부모 객체가 가기 때문에 동일하게 사용이 가능하다!

이렇게 되면 타입별로 있던 메소드가 하나로 줄고, 동작은 동일하다!

 

 

 

 

이번엔 확장이 가능하다! 를 보자

이를 위해 UniversityUser 클래스를 추가해보자

public class UniversityUser extends User {
    private String university;
    
    public UniversityUser(String email, String password, String nickname, String university) {
        super(email, password, nickname);
        this.university = university;
    }

    public void login() {
        System.out.println("Checking email and password and university");
        System.out.println("Hello " + this.nickname + "!");
    }

    public void enterPage() {
                System.out.println("Enter the " + this.university + "university page!");
    }
}

 

만약 다형성을 사용하지 않았으면 LoginManager 클래스에 코드를 추가해야 한다

하지만 다형성을 적용한 LoginManagerImproved 에는 추가할 필요가 없다!

그냥 메인에서 User 객체로 UniversityUser를 추가해서 사용하면 된다~~

 

요러케!

public class NewTubeSystem {
    public static void main(String[] args) {
        LoginManagerImproved loginManagerImproved = new LoginManagerImproved();

        User user = new PaidUser("luke@codepresso.kr", "1111", "Luke", "Pro");
        loginManagerImproved.processLogin(user);

        user = new EnterpriseUser("jin@codepresso.kr", "2222", "jin", "Codepresso");
        loginManagerImproved.processLogin(user);

        user = new Administrator("amy@codepresso.kr", "3333", "Amy", "Lv2");
        loginManagerImproved.processLogin(user);
        
        user = new UniversityUser("yammy@kw.ac.kr", "0102", "yammy", "Kwangwoon");
        loginManagerImproved.processLogin(user);

    }
}

 

결과도 완벽!

4개의 객체에 맞는 로그인 메소드 실행

 

 

미쳤다,, 이해 쏙쏙,,,

좀 오래걸렸지만 가치 있는 다형성 공부였다

빠잉

728x90

댓글