본문 바로가기

Language/Java

[Java8] Strategy Pattern, FP, 1급 객체

사칙 연산을 해보자

public class cal {
	public static void main(String[] args) {
		int addResult = Calculator.add(1, 2); // 3
		int subResult = Calculator.subtract(1, 2); // -1
		int mulResult = Calculator.multiply(1, 2); // 2
		int divResult = Calculator.divide(1, 2); // 0
	}
}

class Calculator{
	public static int add(int i, int j) {
		return i+j;
	}
	public static int subtract(int i, int j) {
		return i-j;
	}
	public static int multiply(int i, int j) {
		return i*j;
	}
	public static int divide(int i, int j) {
		return i/j;
	}
}

보통의 나라면 이런식으로 인스턴스 메소드를 만들어서 처리했지 않을까 싶다.

그런데 만약 앞의 수보다 뒤의 수가 작을 경우 

연산의 순서를 바꾸고 싶다면 다음과 같이 수정해야 할 것이다.

public class cal {
	public static void main(String[] args) {
		int addResult = Calculator.add(1, 2); //3
		int subResult = Calculator.subtract(1, 2); //1
		int mulResult = Calculator.multiply(1, 2); //2
		int divResult = Calculator.divide(1, 2); //2
	}
}

class Calculator{
	public static int add(int i, int j) {
		if(i<j) {
			return j+i;
		}
		return i+j;
	}
	public static int subtract(int i, int j) {
		if(i<j) {
			return j-i;
		}
		return i-j;
	}
	public static int multiply(int i, int j) {
		if(i<j) {
			return j*i;
		}
		return i*j;
	}
	public static int divide(int i, int j) {
		if(i<j) {
			return j/i;
		}
		return i/j;
	}
}

모든 메소드에 조건문을 달아서 처리해야 하거나

클래스 변수를 생성해서 생성자를 만들 때 if처리를 해주어야 한다.

 

이럴 때 Strategy Pattern를 이용한다고 한다.

OOP - Strategy Pattern

Strategy Pattern은 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시하는 것을 뜻하는데 예시를 보자

public class OopAndFpExamples {

  public static void main(final String[] args) {
	  CalculatorService add = new CalculatorService(new Addition());
	  add.calculateNum(1,2); // 3
	  CalculatorService sub = new CalculatorService(new Subtraction());
	  sub.calculateNum(1,2); // 1
	  CalculatorService mul = new CalculatorService(new Multiplication());
	  mul.calculateNum(1,2); // 2
	  CalculatorService div = new CalculatorService(new Division());
	  div.calculateNum(1,2); // 2
  }
}

interface Calculation {
  int calculate(final int num1, final int num2);
}

class Addition implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 + num2;
  }
}
class Subtraction implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 - num2;
  }
}
class Multiplication implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 * num2;
  }
}
class Division implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 / num2;
  }
}

class CalculatorService {
  private final Calculation calculation;

  public CalculatorService(Calculation calculation) {
	  this.calculation = calculation; 
  }
  public int calculateNum(int i, int j) {
	  if(i<j) {
		  return this.calculation.calculate(j,i);
	  }
	  return this.calculation.calculate(i,j);
  }
}

Calculation이라는 인터페이스를 생성하고

그것을 구체화 하는 네 가지 연산의 클래스를 생성했다.

 

그 이후의 CalculatorService의 변수로 Calculation클래스들을 모두 담아

인스턴스 생성시에 생성자에 받아

CalculatorService에는 단지 Calculation클래스의 calculate 메소드만을 리턴하는 역할을 한다.

FP- Strategy Pattern

함수형 프로그래밍은 똑같은 코드를 다음과 같이 사용하려고 한다.

public class OopAndFpExamples {

  public static void main(final String[] args) {
	  CalculatorService cal = new CalculatorService();
	  cal.calculateNum(new Addition(),1,2);
	  cal.calculateNum(new Subtraction(),1,2);
	  cal.calculateNum(new Multiplication(),1,2);
	  cal.calculateNum(new Division(),1,2);
  }
}

interface Calculation {
  int calculate(final int num1, final int num2);
}

class Addition implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 + num2;
  }
}
class Subtraction implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 - num2;
  }
}
class Multiplication implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 * num2;
  }
}
class Division implements Calculation {
  @Override
  public int calculate(final int num1, final int num2) {
    return num1 / num2;
  }
}

class CalculatorService {

  public int calculateNum(Calculation calculation, int i, int j) {
	  if(i<j) {
		  return calculation.calculate(j,i);
	  }
	  return calculation.calculate(i,j);
  }
}

다음과 같은 구조를 Parparrallel structure(병렬 구조)라고 부른다고 한다.(pipe 쓰는 느낌인듯)

클래스 변수로 추상객체를 넘겨주는 것이 아니라 파라미터로 추상객체를 넘겨서 계산을 해주었다.

 

그런데 이것이 정확한 함수형 프로그래밍은 아니라고 한다.

왜냐하면 현재 Calculation은 1급 객체가 아니기 때문이라고 한다.

일급 객체는 다음과 같다고 한다.

...더보기

1급 객체란?

  1. 변수나 데이타에 할당 할 수 있어야 한다. ex) String a = dog.getName => ()가 없는 이상 불가능
  2. 객체의 인자로 넘길 수 있어야 한다. ex) String a(dog.getName) => ()가 없는 이상 불가능
  3. 객체의 리턴값으로 리턴 할수 있어야 한다. return dog.getName => ()가 없는 이상 불가능

쉽게 설명하자면 익명함수를 말한다.

(자바스크립트 같은 경우 var a = function(){return 1+2}; 와 같은 코드를 작성할 수 있었다.)

자바는 Java7까지 어떠한 메소드도 허용하지 않았다.

하지만 Java8부터는 람다식이 추가되면서 1급 객체를 사용할 수 있게 됬다~ 라고 하는데 예시를 보자.

public class OopAndFpExamples {

  public static void main(final String[] args) {
	  CalculatorService cal = new CalculatorService();
	  cal.calculateNum((i,j)->i+j,1,2);
	  cal.calculateNum((i,j)->i-j,1,2);
	  cal.calculateNum((i,j)->i*j,1,2);
	  cal.calculateNum((i,j)->i/j,1,2);
  }
}

interface Calculation {
  int calculate(final int num1, final int num2);
}

class CalculatorService {

  public int calculateNum(Calculation calculation, int i, int j) {
	  if(i<j) {
		  return calculation.calculate(j,i);
	  }
	  return calculation.calculate(i,j);
  }
}

엄청 간단하고 이해도 쉬워졌다.

저것이 가능한 이유는

i(i,j)->i+j) 가 익명 클래스를 가지는 익명 메소드이고

펑션에 들어가는 순간 클래스가 정해진다.

그리고 이러한 클래스의 하나밖에 없는 메소드가 익명 메소드인 람다식을 구현한 식이 된다.(그렇기 때문에 람다식으로 구현하려는 인터페이스는 단 하나의 메소드만 사용할 수 있다.)

이는 다음과 같이 사용할 수도 있다.

	  Calculation add = ((i,j)->i+j);
	  cal.calculateNum(add,1,2);

익숙해질때까지 계속 보자