인터페이스 분리 원칙이란 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.

이 원칙은 큰 덩어리의 인터페이스들은 구체적이고 작은 단위들로 분리 시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다.

이와 같은 작은 단위들을 역할 인터페이스라고 부른다.

인터페이스 분리 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다.

 

단일 책임 원칙(SRP)과 인터페이스 분할 원칙(ISP)의 관계를 생각해보자.

여러 책임을 갖는 클래스가 있다. 이 클래스를 역할에 맞게 책임을 인터페이스 단위로 분리하였다.

이 경우 ISP에 만족 한다고 할 수 있다. 하지만 SRP에 만족하면 ISP 또한 만족되는가?

다음과 같은 경우를 생각해보자

리모컨 기능을 구현한 메서드를 제공해주는 클래스가 있다고 가정하고, 이 클래스는 전원, 음량, 채널, 취침예약을 컨트롤 할 수 있는 기능이 구현된 메소드를 제공해준다. 

하지만 TV에 따라서 리모컨 기능 중 일부분만 사용할 수도 있다.

예를 들어서 신형TV는 취침예약, 인터넷 검색 기능 등 최신 기술들을 지원하지만 저렴한 TV는 지원 하지 않을 수 있다.

이런 경우 리모컨에 관련된 책임을 수행하기 때문에 SRP를 만족한다.

그러나 위 리모컨 인터페이스를 저렴한 TV에서 구현한다면, 사용하지 않는 메소드를 구현해야 함으로 ISP에 위배 된다.  

 

 

 

 

참조 https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EB%B6%84%EB%A6%AC_%EC%9B%90%EC%B9%99

 

인터페이스 분리 원칙 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

  https://defacto-standard.tistory.com/114?category=703460

 

SOLID (5) - 인터페이스 분리 원칙, ISP

인터페이스 분리 원칙 (ISP, Interface Segregation Principle) 프로그래밍 능력에 변화가 생기더라도 외국어 능력이나 발표 능력을 사용하는 영업 업무에는 영향을 미치지 않을 확률이 높지만 개발 업무 부서에는..

defacto-standard.tistory.com

 

의존 역전 원칙이란 의존 관계를 맺을 때 변화하기 쉬운 것 보다 변화하기 어려운 것에 의존하라는 원칙을 의미한다.

DIP를 만족하는 설계는 변화에 유연한 시스템이 된다.

변하기 쉬운 것과 변하기 어려운 것은 어떻게 구분 할 수 있을까?

정책, 전략과 같은 어떤 큰 흐름이나 개념같은 추상적인 것은 변하기 어려운 것이고, 구체적인 방식, 사물 등과 같은 것은 변하기 쉬운 것으로 구분한다.

Person클래스가 무기를 착용하기 위한 클래스 다이어그램

위 클래스 다이어그램을 보면 Weapon은 변하기 어려운 것이고, Sword, Axe, Spear는 변하기 쉬운 것이다.

인터페이스 = 변하지 않는 것
구체 클래스 = 변하기 쉬운 것

DIP를 만족하면 여러가지 타입의 클래스를 인터페이스를 통해 쉽게 저장이 가능하다.

public class Person {

    private Weapon weapon;

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack() {
        weapon.attack();
    }
}

 setWeapon을 통해서 Weapon 인터페이스를 구현한 클래스를 외부에서 주입해 무기를 착용한다. 

public interface Weapon {

    public void attack();
}
public class Sword implements Weapon {

    @Override
    public void attack() {
        System.out.println("검으로 휘두르다.");
    }
}

public class Axe implements Weapon {

    @Override
    public void attack() {
        System.out.println("도끼로 휘두르다");
    }

}

public class Spear implements Weapon {

    @Override
    public void attack() {
        System.out.println("창으로 찌르다");
    }

}

 

public class Main {

