The Office Lover
인터페이스 분리 원칙 - interface segregation principle, ISP 본문
인터페이스 분리 원칙 - interface segregation principle, ISP
Michael Gary Scott 2023. 7. 28. 10:59
인터페이스 분리 원칙을 알기에 앞서 인터페이스에 대해 먼저 알고 싶으시면 아래 링크를 확인해 주세요.
2023.06.05 - [Design Patterns] - 인터페이스를 이해하는 다양한 방법
소개
- 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다.
(클라이언트 = 인터페이스 호출자, 사용자) - 인터페이스 분리 원칙(ISP)은 인터페이스를 작은 단위로 분리하여 클라이언트가 필요로 하는 기능만을 제공하도록 함으로써 결합도를 낮추고 유연한 코드를 작성하는데 도움을 줍니다.
- 인터페이스 분리 원칙(ISP)에서 이야기하는 인터페이스는 크게 세 가지 중 하나릉 의미합니다.
- API나 기능의 집합
- 단일 API 또는 기능
- 객체지향 프로그래밍의 인터페이스
인터페이스 분리 원칙(ISP)을 적용하는 방법
1. 클라이언트에 특화된 인터페이스 설계하기
- 클라이언트는 자신이 필요로 하는 기능만을 가진 작은 인터페이스를 사용해야 합니다.
- 하나의 큰 인터페이스보다 여러 개의 작은 인터페이스가 더욱 유용합니다.
- 이렇게 해야만 클라이언트가 필요하지 않은 메서드에 의존하지 않게 됩니다.
[예시]
// 이동 수단을 위한 기본 인터페이스
interface Movable {
void move();
}
// 자동차를 위한 인터페이스
interface Car extends Movable {
void honk();
}
// 비행기를 위한 인터페이스
interface Airplane extends Movable {
void fly();
}
// 자동차 구현 클래스
class Sedan implements Car {
@Override
public void move() {
System.out.println("Sedan is moving on the road.");
}
@Override
public void honk() {
System.out.println("Sedan is honking.");
}
}
// 비행기 구현 클래스
class Boeing747 implements Airplane {
@Override
public void move() {
System.out.println("Boeing 747 is moving on the runway.");
}
@Override
public void fly() {
System.out.println("Boeing 747 is flying in the sky.");
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Car sedanCar = new Sedan();
Airplane boeing747Airplane = new Boeing747();
// 자동차 기능 사용
sedanCar.move();
sedanCar.honk();
// 비행기 기능 사용
boeing747Airplane.move();
boeing747Airplane.fly();
}
}
위 예시를 살펴보면 인터페이스를 적절하게 분리하여 클라이언트에 특화된 기능을 제공하고 있습니다. Car 인터페이스는 자동차와 관련된 기능을 가지고 있고, Airplane 인터페이스는 비행기와 관련된 기능을 제공합니다. 모두 Movable 인터페이스를 상속받아 이동 기능을 공통으로 가지고 있습니다.
클라이언트 코드인 Client 클래스에서는 Sedan과 Boeing747 객체를 생성하여 각가의 인터페이스를 통해 클라이언트에 특화된 기능을 사용할 수 있습니다.
이렇게 작은 단위로 인터페이스를 분리함으로써, 클라이언트는 자신이 필요로 하는 기능만을 사용할 수 있으며, 불필요한 의존성을 제거하여 코드의 유지보수성과 재사용성을 향상할 수 있습니다.
2. 다중 구현 고려하기
- 인터페이스는 다중 구현 가능성을 고려하여 설계해야 합니다. 여러 클래스들이 같은 인터페이스를 사용하며, 각자 필요한 기능에 맞게 선택하여 구현할 수 있습니다.
[예시]
// 이동 수단을 위한 인터페이스
interface Movable {
void move();
}
// 차량을 위한 인터페이스
interface Vehicle extends Movable {
void honk();
}
// 비행기를 위한 인터페이스
interface Airplane extends Movable {
void fly();
}
// 배를 위한 인터페이스
interface Ship extends Movable {
void sail();
}
// 자동차 구현 클래스
class Car implements Vehicle {
@Override
public void move() {
System.out.println("Car is moving on the road.");
}
@Override
public void honk() {
System.out.println("Car is honking.");
}
}
// 비행기 구현 클래스
class Airplane implements Airplane {
@Override
public void move() {
System.out.println("Airplane is moving on the runway.");
}
@Override
public void fly() {
System.out.println("Airplane is flying in the sky.");
}
}
// 배 구현 클래스
class CargoShip implements Ship {
@Override
public void move() {
System.out.println("Cargo ship is moving on the sea.");
}
@Override
public void sail() {
System.out.println("Cargo ship is sailing on the sea.");
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Vehicle car = new Car();
Airplane airplane = new Airplane();
Ship cargoShip = new CargoShip();
// 다양한 이동 수단 사용
car.move();
car.honk();
airplane.move();
airplane.fly();
cargoShip.move();
cargoShip.sail();
}
}
위의 예시에서 Vehicle, Airplane, Ship 인터페이스는 각각 다른 이동 수단들을 위한 인터페이스입니다. Car, Airplane, CargoShip 클래스들은 각각 Vehicle, Airplane, Ship 인터페이스를 구현하며, 해당 인터페이스에서 제공하는 기능들을 구현합니다.
Client 클래스에서는 각각의 수단을 생성, 해당 인터페이스를 통해 다양한 이동 기능을 사용할 수 있습니다. 이렇게 다중 구현 가능성을 고려하여 인터페이스를 설계함으로써, 각각의 클래스들이 같은 인터페이스를 사용할 수 있고, 자신에게 필요한 기능만을 구현할 수 있습니다. 이는 유연하고 확장 가능한 시스템을 구축하는데 도움이 됩니다.
3. 인터페이스 간 의존성 최소화하기
- 인터페이스 간의 의존성을 최소화하여 결합도를 낮추는 것이 중요합니다.
- 인터페이스 간에 서로 의존하는 경우, 한 인터페이스가 변경되면 그에 의존하는 다른 인터페이스도 변경될 수 있기 때문입니다.
[예시]
// 이동 수단을 위한 기본 인터페이스
interface Movable {
void move();
}
// 자동차를 위한 인터페이스
interface Car {
void honk();
}
// 비행기를 위한 인터페이스
interface Airplane {
void fly();
}
// 자동차 구현 클래스
class Sedan implements Movable, Car {
@Override
public void move() {
System.out.println("Sedan is moving on the road.");
}
@Override
public void honk() {
System.out.println("Sedan is honking.");
}
}
// 비행기 구현 클래스
class Boeing747 implements Movable, Airplane {
@Override
public void move() {
System.out.println("Boeing 747 is moving on the runway.");
}
@Override
public void fly() {
System.out.println("Boeing 747 is flying in the sky.");
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Movable sedanCar = new Sedan();
Movable boeing747Airplane = new Boeing747();
// 자동차 기능 사용
sedanCar.move();
Car sedanAsCar = (Car) sedanCar; // Movable을 Car로 다운캐스팅
sedanAsCar.honk();
// 비행기 기능 사용
boeing747Airplane.move();
Airplane boeing747AsAirplane = (Airplane) boeing747Airplane; // Movable을 Airplane으로 다운캐스팅
boeing747AsAirplane.fly();
}
}
위의 예시처럼 Movable 인터페이스는 Car, Airplane 인터페이스와 의존성이 없도록 설계되었습니다. 각 인터페이스는 독립적으로 사용될 수 있고, 구현 클래스들은 필요한 인터페이스를 모두 구현합니다.
인터페이스 분리 원칙(ISP) 위반 예시
가장 대표적인 예시 중 하나는 Square-Rectangle Problem입니다. 이는 정사각혀(Square)과 직사각형(Rectangle) 사이의 상속 관계에서 발생합니다.
보통 정사각형은 직사각형의 특별한 형태로 간주하여 Square 클래스가 Rectangle 클래스를 상속받도록 설계하는 경우를 말합니다.
하지만 이러한 설계는 리스코프 치환 원칙(LSP)을 위반하게 됩니다.
2023.07.27 - [Design Patterns] - 리스코프 치환 원칙 - Liskov Substitution Principle, LSP
[예시]
// 이동 수단을 위한 인터페이스 (ISP 위반)
interface Movable {
void move();
void fly(); // 자동차에 필요 없는 기능이 포함되어 있음
}
// 자동차 클래스
class Car implements Movable {
@Override
public void move() {
System.out.println("Car is moving on the road.");
}
@Override
public void fly() {
// 자동차에는 비행 기능이 필요 없으므로 빈 메서드로 구현
}
}
// 비행기 클래스
class Airplane implements Movable {
@Override
public void move() {
System.out.println("Airplane is moving on the runway.");
}
@Override
public void fly() {
System.out.println("Airplane is flying in the sky.");
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Movable car = new Car();
Movable airplane = new Airplane();
// 자동차와 비행기 모두 이동과 비행 기능을 사용하려고 함
car.move();
car.fly(); // 실제로 비행하지 않는데도 불필요한 메서드를 호출하고 있음
airplane.move();
airplane.fly();
}
}
위의 예시에서 Movable 인터페이스는 move() 메서드와 fly() 메서드를 함께 포함하고 있습니다. 하지만 Client 클래스에서 자동차 생성 후에 불필요한 메서드인 fly() 메서드를 빈 메서드로 구현하고 있습니다.
이러한 상황에서는 자동차와 비행기를 각각 별도의 인터페이스로 분리하여 인터페이스 분리 원칙(ISP)을 준수하는 것이 바랍직합니다.
결론
- 인터페이스 분리 원칙(ISP)은 클라이언트가 필요로 하는 기능만을 포함하도록 작은 인터페이스를 설계함으로써 불필요한 의존성을 제거하고 유연하고 재사용 가능한 코드를 작성하는데 도움을 줍니다.
- 인터페이스 분리 원칙(ISP)을 지키면 유지보수성이 높은 프로그래밍을 할 수 있으며, 더 나아가 SOLID 원칙 전체를 따르는 객체지향 프로그래밍의 원칙적인 설계를 구현할 수 있습니다.
2023.07.27 - [Design Patterns] - 단일 책임 원칙 - single responsibility principle, SRP
2023.06.08 - [Design Patterns] - 디자인 패턴 - 개방 폐쇄 원칙
참고 서적 : 디자인패턴의 아름다움
'Design Patterns' 카테고리의 다른 글
의존 역전 원칙 - dependency inversion principle (0) | 2023.08.10 |
---|---|
리스코프 치환 원칙 - Liskov Substitution Principle, LSP (0) | 2023.07.27 |
단일 책임 원칙 - single responsibility principle, SRP (0) | 2023.07.27 |
디자인 패턴 - 개방 폐쇄 원칙 (0) | 2023.06.08 |
디자인 패턴 - 팩토리 패턴(factory pattern) (0) | 2023.06.05 |