-
자바 nextstep - 인터페이스, 전략패턴카테고리 없음 2023. 7. 25. 16:59
1. 인터페이스란? (사전적의미)
- 하나의 시스템을 구성하는 2개의 구성요소(하드웨어,소프트웨어) 또는 2개의 시스템이
상호 작용할 수 있도록 접속되는 경계
- 이 경계에서 상호 접속하기 위한 조건, 규약 등을 포괄적으로 가리키는말
2. 인터페이스 예시
JDBC를 통해 이해해보기
-> DB를 교체해야할 때 만약 DB를 담당하는 코드와 자바코드가 밀접하게 연결되어 있다면,
DB를 교체해야할 때 많은 비용과 시간이 발생할 것이다 (전부 수정)
-> 이를 위한 해결방법으로 인터페이스를 사용한다
JDBC(DB연결/인증, SQL쿼리생성/문장실행, 결과패치) 인터페이스 [규약,표준 생성]
-> Mysql,oracle 등이 이에 맞게 구현체 만듦
-> DB작업을 할 때 이제 JDBC에서 제공하는 API만을 사용함 (내부 구현은 각 DB별로 따로 되어 있다)
3. 자바에서 interface란
- 서비스 집중화 보단, 분리와 다형성에 더 초점이 맞춰진다.
- 인터페이스는 더 높은 추상화를 위해 사용된다.
- 구현 로직은 존재하지 않으며, 메소드에 대한 입력과 출력만 정의한다.
- 인터페이스를 통해 추상화를 하는 이유는 소프트웨어 변경이 발생할 경우 소스 코드 변경을 최소화하여
유지보수 비용을 줄이고, 변화에 빠르게 대응하기 위함이다.
- 단 추상화를 위해 개발 비용이 증가한다.
4. 인터페이스 활용 예시
- 인터페이스를 만들 때는 특정 부품을 떠올려보자.
- 부품을 만들 수 있는 틀만을 제공하고, 세부 내용은 필요에 따라 구현하도록 분리한다고 생각하자
- 특정 부분들을 집중화보단 도구화한다고 생각해보자.
- 이러한 인터페이스는 하나의 캡슐, 일부 기능, 코드의 일부 구간 3가지 패턴에서 마감 기능을 할 수 있다.
4.1 캡슐
- 필요에 따라 클래스를 교체해서 사용해야할 때 인터페이스를 사용하면, 내부 구현 클래스가 뭔지는 상관없이
참조변수 B에 담아서 사용하면 된다.
- main메서드에서 B1혹은 B2이 필요하다고 해보자
- B1, B2를 그냥 생성할 수도 있지만, B라는 인터페이스를 통해서 생성할 수도 있다.
- 이때 소스코드에는 변화를 주지 않고 변경하는 방법은 아래와 같다.
public static void main(String[] args) throws Exception{ InputStream is = Main.class.getResourceAsStream("newinstance.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(is)); Class bclazz = Class.forName(br.readLine()); B b = (B)bclazz.newInstance(); b.printHowis(); }
public interface B { void printHowis(); } public class B1 implements B{ @Override public void printHowis() { System.out.println("this is B1"); } } public class B2 implements B{ @Override public void printHowis() { System.out.println("This is B2"); } }
//newinstance.txt interfaces.B2
4.2 일부 기능
private static int sumFunc(sumFunction f,List<Integer> list){ return f.listsum(list); } //list를 더하는 기능을 인터페이스를 통해서 구현하도록 만들었다.
public static void main(String[] args) throws Exception{ List<Integer> list = List.of(1,2,3,4,5,6,7,8,9,10); sumFunction f1 = (list1) ->{ int result =0; for(int i : list1){ result +=i; } return result; }; sumFunction f2 = new sumFunction() { @Override public int listsum(List<Integer> list) { int result=0; for(int i : list){ result += i*2; } return result; } }; System.out.println(sumFunc(f1,list)); System.out.println(sumFunc(f2,list)); }
어떤 인터페이스를 집어넣느냐에 따라 결과가 다를 것이다.
4.3 코드 일부 구간
move를 처리하는 조건문을 인터페이스로 처리하였다.
public void move(MoveStretergy moveStretergy){ if(moveStretergy.moveable()) pos =pos.move(); }
public class RadomMoving implements MoveStretergy{ private static final int MAX_BOUND =10; private static final int MOVABLE =4; @Override public boolean moveable() { return getRadomNum() >= MOVABLE; } private int getRadomNum() { return new Random().nextInt(MAX_BOUND); } }
어떤 구현 클래스를 매개변수로 전달하느냐에 따라 조건문의 결과가 바뀔 것이고
각 구현 클래스는 요구사항에 맞게 자유롭게 오버라이딩 하면 된다.
5. 전략패턴(Stratey Pattern)
- 알고리즘 패밀리를 정의, 각 패밀리를 별도의 클래스에 넣은 후 객체들이 상호교환 하도록 하는 행동 디자인 패턴
- 실행 중 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 한다.
- 전략 패턴이란 객체들이 할 수 있는 행위 가각에 대해 전략 클래스를 생성
- 유사한 행위들을 캡슐화 하는 인터페이스 정의
- 객체 행위를 동적으로 바꾸고 싶지 않을 경우 직접 행위를 수정하지 않고, 전략만 바쭘
- 행위를 유연하게 확장하는 것을 의미한다.
5.1 전략 패턴의 구조
1. context : 특정 알고리즘을 실행해야할 때 해당 알고리즘과 연결된 전략 객체의 메서드 호출
2. 전략 인터페이스: 모든 전략 구현체에 대한 공용 인터페이스
3.전략 알고리즘 객체들: 알고리즘 행위, 동작을 객체로 정의한 구현체
4. 클라이언트: 특정 전략 객체를 컨텍스트에 전달하여 전략을 등록한다.
//전략 알고리즘 interface IStrategy { void doSomething(); } // 전략 알고리즘 구현체 class ConcreteStratey1 implements IStrategy { public void doSomething() {} } // 전략 알고리즘 구현체 class ConcreteStratey2 implements IStrategy { public void doSomething() {} }
// 컨텍스트(전략 등록/실행) class Context { IStrategy Strategy; // 전략 인터페이스를 합성(composition) // 전략 교체 메소드 void setStrategy(IStrategy Strategy) { this.Strategy = Strategy; } // 전략 실행 메소드 void doSomething() { this.Strategy.doSomething(); } }
// 클라이언트(전략 교체/전략 실행한 결과를 얻음) class Client { public static void main(String[] args) { // 1. 컨텍스트 생성 Context c = new Context(); // 2. 전략 설정 c.setStrategy(new ConcreteStrateyA()); // 3. 전략 실행 c.doSomething(); // 4. 다른 전략 설정 c.setStrategy(new ConcreteStrateyB()); // 5. 다른 전략 시행 c.doSomething(); } }
5.2 예시 1
- 위와 같이 Train과 Bus 클래스가 있고, 두 클래스가 Movable인터페이스를 구현했다고 가정
- 이 두 클래스를 사용하는 Client도 있다
public interface Movable{ public void move(); } public class Train implements Movable{ public void move(){ System.out.println("선로를 통해 이동"); } } public class Bus implements Movable{ public void move(){ System.out.println("도로를 통해 이동"); } } public class Client { public static void main(String args[]){ Movable train = new Train(); Movable bus = new Bus(); train.move(); bus.move(); } }
- 추후에 Bus가 움직이는 방식이 바뀐다면, Bus에 move메서드를 직접 수정해야한다.
- 이는 SOLID원칙 중 OCP에 위배된다. 기존의 move()를 수정하지 않으면서 행위를 수정할 수 있는
방법을 찾아야 한다.
- 혹은 추후에 택시, 자가용, 고속버스가 버스의 move()와 동일하게 변경되었다면, 모든 move()를 수정해야하는
번거로움이 발생한다(동일 메서드 중복)
5.2.2
1. move에 대한 전략 인터페이스 생성 -> 각 전략에 맞게 이를 구현한 구현체 생성
public interface MovableStrategy { public void move(); } public class RailLoadStrategy implements MovableStrategy{ public void move(){ System.out.println("선로를 통해 이동"); } } public class LoadStrategy implements MovableStrategy{ public void move() { System.out.println("도로를 통해 이동"); } }
2. 각 운송수단에 해당하는 클래스는 위 movableStrategy 구현 클래스를 주입 받아 move메서드를
사용할 수 있도록 변경
public class Moving //컨텍스트 { private MovableStrategy movableStrategy; public void move(){ movableStrategy.move(); } public void setMovableStrategy(MovableStrategy movableStrategy){ this.movableStrategy = movableStrategy; } } public class Bus extends Moving{ } public class Train extends Moving{ }
3. 운송수단을 사용하는 클라이언트에서 각 운송 수단에 맞는 전략을 주입
public class Client { public static void main(String args[]){ Moving train = new Train(); Moving bus = new Bus(); /* 기존의 기차와 버스의 이동 방식 1) 기차 - 선로 2) 버스 - 도로 */ train.setMovableStrategy(new RailLoadStrategy()); bus.setMovableStrategy(new LoadStrategy()); train.move(); bus.move(); /* 선로를 따라 움직이는 버스가 개발 */ bus.setMovableStrategy(new RailLoadStrategy()); bus.move(); } }
5.3 예시 2 RPG게임
https://refactoring.guru/ko/design-patterns/strategy
https://victorydntmd.tistory.com/292