    public static void main(String[] args) {
        Person person = new Person();
        person.setWeapon(new Sword());
        person.attack();

        person.setWeapon(new Axe());
        person.attack();
    }
}

 

참조 : https://defacto-standard.tistory.com/113?category=703460

단일 책임의 원칙이란 객체는 단 하나의 책임을 가져야한다는 원칙을 말한다.

객체지향적으로 설계 할 때는 응집도를 높게, 결합도는 낮게 설계하는 것이 좋다.

SRP에 만족하는 설계를 하면 응집도를 높게, 결합도는 낮게 설계 할 수 있다.

한 객체에 책임이 많아질수록 클래스 내부에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높아진다.

즉, 객체마다 책임을 나누지 않는다면 시스템은 매우 복잡해진다.

그리고 시스템에 수정이 생기면, 수정과 관련 없는 기능에 영향이 생길 수 있고, 모두 테스트 해주어야 한다.

단일 책임의 원칙, 하나의 객체에 하나의 책임을 갖게 설계하면, 위와 같은 문제점에서 벗어날 수 있고, 소스의 재사용  및 가독성이 좋아진다. 

하지만 각 객체마다 책임을 적절히 분배하는 것은 쉽지 않다. 책임을 적절히 분배하기 위해서는 프로그램의 기능을 잘 이해하고 기능에 맞게 객체를 설계해야 한다.

개방폐쇄원칙이란 확장에 대해 개방되어 있고, 수정에 대해 폐쇄되어 있는 원칙을 의미한다.

OCP에 만족되는 설계를 하려면 변경되는 소스를 파악하고, 수정 시 영향 받지 않게 해야한다.

이를 만족하기 위해서는 인터페이스를 이용해 기능의 추상화가 이루어져야한다.

 

다음 소스는 OCP를 위배하는 잘못된 예이다. 

 

public class Sword {

    public void attack() {
        System.out.println("검으로 휘두르다.");
    }
}

 

public class Person {
    private Sword sword;
    
    public void setSword(Sword sword) {
        this.sword = sword;
    }
    
    public void attack() {
        sword.attack();
    }
}

 

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setSword(new Sword());
        person.attack();
    }
}

Person 클래스가 Sword를 받아 attack()메소드를 실행하는 간단한 소스이다.

만약 Axe 무기로 변경하고 싶으면 아래와 같이 변경해야한다.

public class Axe {

    public void attack() {
        System.out.println("도끼로 휘두르다");
    }
}

 

public class Person {

    private Axe axe;

    public void setAxe(Axe axe) {
        this.axe = axe;
    }

    public void attack() {
        axe.attack();
    }

}

무기가 새롭게 추가될 때 마다, Person 클래스가 변경되어야한다. 이는 OCP에 위배된다.

OCP에 위배되지 않으려면 변경되는 코드를 확인하고, 기능의 추상화를 해야한다.

 

public interface Weapon {

    public void attack();
}

 

public class Sword implements Weapon {

    @Override
    public void attack() {
        System.out.println("검으로 휘두르다.");
    }
}

 

public class Axe implements Weapon {

    @Override
    public void attack() {
        System.out.println("도끼로 휘두르다");
    }
}

 

public class Person {

    private Weapon weapon;

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack() {
        weapon.attack();
    }
}

 

public class Main {

    public static void main(String[] args) {
        Person person = new Person();
        person.setWeapon(new Sword());
        person.attack();

        person.setWeapon(new Axe());
        person.attack();
    }
}

Weapon Interface를 생성함으로서 attack() 메소드를 추상화 하였다.

Sword, Axe 클래스는 Weapon의 attack() 메소드를 구현하고 Person클래스는 setWeapon(Weapon weapon) 메소드를 통해 무기를 착용하고 각 무기에 맞는 attack() 메소드를 실행 할 수 있다.

새로운 무기 Spear가 추가되어도 Person클래스는 수정없이 Spear무기를 착용 할 수 있다.

+ Recent posts