코학다식

Java 시작하기 :: OOP(3) : 인터페이스를 사용한 추상화 본문

Programming/Java

Java 시작하기 :: OOP(3) : 인터페이스를 사용한 추상화

copeng 2019. 9. 26. 21:49

Java 시작하기(6)

Object Oriented Programming(3): abstraction using interfaces

 

 

 

객체 지향 프로그래밍의 네 가지 원칙


 

객체 지향 프로그래밍에는 앞서 살펴보았던 캡슐화를 포함한 네 가지 원칙이 존재한다. 그 원칙들은 다음과 같다.

  • 캡슐화(Encapsulation)
    • public 메서드로 접근을 제한함으로써 내부적 구현을 숨긴다.
    • 인스턴스 변수와 몇 메서드들은 private을 유지한다.
  • 추상화(Abstraction)
    • 구현 없이 명세된 "Interface"의 사용
    • 추상적인 클래스들
  • 상속(Inheritance)

    • 두 객체 사이의 "is-a" 또는 "has-a" 관계

    • super 클래스(부모 클래스) vs sub 클래스(자식 클래스)

    • super 클래스들에 존재하는 코드의 재사용

  • 다형성(Polymorphism)
    • 하나의 이름이 많은 다른 형태를 가질 수 있다.
    • 정적 다형성: 메서드 오버로딩(같은 이름에 매개변수의 타입이나 수가 다른 것)
    • 동적 다형성: 메서드 오버라이딩(추후 설명)

 

 

 

Interface intro


 

  • 정수 시퀀스에서 처음 n개의 숫자의 평균을 구하는 프로그램을 만든다고 가정해 보자.
  • 프로그램에서, 우리는 정수 시퀀스의 평균을 반환하는 static 메서드(average)를 작성한다.
    • 이 메서드는 따로 우리가 정의한 클래스를 매개변수로 받는다.
class IntSequence {
    private int i;

    public boolean hasNext(){
        return true;
    }

    public int next() {
        i++;
        return i;
    }
}

public static double average(IntSequence seq, int n) {
    int count = 0;
    double sum = 0;
    while(seq.hasNext() && count < n) {
        count++;
        sum += seq.next();
    }
    return count == 0 ? 0 : sum/count;
}

 

  • 만약 우리가 다양한 타입의 정수 시퀀스를 가지고 있다고 생각해 보자.
    • 1, 2, 3, 4, 5, ...
    • 1, 4, 9, 16, 25, ...
    • 1792(1, 7, 9, 2)
  • 각각의 시퀀스마다 다른 클래스를 생성한다면, 클래스만큼 많은 메서드가 필요하다.
    • SimpleSequence: public static double average(SimpleSequence seq, int n)
    • SquareSequence: public static double average(SquareSequence seq, int n)
    • DigitSequence: public static double average(DigitSequence seq, int n)
  • 각 클래스마다 메서드를 생성하는 것 대신, 우리는 interface를 사용할 수 있다.

 

 

 

What is Interface?


 

  • 인터페이스(interface)란 자바에서 추상화를 달성하는 방법이다.
  • 메서드의 그룹인 인터페이스를 정의한다.
interface IntSequence {
    boolean hasNext;
    int next();
}

 

  • 이 인터페이스는 어떤 메서드를 사용 가능한지만을 명시하고 있고, 구현(implementation)은 가지고 있지 않다. 그래서 인터페이스는 추상적인 개념이다.
  • 인터페이스에 명시된 모든 메서드는 디폴트로 public이다.
  • 구현이 없는 메서드는 public abstract 메서드라고 불린다.

 

interface IntSequence {
    boolean hasNext;
    int next();
}
  • 실제로 인터페이스를 사용하기 위해서는 인터페이스를 구현한 클래스가 필요하다.

 

class SquareSequence implements IntSequence {
    private i;
    public boolean hasNext() {
        return true;
    }
    public int next() {
        i++;
        return i*i;
    }
}

 

다시 위에서 정의한 메서드 average를 생각해 보자. 이제 우리는 이 메서드를 아래와 같이 호출할 수 있다. 인스턴스의 타입에 주목하자. SquareSequence 클래스는 인터페이스 IntSequence를 구현했으므로, 아래 변수를 메서드의 매개변수로 사용할 수 있다.

 

