코학다식
Java 시작하기 :: OOP(3) : 인터페이스를 사용한 추상화 본문
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)
- SimpleSequence:
- 각 클래스마다 메서드를 생성하는 것 대신, 우리는 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
의 경우)
'Programming > Java' 카테고리의 다른 글
Java 시작하기 :: OOP(2): 클래스, 변수, 메서드 (0) | 2019.09.24 |
---|---|
Java 시작하기 :: OOP(1): 클래스, 변수, 메서드 (0) | 2019.09.24 |
Java 시작하기 :: 자바 프로그래밍 기초(2) (0) | 2019.09.10 |
Java 시작하기 :: 자바 프로그래밍 기초 (0) | 2019.09.08 |
Java 시작하기 :: 자바란 무엇일까? (0) | 2019.09.04 |