ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 추상클래스와 인터페이스의 정의 및 활용법
    기타/과제 2020. 11. 24. 12:01

    추상 클래스의 정의

    클래스 안에 '*추상 메소드'가 하나 이상 포함되거나 *abstract로 정의된 경우를 말한다.

    추상 클래스의 목적은 추상 클래스를 상속받아 기능을 이용하고 확장하기 위해서이다.

    추상 클래스는 상속을 통한 자손 클래스에 의해서만 완성될 수 있다.

    클래스의 역할은 다할 수 없으나 새로운 클래스를 작성하는 바탕=조상클래스 의 의미를 갖는다.

    (TV 리모콘에 기본 기능을 설정한 미완성 설계도를 이용하면 각자 따로 설계도를 만드는 것보다 효율적이다)

     

    추상 클래스는 추상 메소드를 포함한다는 것 외에는 일반 클래스와 동일하다.

    추상 클래스는 *인스턴스를 생성할 수 없다.

    완성된 클래스라도 추상 클래스루 지정하면 인스턴스를 생성할 수 없다.

    추상 클래스로부터 상속받는 자손클래스는 *오버라이딩을 통해 조상의 추상 메소드를 모두 구현해야한다.

     

    *추상메소드:

    - 아직 구현되지 않은 abstract로 정의된 메소드

    - 선언만하고 상속받은 자식 클래스에서 재정의하도록 할 때 사용

     

    *abstract:

    - 메소드 앞에 붙여주면 *추상화 & 추상 메소드화 시킨다. 

    - 추상 메소드를 가지고 있지 않은 클래스도 추상 클래스로 지정할 수 있다.

     

    *추상화: 기존 클래스의 공통부분을 뽑아서 조상클래스는 만드는 것

     

    *인스턴스:

    - *객체를 소프트웨어에 실체화 한 것

    - 클래스를 new 명령문으로 메모리에 생성한 독립적인 객체

    - 객체를 실제 메모리에 할당하는 과정을 인스턴스화라고 한다.

    String str; //객체
    str = new String("Hello world"); //인스턴스
    

     

    *객체(Object):

    - 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신과 다른 것을 식별가능한 것을 말한다. 

    - 예를 들어 물리적으로 존재하는 사람, 책, 자동차, 컴퓨터 등 과 추상적인 학과, 강의, 주문 등이 모두 객체가 될 수 있다.

    - 크게 속성과 동작으로 구성되어있다.

    - 자바에서 객체의 속성을 필드(field), 동작을 메소드(method)라고 한다.

    - 객체는 선언을 의미한다.

    - 프로그래밍으로 표현하려는 실체

    - 클래스를 인스턴스화 하면 객체가 생성된다.

     

    *오버라이딩:

    - 조상 클래스로부터 상속받은 메소드를 재정의 하는 것

    - 오버라이딩 된 메소드는 조상 클래스의 메소드보다 우선시 되기 때문에 자식 객체에서 메소드 호출 시 오버라이딩 된 메소드가 호출된다.

    - 원래 메소드와 동일한 리턴 타입, 메소드 이름, 매개 변수 리스트를 가져야 한다.

    -접근 제한을 더 높을 수 없다.

    -새로운 예외를 throws 할 수 없다.

     

     

    추상클래스의 작성예시

    추상 클래스 형식

    abstract class 클래스명{
        추상 메소드();
    }

    추상 메소드 형식

    //추상 메소드 형식
    abstract 접근제한자 반환타입 메서드명(매개변수){
         내용
    }
    

    심플한 예제

    //AbstractTest라는 테스트 클래스를 만든다.
    public class AbstractTest {
    	public static void main(String[] args) {
    		FirstCat fc = new FirstCat();
    		SecondCat sc = new SecondCat();
    		fc.call();
    		sc.call();
    	}
    }
    
    // 추상 메서드를 포함하므로 추상클래스로 선언
    abstract class Cat { 
    	// 추상 메서드 선언(구현x)
    	abstract void call(); 
    	void call2() {
    		System.out.println("일반 메서드");
    	}
    }
    
    //Cat 추상클래스를 상속한 클래스들
    class FirstCat extends Cat {
    	// 추상메소드는 서브클래스에서 반드시 재정의 되어야 함
    	void call() {
    		System.out.println("첫번째 야옹이");
    	}
    }
    
    class SecondCat extends Cat {
    	void call() {
    		System.out.println("두번째 야옹이");
    	}
    }
    

    위 소스코드를 보면 Cat이라는 추상 클래스(조상 클래스)

    그것을 상속받은 자식 클래스 FirstCat, SecondCat을 확인 할 수 있다

    자식클래스는 *extends를 이용해 Cat을 상속받아 call(); 이라는 메소드를 사용했다.

     

    *extends: 

    - extends는 클래스를 확장시켜준다.
    - 조상 메소드를 그대로 사용할 수 있으며 오버라이딩 할 필요없이 조상에 구현되어 있는 것을 직접 사용할 수 있다.

     

    그래서 결과는 아래와 같이 나온다.

    첫번째 야옹이
    두번째 야옹이
    

    그렇다면 이 소스는 올바르게 실행이 될까?

    public class AbstractTest {
    	public static void main(String[] args) {
    		Cat ac = new Cat();
    		ac.call();
    	}
    }
    

    결과는 에러가 뜬다

    추상 클래스를 통해 만들어낸 객체는 객체의 모습을 가지지 못해 해당 클래스가 객체를 생성시킬 수 없다.

     

     

    인터페이스의 정의

    인터페이스모든 메소드가 추상메소드인 경우를 말한다.

    인터페이스는 함수의 구현을 강제하기 위해서 생성한다.

    인터페이스는 구현 객체의 같은 동작을 보장하기 위해서 생성한다.

    자바의 다양성을 극대화하여 개발코드 수정을 줄이고 프로그램 유지보수성을 높이기 위해 사용한다.

    *interface 키워드를 통해 선언 할 수 있다.

    *implements 키워드를 통해 일반 클래스에서 인터페이스를 구현할 수 있다.

    인터페이스는 public 추상메소드public 정적 상수만 가질 수 있다.

    자바에서는 다중 상속이 불가능 하지만 인터페이스를 이용하면 다중상속처럼 사용가능하다.

    JAVA8 이후부터는 *디폴트 메소드, *정적 메소드가 추가되었다.

    인터페이스는 private 값을 가지지 못한다.

    인터페이스는 생성자를 가질 수 없으며 객체화 할 수 없다. (추상 메소드이기 때문이다)

     

     

    *interface:

    - 추상 메서드임을 나타내는 abstract를 붙이지 않아도 자동으로 자바에서 해당 키워드를 붙여준다.

    - 하지만 키워드를 붙여주는 편이 좋은 코드

     

    *implements

    - 사전적 의미로 도구, 이행하다란 뜻을 가지고 있다.

    - 인터페이스에 정의된 메소드를 해당 클래스에서 대신 구현을 하겠다는 의미

    - 여러개의 인터페이스를 받아 메서드를 구현 할 수 있다.


    *디폴트 메소드(default):

    - 인터페이스가 default키워드로 선언되면 메소드가 구현될 수 있다.

    - 이를 구현하는 클래스는 default메소드를 오버라이딩 할 수 있다.

    - 기존 인터페이스 기능을 확장하며 구현체에 공통으로 들어갈 기능(코드)를 디폴트 메서드 내부의 작성함으로써 반복되는 코드의 작성을 줄여준다.

    - 추상 메소드와 달리 일반 메소드처럼 몸통 {} 이 있어야 한다.

    - 재정의 할 수 있다.

     

    *정적 메소드 (static):

    - 재정의 할 수 없다.

    - 반드시 클래스 명으로 메소드를 호출해야한다.

    - 인터페이스에서 메소드 구현이 가능하다.

    - 인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메소드

    - 항상 접근 제어자는 public이며 생략가능하다.

     

     

    인터페이스의 작성예시

    인터페이스는 4가지를 정의하거나 구현할 수 있다.

    public interface 인터페이스명 {
    
    	// 상수
    	타입 상수명 = 값;
    
    	// 추상 메소드
    	타입 메소드명(매개변수, ... );
    
    	//디폴트 메소드
    	default 타입 메소드명(매개변수, ... ){
    	  //구현부
    	}
    
    	// 정적 메소드
    	static 타입 메소드명(매개변수) {
    	  //구현부
    	}
    
    }
    

    상수: 인터페이스에서 값을 정해주니 함부로 바꾸지 말고 제공하는 값만 참고해라 (절대적)

    추상 메소드: 가이드만 줄테니 추상 메소드를 오버라이딩해서 재구현해라 (강제적)

    디폴트 메소드: 인터페이스에서 기본적으로 제공해주지만 마음에 안들면 각자 구현해서 써라 (선택적)

    정적 메소드: 인터페이스에서 제공해주는 것으로 무조건 사용해라 (절대적)

     

    절대적은 아무것도 손댈 수 없고 강제적은 implements하지 않으면 피할 수 있다.

     

     

    클래스가 인터페이스를 참조하게 될 경우 형식

    class 클래스명 extends 상속받을클래스명 implements 인터페이스명{
        내용
    }

     

    간단한 인터페이스 구현예제

    interface catWorld{
        public void call();
    }
     
    public class InterfaceTest implements catWorld{
    
        @Override
        public void call() {
            System.out.println("야옹야옹!");
        }
        
        public static void main(String[] args) {
            InterfaceTest it = new InterfaceTest();      
            it.call();
        }
     
    }

    catWorld라는 인터페이스를 만들었다.

    그 다음 InterfaceTest라는 클래스는 catWorld 인터페이스를 상속받았다.

    그리고 오버라이딩을 이용해 값을 출력했다.

    야옹야옹!

     

    만약 이렇게 소스를 만들면 어떻게 될까?

    interface catWorld {
    	public void call();
    	public void call2();
    }
    
    public class InterfaceTest implements catWorld {
    
    	@Override
    	public void call() {
    		System.out.println("야옹야옹!");
    	}
    
    	public static void main(String[] args) {
    		InterfaceTest it = new InterfaceTest();
    		it.call();
    	}
    
    }

     

    추상 메소드 call2(); 를 추가한 뒤에 결과를 출력해보았다.

    야옹야옹!

    결과는 동일하게 '야옹야옹!'이 나온다.

    하지만 소스상에는 에러가 잡힌다.

    그 이유는 call2(); 를 오버라이딩 시키지 않았기 때문이다.

     

    그래서 이렇게 만들어야 에러가 사라진다.

    interface catWorld {
    	public void call();
    	public void call2();
    }
    
    public class InterfaceTest implements catWorld {
    
    	@Override
    	public void call() {
    		System.out.println("야옹야옹!");
    	}
    	
    	@Override
    	public void call2() {
    		// TODO Auto-generated method stub
    		
    	}
    	public static void main(String[] args) {
    		InterfaceTest it = new InterfaceTest();
    		it.call();
    	}
    
    }

    이처럼 클래스가 인터페이스를 상속 할 때는 인터페이스 안에 정의된 모든 추상 메소드를 오버라이딩해야

    정상작인 컴파일이 가능하다.

     

     

     

    다음은 금융결제원이 은행을 운영하기 위한 기본 가이드라인을 만들었다.

    Bank라는 이름의 인터페이스를 생성했다.

    package Test;
    
    public interface Bank {
    
    	// 상수 (최대 고객에게 인출해 줄 수 있는 금액 명시)
    	// 최대 금액은 바뀔 수 없다 (절대적)
    	public int MAX_INTEGER = 10000000;
    
    	// 추상메소드(인출하는 메소드)
    	// 가이드 안에서 자유롭게 인출가능 (강제적)
    	void withDraw(int price);
    
    	// 추상메소드(입금하는 메소드)
    	// 입금액은 자유롭다.
    	void deposit(int price);
    
    	// JAVA8에서 가능한 defualt 메소드
    	// 고객의 휴면계좌 찾아주는 메소드 : 필수구현은 (선택적)
    	default String findDormancyAccount(String custId) {
    		System.out.println("**금융결제원에서 제공하는 로직**");
    		return "00은행 000-000-0000-00";
    	}
    
    	// JAVA8에서 가능한 정적 메소드
    	// 보안을 위한 블록체인 인증을 요청하는 메소드(절대적)
    	static void BCAuth(String bankName) {
    		System.out.println(bankName + " 에서 블록체인 인증을 요청합니다.");
    		System.out.println("전 금융사 공통 블록체인 로직 수행");
    	}
    
    }

    디폴트 메소드를 사용 하는 이유는 뭘까?

    현재 휴먼 계좌 찾는 프로그램은 선택사항이다.

    그런데 정책이 바뀌어서 갑자기 휴먼 계좌 찾기가 필수가 되어버리면 어떻게 해야할까?

    그냥 추상 메소드를 추가해서 다시 가이드를 잡으면 될까?

    하지만 각 은행사마다 개발환경과 운영환경, 개발기간이 모두 다르다.

    만약 추상 메소드를 인터페이스에 추가한다면 이를 implements한 모든 클래스에서 강제적으로 추상 메소드를 구현해야하고 그렇지 않으면 에러가 난다.

     

    하지만 디폴트 메소드를 정의하고 기본 구현부를 제공할 경우 마음에 들지 않으면 각자 오버라이딩해서 재구현 할 수가 있다.

    이미 운영되고 있는 시스템에서 추가 요건으로 인해 불가피하게 반영해야할 때 디폴트 메소드를 쓰면 효과적이다.

     

     

     

    이제 인터페이스를 토대로 KB은행과 SH은행이 

    각자 은행에 맞는 스타일로 입출금 서비스를 제공한다.

    public class KBBank implements Bank {
    
    	@Override
    	public void withDraw(int price) {
    		System.out.print("KB은행만의 인출 로직...");
    		if (price < Bank.MAX_INTEGER) {
    			System.out.println(price + " 원을 인출한다.");
    		} else {
    			System.out.println(price + " 원을 인출실패.");
    		}
    	}
    
    	@Override
    	public void deposit(int price) {
    		System.out.println("KB은행만의 입금 로직..." + price + " 원을 입금한다.");
    
    	}
    
    }

    KB은행은 휴면계좌 찾기 메소드를 재구현하지 않았다.

    이는 금융결제원이 제공하는 메소드를 사용하거나 사용하지 않겠단 뜻이다.

     

    public class SHBank implements Bank {
    
    	@Override
    	public void withDraw(int price) {
    		System.out.println("SH은행만의 인출 로직...");
    		if (price < Bank.MAX_INTEGER) {
    			System.out.println(price + " 원을 인출한다.");
    		} else {
    			System.out.println(price + " 원을 인출실패.");
    		}
    	}
    
    	@Override
    	public void deposit(int price) {
    		System.out.println("SH은행만의 입금 로직..." + price + " 원을 입금한다.");
    		System.out.println("SH은행은 별도의 후행처리 작업을 따로 한다.");
    
    	}
    
    	// JAVA8에서 가능한 defualt 메소드(고객의 휴면계좌 찾아주는 메소드)
    	@Override
    	public String findDormancyAccount(String custId) {
    		System.out.println("**금융개정법안 00이후 고객의 휴면계좌 찾아주기 운동**");
    		System.out.println("*SH은행만의 로직 적용*");
    		return "00은행 000-000-0000-00";
    	}
    
    }
    

    SH은행은 휴면계좌 찾기 메소드를 재정의하여 SH은행사만의 휴면계좌 찾기 로직을 재구현했다.

     

    그리고 얼마 안가 신규 은행 Kakao뱅크가 생겼다.

    카카오뱅크는 인터페이스를 implements 하지 않은채 자신만의 메소드를 구현했다.

    public class KakaoBank {
    
    	public void kakaoWithDraw(int price) {
    		System.out.print("Kakao은행만의 인출 로직...");
    		System.out.println(price + " 원을 인출한다.");
    	}
    
    	public void kakaoDeposit(int price) {
    		System.out.println("Kakao은행만의 입금 로직..." + price + " 원을 입금한다.");
    	}
    
    	public void kakaoFindDormancyAccount(){
            System.out.println("kakao은행만의 휴면계좌 찾아주기 로직");
        }
    }

    여기서 어떤 문제가 발생할까?

    당연히 금융결제원에서 제공하는 어떠한 서비스도 사용할 수 없으며 

    호환성 없으면 연동이 불가능하다.

     

    즉 Mian 클래스에서 아무리 bank = new kakaoBank(); 를 선언해도 호환되지 않아 에러가 난다.

    public class Main {
    
    	public static void main(String[] args) {
    		Bank bank = new KBBank();
    		bank.withDraw(100);
    		bank.deposit(100);
    		bank.findDormancyAccount("763231");
    		Bank.BCAuth("KBBank");
    
    		// 호환성 불가로 실행하면 에러가 난다.
    		/*
    		bank = new KakaoBank();
    		bank.withDraw(100);
    		bank.deposit(100);
    		bank.findDormancyAccount("4311");
    		*/
    
    	}
    
    }

    하지만 위의 KB은행은 호환되기 때문에 아래와 같은 결과가 나온다.

    KB은행만의 인출 로직...100 원을 인출한다.
    KB은행만의 입금 로직...100 원을 입금한다.
    **금융결제원에서 제공하는 로직**
    KBBank 에서 블록체인 인증을 요청합니다.
    전 금융사 공통 블록체인 로직 수행

     

    인터페이스와 추상클래스의 공통점

    1. 추상 클래스와 인터페이스는 선언만 있고 구현 내용은 없다.

    2. 자기 스스로 new를 통해 객체를 생성할 수 없다.

    3. 상속받은 자식만 객체 생성이 가능하다.

    4. 상속받은 자식이 반드시 구현해야할 때 사용한다.

    5. 선언된 type과 자식의 type이 같아야한다.

     

     

    인터페이스와 추상클래스의 차이점 

    인터페이스 추상클래스
    구현 객체의 같은 동작을 보장하기 위해 사용 추상클래스를 상속받아 기능을 이용, 확장시키기 위해 사용
    다중 상속 가능 다중 상속 불가능
    추상 메소드만 사용가능 일반 메소드 + 추상 메소드 사용가능
    상수, 추상 메소드 형태 일반변수, 일반 메소드, 추상메소드 형태
    생성자와 일반 변수를 가질 수 없다 생성자와 일반 변수 모두 가질 수 있다
    implments 사용 extends 사용
    메소드 선언만 가능 메소드의 부분적인 구현 가능

    인터페이스를 상속받는 클래스는 반드시 인터페이스에 있는 메소드를 다 구현해야 한다.

    추상클래스를 상속받는 클래스는 추상메서드만 구현하고 일반메서드는 사용하지 않아도 문제가 없다.

    댓글

Designed by Tistory.