SquareSequence squares = new SquareSequence();
double avg = average(squares, 100);

 

다른 클래스를 정의하여 마찬가지로 사용할 수 있다.

 

class DigitSequence implements IntSequence {
    private int number;
    public DigitSequence(int n) { number = n; }
    public boolean hasNext() { return number != 0; }
    public int next() {
        int result = number % 10;
        number /= 10;
        return result;
    }
    public int rest() { return number; }
}

DigitSequence digits = new DigitSequence();
double avg = average(digits, 100);

 

 

 

Supertype & subtype


 

  • 우리는 객체를 생성할 때 보통 위와 같이 객체의 타입을 정확히 명시한다.
  • 하지만 우리는 변수를 인터페이스의 타입으로 정의할 수도 있다.
IntSequence digits = new DigitSequence();
double avg = average(digits, 100);

 

  • DigitSequence이 인터페이스 IntSequence를 구현했으므로 위와 같은 사용도 가능하다.

    • IntSequence는 DigitSequence의 supertype이다.
    • DigitSequence는 IntSequence의 subtype이다.
  • supertype의 변수에 subtype의 객체를 할당할 수 있다.

    • 반대의 경우는 명시적으로 type casting을 해 주어야 한다.
    • 이 경우 casting되는 변수가 subtype(혹은 그의 supertype)을 참조하고 있어야 한다.
    • 하지만 다음과 같은 경우는 예외가 발생한다.
    IntSequence sequence = new SquareSequence();
    DigitSequence digits = (DigitSequence)sequence;
    System.out.println(digits.rest());
    • 이때 발생하는 예외를 ClassCastException이라고 하는데, 이를 피하기 위해 instanceof라는 연산자를 사용해서 객체의 타입을 확인할 수 있다. 객체가 어떤 타입의 subtype이라면 true를 반환한다.
  • 인터페이스 타입 변수를 선언하는 건 가능하지만, 인터페이스의 객체는 생성할 수 없다.

 

 

 

Extending interface


 

  • 다른 인터페이스를 extending함으로써 또 다른 인터페이스를 정의할 수 있다.
interface Closeable {
    void close();
}

interface Channel extends Closeable {
boolean isOpen();
}

 

  • 이 경우, 인터페이스 Channel을 구현한 어떤 클래스든 close()isOpen() 메서드를 사용할 수 있다.
  • 클래스는 여러 개의 인터페이스를 구현할 수 있기도 하다.
    • 여러 개의 인터페이스를 구현한 경우 사용한 모든 인터페이스의 메서드를 구현해야 한다.
  • 인터페이스의 정의에 변수를 정의할 수도 있다.
  • 이 변수는 자동적으로 public static final 변수가 된다.
    • 어떤 인스턴스 변수도 인터페이스 정의 안에서 정의될 수 없다.
interface Motion {
    int NORTH = 1;
    int EAST = 2;
    int SOUTH = 3;    
    int WEST = 4;
    void move(int direction);
    int getX();
    int getY();
}

class TwoDMotion implements Motion {
    private int posX, posY;
    public TwoDMotion() { posX = 0; posY = 0; }
    public void move(int direction) {
        if(direction == NORTH) posY--;
        else if(direction == SOUTH) posY++;
        else if(direction == EAST) posX++;
        else if(direction == WEST) posX--;
    }
    public int getX() { return posX; }
    public int getY() { return posY; }
}

 

 

 

