* JAVA - 인터페이스 (2)

jodbsgh·2022년 3월 27일
0

💡"JAVA"

목록 보기
27/67

인터페이스의 장점

  • 개발시간을 단축시킬 수 있다.
  • 표준화가 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능하다.

1. 개발시간을 단축시킬 수 있다.

인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.

그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발으 ㄹ진행할 수 있다.

2. 표준화가 가능하다.

프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.

서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.

4. 독립적인 프로그래밍이 가능하다.

인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

//수리가능한 유닛들 오버로딩 메서드 정의

void repair(Tank t){
	//Tank를 수리한다.
}

void repair(Dropship d){
	//Dropship을 수리한다.
}

이런 식으로 수리가 가능한 유닛의 개수만큼 다른 버전의 오버로딩된 메서드를 정의해야 할 것 이다.

매개변수 타입을 이 들의 공통 조상으로 하면 좋겠지만 Dropship은 공통조상이 다르기 때문에 공통조상의 타입으로 메서드를 정의한다고 해도 최소한 2개의 메서드가 필요할 것이다.

예를들면 공중유닛, 지상유닛처럼 말이다.

이를 해결하기 위해서는

다음과 같이 Repairable 이라는 인터페이스를 정의하고 수리가 가능한 기계화 유닛에게 이 인터페이스를 구현하도록 하면 된다.

	interface Repairable	{}
    
    class SCV extends GroundUnit implements Repairable{
    	//...
    }
    
      class Tnak extends GroundUnit implements Repairable{
    	//...
    }
    
      class Dropship extends GroundUnit implements Repairable{
    	//...
    }

이 3개의 클래스에는 같은 인터페이스를 구현했다는 공통점이 생겼다. 인터페이스 Repairable 에 정의된 것은 아무것도 없고, 단지 인스턴스의 타입체크에만 사용될 분이다.

repair메서드의 매개변수의 타입을 Repairable로 선언하면, 이 메서드의 매개변수로 Repairable 인터페이스를 구현한 클래스의 인스턴스만 받아들여질 것이다.

	void repair(Repairable r)
    {
    	// 매개변수로 넘겨받은 유닛을 수리한다.
    }

새로운 클래스가 추가될 때, SCV의 repair메서드에 의해서 수리가 가능하도록 하려면 Repairable 인터페이스를 구현하도록 하면 될것이다.

예제)

class RepairableTest{
	public static void main(String args[])
    {
    	Tank tank = new Tank();
        Dropship dropship = new Dropship();
        
        Marine marine = new Marine();
        SCV scv = new SCV();
        
        scv.repair(tank);
        scv.repair(dropship);
        scv.repair(marine);	//에러 repair를 지원하지 않는다.
    }
}

interface Repairable { }

class Unit{
	int hitPoint;
    final int MAX_HP;
   
   	Unit (int hp){
    	MAX_HP = hp;
    }
    //...
}

class GroundUnit extends Unit{
	GroundUnit (int hp){
    	super(hp);	
    }
}

class AirUnit extends Unit{
	AirUnit (int hp){
    	super(hp);	
    }
}

class Tank extends GroundUnit implements Repairable{
	Tank()
    {
    	super(150);	//Tank HP는 150이다.
        hitPoint = MAX_HP;
    }
    
    public String toString()
    {
    	return "Tank";
    }
    // ....
}

class Dropship extends AirUnit implements Repairable{
	Dropship()
    {
    	super(125);	//Dropship HP는 150이다.
        hitPoint = MAX_HP;
    }
    
    public String toString()
    {
    	return "Dropship";
    }
    // ....
}

class Marine extends GroundUnit {
	Marine()
    {
    	super(40);
        hitPoint=MAX_HP;
    }
}

class SCV extends GroundUnit implements Repairable{
	SCV()
    {
    	super(60);
        hitPoint=MAX_HP;
    }
    
    void repair(Repairable r)
    {
    	if(r instanceof Unit)
        {
        	Unit u = (Unit) r;
            while(u.hitPoint != u.MAX_HP)
            {
            	/* Unit의 현재 HP를 증가시킨다 */
                u.hitPoint++;
            }
            System.out.println( u.toString() + "의 수리가 끝났습니다.");
        }
    }
}

실행결과

Tank의 수리가 끝났습니다.
Dropship의 수리가 끝났습니다.

인터페이스의 이해

  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  • 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다.)
	class B{
    	public void methodB()
        {
        	System.out.println("methodB()");
        }
    }
    
    class InterfaceTest{
    	public static void main(String args[])
        {
        	A a = new A();
            a.methodA(new B());
        }
    }
    
 실행결과
 methodB()

이 경우 클래스 A를 작성하려면 클래스 B가 이미 작성되어 있어야 한다. 또한, 클래스 B의 methodB()의 선언부가 변경되면, 이를 사용하는 클래스 A도 변경되어야 한다.

이처럼 직접적인 관계의 두 클래스는 한 쪽이 변경되면 다른 한 쪽도 변경되아야 한다는 단점이 있다.

그러나 클래스 A가 클래스 B를 직접호출하지 않고 인터페이스를 매개체로 해서 클래스A가 인터페이스를 통해서 클래스 B의 메서드에 접근하도록 하면,

클래스 B에 변경사항이 생기거나 클래스 B와 같은 기능의 다른 클래스로 대체 되어도 클래스 A는 전혀 영향을 받지 않도록 하는 것이 가능하다.

