인터페이스(interface) 개념
자바에서 인터페이스는 클래스들이 필수로 구현해야 하는 추상 자료형이며,
객체 간의 의존성 & 결합도를 낮추기 위해 많이 사용된다.
즉, 확장에는 열려 있고, 변경에는 닫혀 있는 유연한 방식의 개발을 하기 위함이 인터페이스를 사용하는 가장 큰 이유라고 볼 수 있다.
인터페이스(interface)의 특징
1. 다중 상속 가능
- 자바에선 다중 상속을 금지하고 있다.
- 인터페이스의 경우 껍데기만 존재하기 때문에 다중 상속이 가능하다.
2. 메소드 선언 or 상수만 사용 가능
- 인터페이스에선 메소드를 선언만 하며, 구현은 할 수 없다. (추상 메소드)
- 인터페이스를 사용하고자 하는 클래스에서 해당 메소드를 구현하여 사용 해야한다.
- 반대로 생각하면, 인터페이스 내의 모든 메소드를 강제로 구현하게하는 것이다.
인터페이스(interface)의 구조
- 인터페이스는 아래와 같이 메소드의 구현부 없이 선언만 할 수 있음
- default 키워드를 붙이면 구현까지 가능하기는 함
- 변수는 가질 수 없고, 상수는 가질 수 있음
interface Observer {
public void notify();
}
인터페이스 예시
옵저버 패턴 (Observer Pattern)
- 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴
개방-폐쇄 원칙 (OCP, Open-Closed Principle)
- 소프트웨어는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 함
- 특정 모듈을 수정할 때, 그 모듈과 관련된 다른 모듈들을 수정 해야 하면 안됨
- 개방-폐쇄 원칙이 잘 적용되면, 기능을 추가하거나 변경해야 할 때 기존의 코드의 변경 없이 추가나 변경이 가능
- 옵저버 패턴에서도 Subject 객체의 수정 없이 Observer 들을 추가, 삭제, 변경하기 위해서 인터페이스를 통해 일반화하는 것
옵저버 패턴 구조
- Subject(주제 객체) : 데이터를 제공, 상태 변화를 알리는 클래스
- Observer(옵저버 객체) : 주제 객체에서 데이터를 전달해주기를 기다리는 클래스 (알림을 기다리는 입장이므로 의존성이 있다고 할 수 있다.)
- ConcreteObserverA,B : 옵저버를 구현하는 객체. 알림을 받았을 때 해야 할 일을 구현한다.
예제 코드 - 날씨 앱
class WeatherData {
getTemperature()
getHumidity()
getPressure()
measurementsChanged() // 기상 관측값이 갱신되면 해당 메소드가 호출됨
}
안 좋은 예시
- measurementsChanged() 메소드를 통해 화면을 갱신
class WeatherData {
// 인스턴스 변수 선언
private CurrentWeatherDisplay currentWeatherDisplay;
private ForecastWeatherDisplay forecastWeatherDisplay;
pirvate StaticsticDisplay staticsticDisplay;
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentWeatherDisplay.update(temp, humidity, pressure);
forecastWeatherDisplay.update(temp, humidity, pressure);
staticsticDisplay.update(temp, humidity, pressure);
}
}
해당 코드의 문제점
- currentWeatherDisplay.update(...) 처럼 구체적인 구현에 맞춰 구현했기 때문에 다른 화면을 추가하고자 하면 measurementsChanged() 메소드를 고치지 않고서는 불가능하다.
- Display 클래스를 새로 만들고(추가) 다시 measurementsChanged()로 와서 수정 해야 하는 번거로움이 있음
- 위와 같은 이유로 바뀔 수 있는 부분(각 화면 단)을 분리 해야 한다. 위의 코드는 분리 되어있지 않다.
- Display 추가시 소스의 추가가 너무 많아짐 - 확장이 어렵다.
- 특정 Display에 보내는 걸 까먹을 수도 있음 - 실수 유발 가능성
- WeatherData의 일이 많아짐 - 클래스는 하나의 원칙만 다뤄야 하는데 전달에 대한 규모가 커짐
옵저버 패턴을 이용
Subject 인터페이스
interface Subject {
registerObserver() // 옵저버 등록
removeObserver() // 옵저버 삭제
notifyObserver() // 옵저버에게 업데이트 알림
}
Subject 구현체 (변경을 알리는 객체)
class SubjectImpl implements Subject {
registerObserver() { ... } // 옵저버를 등록하는 메소드
removeObserver() { ... } // 옵저버를 제거하는 메소드
notifyObserver() { ... } // 옵저버에게 알리는 메소드
}
Observer 인터페이스
interface Observer{ // 옵저버가 될 객체에서는 반드시 Observer 인터페이스를 구현해야함.
update() // 주제의 상태가 바뀌었을 때 호출됨
// Subject 객체에서 notify 메소드를 실행하면 update() 메소드가 실행 됨
}
Observer 구현체 (변경을 기다리는 객체)
class ObserverImpl implements Observer {
update() {
// Subject 객체에서 알림이 왔을 때 각자 할 일을 작성
}
}
옵저버 패턴이 적용된 날씨 앱
WeatherData 객체에서 Subject를 구현
- WeatherData 객체가 주제 객체가 되고, WeatherData의 상태가 변하면 옵저버들에게 알려주는 것
interface Subject {
registerObserver()
removeObserver()
notifyObserver()
}
class WeatherData implements Subject {
registerObserver()
removeObserver()
notifyObserver()
getTemperature()
getHumidity()
getPressure()
measurementsChanged()
}
Display (화면) 객체에서 구현해야할 인터페이스 정의
interface Observer {
update() // 새로 갱신된 주제 데이터를 전달하는 인터페이스
}
interface DisplayElement {
display() // 화면에 표현시키는 인터페이스
}
위의 인터페이스를 구현한 Display(화면) 객체 생성
class CurrentWeather implements Observer, DisplayElement{
update()
display() { // 현재 측정 값을 화면에 표시 }
}
class ForcastWeather implements Observer, DisplayElement{
update()
display() { // 날씨 예보 표시 }
}
class StatisticsDisplay implements Observer, DisplayElement{
update()
display() { // 평균 기온, 평균 습도 등 표시 }
}
- 이렇게 인터페이스로 구성을 하면 새로운 화면도 쉽게 추가할 수 있다.
- 어떤 객체든 DisplayElement, Observer 인터페이스만 구현하면 되기 때문
class DiscomfortDisplay implements Observer, DisplayElement{
update()
display() { // 불쾌지수를 화면에 표시 }
}
실제 코드
더보기
Interface
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
public void display();
}
WeatherData.java
public class WeatherData implements Subject {
private List<Observer> observers; -> 주제 객체가 옵저버들의 목록을 가지고 있음
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
public void registerObserver(Observer o) { -> 옵저버 추가
observers.add(o);
}
public void removeObserver(Observer o) { -> 옵저버 제거
observers.remove(o);
}
public void notifyObservers() { -> 각 옵저버들에게 알림을 보냄
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure); -> 각 옵저버는 파라미터를 이용하여 각자 데이터를 처리
}
}
public void measurementsChanged() {
notifyObservers(); -> 옵저버들에게 알림
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged(); -> 값이 변경되면 measurementsChanged()를 호출
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
Display
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionsDisplay(WeatherData weatherData) { -> 생성자를 통해 주제 객체를 받아옴
this.weatherData = weatherData;
weatherData.registerObserver(this); -> 가져온 주제 객체를 통해 옵저버로 자신을 등록
}
public void update(float temperature, float humidity, float pressure) { -> 주제 객체에서 알림을 받으면 해당 로직을 처리
this.temperature = temperature;
this.humidity = humidity;
display();
*}*
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
메인
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); -> 디스플레이 추가
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
weatherData.removeObserver(forecastDisplay);
weatherData.setMeasurements(62, 90, 28.1f);
}
}
'JAVA' 카테고리의 다른 글
[ JAVA ] MVP 패턴 + 코드 예시 (0) | 2023.05.17 |
---|