Interface methods


 

  • 인터페이스에는 세 타입의 메서드가 포함될 수 있다. (Java9부터)
    • static method
    • default method
    • private method

 

  • Static method

    • 아래의 digitsOf 메서드는 Digitsequence 객체를 생성하고 반환한다.
    • 인스턴스를 생성하는 메서드는 factory method라고 불린다.
      • 이러한 메서드는 인터페이스 정의에서 자주 static으로 정의된다.
    interface IntSequence {
        static IntSequence digitsOf(int n) {
            return new DigitSequence(n);
        }
        boolean hasNext();
        int next();
    }
    
    // Using a factory method to create an object instance
    IntSequence digits = IntSequence.digitsOf(1729);

 

 

 

  • Default method

    • default 메서드가 포함되기 전에는 인터페이스를 구현한 클래스는 인터페이스에 명시된 모든 메서드를 구현해야 했다.
    • 이제 인터페이스를 구현한 클래스는
      • 메서드를 정의하지 않고 default 메서드를 사용하거나
      • default 메서드를 오버라이딩해서 정의할 수 있다.
    interface IntSequence {
        default boolean hasNext() { return true; }
        int next();
    }
    
    class SquareSequence implements IntSequence {
        private int i;
        public int next() {
            i++;
            return i*i;
        }
    }
    • 왜 필요할까?
      • 어떤 인터페이스를 많은 클래스들이 구현했다고 생각해 보자.
      • 나중에, 누군가가 그 인터페이스에 새로운 메서드를 추가했다.
      • 이제 해당 인터페이스를 사용한 모든 클래스에 새로운 메서드를 구현하지 않으면 인터페이스를 사용할 수 없다.
      • 하지만 새로운 메서드가 default로 정의되었다면 구현하지 않아도 그 메서드를 사용할 수 있다.
    • default 메서드를 사용하면, 많은 인터페이스를 구현한 클래스에 충돌이 일어날 수 있다.
      • 다음과 같이 메서드의 헤더가 같은 경우 어떤 것을 선택할지 컴파일러가 결정할 수 없어 에러가 발생한다.
    interface Person {
        String getName();
        default int getId() { return 0; } // Error: duplicate default methods with the same type of parameters
    }
    
    interface Identified {
        default int getId() { return 1; } // Error: duplicate default methods with the same type of parameters
    }
    
    class Employee implements Person, Identified {
        private String name;
        public Employee(String name) { this.name = name; }
        public String getName() { return this.name; }
    }
    
    public class Lecture {
        public static void main(String[] args) {
            Employee m = new Employee("Peter");
            System.out.println(m.getId());
        }
    }
    • 이는 한 메서드를 default가 아닌 것으로 바꾸어도 마찬가지이다.
      • 이런 경우, 클래스에 getId()를 구현해 주면 문제가 해결된다.
      • 혹은 매개변수의 타입이나 수가 다른 경우, 모호함이 해결되므로 문제가 해결된다.

 

 

  • Private method

    • private 메서드는 클래스의 다른 메서드에서만 호출될 수 있다.
    interface CustomInterface {
    
        public abstract void method1();
    
        public default void method2() {
            method4(); // private method inside default method
            method5(); // static method inside other non-static method
            System.out.println("default method");
        }
    
        public static void method3() {
            method5(); //static method inside other static method
            System.out.println("static method");
        }
    
        private void method4() {
            System.out.println("private method");
        }
    
        private static void method5() {
            System.out.println("private static method");
        }
    }
    
    public class Lecture implements CustomInterface {
        public void method1() {
            System.out.println("abstract method");
        }
    
        public static void main(String[] args){
            CustomInterface instance = new Lecture();
            instance.method1();
            instance.method2();
            CustomInterface.method3();
        }
    }
    /* Outputs----------------------------------------------------------------------
    abstract method // by instance.method1();
    private method // by instance.method2();
    private static method // by instance.method2();
    default method // by instance.method2();
    private static method // by instance.method3();
    static method // by instance.method3();
    ------------------------------------------------------------------------------*/
  • Private 인터페이스 메서드의 규칙

    • abstract일 수 없다.
    • 인터페이스 안에서만 사용될 수 있다.
    • Private static 메서드는 다른 static 그리고 non-static 메서드 안에서 사용될 수 있다.
    • Private non-static 메서드는 private static 메서드 안에서 사용될 수 없다.
      • static 메서드는 클래스에 속하는 메서드임을 생각하자.
      • 따라서 static 메서드는 같은 static 메서드(Private static)만을 호출할 수 있다. (method3의 경우)
      • 나머지 다른 메서드의 경우 private, private static 메서드 둘 다 호출 가능하다. (method2의 경우)
Comments