두 클래스간의 관계를 간접적으로 변경하기 위해서는 먼저 인터페이스를 이용해서 클래스 B의 선언과 구현을 분리해야 한다.

	interface I {
    	public abstract void methodB();
    }

그 다음에는 클래스 B가 인터페이스 I를 구현하도록 한다.

	class B implements I{
    	public void methodB()
        {
        	System.out.println("methodB in B class");
        }
    }

이제 클래스 A는 클래스 B대신 인터페이스 I를 사용해서 작성할 수 있다.

class A{
	public void methodA(I i)
    {
    	i.methodB();
    }
}

클래스 A를 작성하는데 있어서 클래스 B가 사용되지 않았다는 점에 주목하자.
이제 클래스 A와 B는 'A-B'의 직접적인 관계에서 'A-I-B'의 간접적인 관계로 바뀐 것이다.

클래스A는 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않는다.

클래스 A는 인터페이스를 통해 실제로 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제되지 않는다.

클래스 A는 오직 직접적인 관계에 있는 인터페이스 I의 영향만 받는다.
인터페이스 I는 실제구현 내용을 감싸고 있는 껍데기며, 클래스A는 껍데기 안에 어떤 알맹이(클래스)가 들어 있는지 몰라도 된다.

예제)

class A{
	void autoPlay(I i){
    	i.play();
    }
}

interface I{
	public abstract void play();
}

class B implements I{
	public void play()
    {
    	System.out.println("pay in B class");
    }
}

class C implements I{
	publoc void play()
    {
    	System.out.println("play in C calss");
    }
}

class InterfaceTest2{
	public static void main(String args[])
    {
    	A a = new A();
        a.autoPlay(new B());	//void autoPlay(I i); 호출
        a.autoPlay(new C());	//void autoPlay(I i); 호출
    }
}

실행결과

play in B class
play in C class

클래스 A가 인터페이스 I를 사용해서 작성되긴 하였지만, 이처럼 매개변수를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 동적으로 제공받아야 한다.

class InterfaceTest3{
	public static void main (String args[])
    {
    	A a = new A();
        a.methodA();
    }
}

class A{
	void methodA()
    {
    	//제 3의 클래스의 메서드를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 얻어온다.
    	I i = InstanceManager.getInstance();
        i.methodB();
        System.out.println(i.toString()); // i로 Object 클래스의 메서드 호출 가능
    }
}

interface I{
	public abstract void methodB();
}

class B implements I {
	public void methodB()
    {
    	System.out.println("methodB in B calss");
    }
    
    public String toString() {return "class B"};
}

class InstanceManager{
	public abstract I getInstance()
    {
    	return new B();
    }
}

실행결과

methodB in B class
class B

인스턴스를 직접 생성하지 않고, getInstance()라는 메서드를 통해 제공받는다.

이렇게 하면 나중에 다른 클래스의 인스턴스로 변경되어도 A클래스의 변경없이 getInstace()만 변경하면 된다는 장점이 있다.

	class InstanceManager{
    	public static I getInstance(){
        	return new B();	//다른 인스턴스로 바꾸려면 여기만 변경하면 됨.
        }
    }

인터페이스 I타입의 참조변수 i로도 Object클래스에 정의된 메서드들을 호출할 수 있다는 것도 알아두자. i에 toString()이 정의되어 있지 않지만, 모든 갹체는 Object클래스에 정의된 메서드를 가지고 있을 것이기 때문에 허용하는 것이다.

	class A {
    	void methodA(){
        	I i = InstanceManager.getInstance();
            i.methodB();
            System.out.println(i.toString());	//i로 Object의 메서드 호출가능
        }
    }

디폴트 메서드

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

interface MyInterface{
	void method();
    void newMethod();	//추상 메서드
}


/////////////////////////////////////////////////
interface MyInterface{
	void method();
    default void newMethod(){}
}

위같이 newMethod()라는 추상 메서드를 추가하는 대신, 디폴트 메서드를 추가하면, 기존의 MyInterface를 구현한 클래스를 변경하지 않아도 된다.

즉, 조상 클래스에 새로운 메서드를 추가한 것과 동일해 지는 것이다.

대신, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생한다.

  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    -인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
  2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
    -조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
예제)

class DefaultMethodTest{
	public static void main(String[] args){
   Child c = new Child();
   c.method();
   c.method2();
   MyInterface.staticMethod();
   MyInterface2.staticMethod();
   }
}

class Child extends Parent implements MyInterface, MyInterface2{
	public void method1(){
		System.out.println("method1() in Child");	//오버라이딩    
   }
}

class Parent{
	public void method2()
   {
   	System.out.println{"method2() in Parent");
   }
}

interface MyInterface{
	defualt void method1(){
   	System.out.println("method1() in MyInterface");
   }
   
   defualt void method2(){
   	System.out.println("method2() in MyInterface");
   }
   
   static void staticMethod(){
   	System.out.println("staticMethod() in MyInterface");
   }
}


interface MyInterface2{
	defualt void method1(){
   	System.out.println("method1() in MyInterface2");
   }
   
   
   static void staticMethod(){
   	System.out.println("staticMethod() in MyInterface2");
   }
}

실행결과

method1() in Child
method2() in Parent
staticMethod() in MyInterface
staticMethod() in MyInterface2
profile
어제 보다는 내일을, 내일 보다는 오늘을 🚀

0개의 댓글