# Lambda, 동작 파라미터화
최근 [Clean Code](http://book.daum.net/detail/book.do?bookid=BOK00022070866YE)라는 책을 보고있다. 책을 보다가 **6장 객체와 자료 구조**에서 다루는 **자료/객체 비대칭**에 관한 내용중에서 한가지 의문점이 생기는 부분이 생겼다. **p.119**에 나와있는 내용인데 다음과 같은 내용을 서술하고 있다.
##
> 절차지향과 객체지향코드는 상호보완적인 역할을 한다. 절차지향코드의 경우에 새로운 메서드를 추가하는게 편한 반면에 새로운 클래스를 추가하기는 어렵다. 반대로 객체지향코드의 경우에는 새로운 클래스를 추가하는 것은 추상화를 통해 쉽게 할 수 있으나 새로운 메서드를 추가하고자 하면 모든 추상화를 구현한 클래스들이 메서드를 수정해야 하는 문제가 생기므로 이들은 상호보완적으로 사용해야 한다.
! 책의 내용을 이해한대로 풀어서 작성한 것으로 구문을 완전히 가져온 것이 아님
위와 같은 내용과 함께 책에서는 도형과 도형을 계산하는 수식을 예제로 설명했다.
### Procedural Shape
> **클린코드** 예제
```java:line-numbers
public class Square {
public Point topLeft;
public double side;
}
public class Rectangle {
public Point topLeft;
public double height;
public double width;
}
public class Circle {
public Point center;
public double radius;
}
public class Geometry {
public final double PI = 3.141592653589793;
public double area(Object shape) throws NoSuchShapeException {
if (shape instanceof Square) {
Square s = (Square) shape;
return s.side * s.side;
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.height * r.width;
} else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}
```
> 절차지향적 코드에서는 각 도형별로 멤버변수외에 별다른 메서드를 갖고있지 않다. 대신 넓이를 구하는 `area(Object shape)`메서드를 필요할 때 만들어서 수행하고 있다. 부피를 구하는 `volume()`을 만든다면 아마 위의 `area(Object shape)`코드를 복붙해서 실제 계산수식만 변경할 것이다. 비효율적이긴 하지만 새로운 행위를 정의하기 위한 메서드를 추가하는데 있어서 기존의 **도형 클래스**들은 별다른 영향을 받지 않는다. 하지만 새로운 **도형 클래스**를 추가하게 되면 모든 절차적 코드를 하나하나 찾아서 수정해야 될 것이다.
## Polymorphic Shapes
> **클린코드** 예제
```java:line-numbers
public class Square implements Shape {
private Point topLeft;
private double side;
public double area() {
return side * side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
public double area() {
return height * width;
}
}
public class Circle implements Shape {
public final double PI = 3.141592653589793;
private Point center;
private double radius;
public double area() {
return PI * radius * radius;
}
}
```
> 이 예제코드는 다형성을 이용해서 좀더 객체지향적으로 작성된 코드이다. `Shape` 인터페이스를 구현하면서 `area()` 메서드를 각 **도형 클래스**가 가지고 있다. 이렇게 하면 새로운 **도형 클래스**가 추가되더라도 `Shape` 인터페이스를 구현하기만 하면 다른 코드를 수정할 필요는 없어진다. 다만 `Shape` 인터페이스에 부피를 구하는 `volume()` 추상 메서드가 추가된다면 이를 구현하고 있는 모든 **도형 클래스**가 수정되어야 할 것이다. (그래도 컴파일 에러 레벨이고 ide가 자동으로 체크할테니 절차지향코드에서 발생하는 문제보다는 낫다)
###
위의 두 예제를 통해 책에서는 **절차지향적 코드와 객체지향적 코드는 상호보완적인 문제에 직면해 있다.** 고 설명한다. 하지만 이 대목을 보면서 **Java 8 Lambda**로 해결할 수 있는 문제라는 생각이 들었다. 새로운 메서드를 추가하기 어렵다는 것은 새로운 행위를 각 클래스에 추가하기 어렵다는 것인데, `Lambda`는 행위를 파라미터로 넘겨서 실행시점에 동작을 결정하도록 하는 **동작 파라미터화**개념을 적용하기 때문이다.
###
***
## Lambda 를 이용해 넓이와 부피를 구하는 동작을 파라미터화 하기
`Lambda`의 **동작 파라미터화**를 이용해 각 **도형 클래스**의 코드수정없이 새로운 기능을 수행하는 코드를 예제로 작성해봤다. 다음과 같이 작성했다.
###
- `calc( ... )` 메서드를 갖는 `abstract Shape`클래스를 만든다. 예제에서는 `Shape`클래스를 인스턴스화 할 필요가 없으므로 `abstract`으로 만들었다.
- 각 **도형 클래스**는 `Shape` 클래스를 상속(extends)한다.
- 함수형 인터페이스 `Geometry`를 만들고 수식을 수행하는 `double formula(T t)` 메서드를 만든다. (`@FunctionalInterface` 이므로 단 하나의 추상 메서드만을 갖는다.)
###
대충 이정도로 정리하고 예제를 보자. (본 예제는 완벽하다고 할 순 없다. 함수형 프로그래밍을 학습중인 사람임을 참고해 주시길 바라며 개인적인 생각이 다분히 적용된 점을 감안하시기 바란다.)
## 동작 파라미터화 예제
```java:line-numbers
@FunctionalInterface
public interface Geometry<T> {
double formula(T t);
}
public abstract class Shape<T> {
double calc(Shape<T> t, Geometry<T> geometry) {
return geometry.formula((T) t);
}
}
public class Rectangle extends Shape {
private double height;
private double width;
private double depth;
private Rectangle(double height, double width, double depth) {
this.height = height;
this.width = width;
this.depth = depth;
}
public Rectangle(double height, double width) {
this.height = height;
this.width = width;
}
public static Rectangle createWithHeightWidthDepth(double height, double width, double depth) {
return new Rectangle(height, width, depth);
}
public static Rectangle createWithHeightWidth(double height, double width) {
return new Rectangle(height, width);
}
private Rectangle() {
super();
}
public double getHeight() {
return height;
}
public double getWidth() {
return width;
}
public double getDepth() {
return depth;
}
}
public class UsingLambda {
public static void main(String[] args) {
Shape<Rectangle> shape = Rectangle.createWithHeightWidth(100, 200);
final Geometry<Rectangle> rectangleAreaFormula = s -> s.getHeight() * s.getWidth();
// final Geometry<Rectangle> rectangleVolumnFormula = s -> s.getHeight() * s.getWidth() * s.getDepth();
double area = shape.calc(shape, rectangleAreaFormula);
System.out.println("rectangle area: " + area);
shape = Rectangle.createWithHeightWidthDepth(100, 200, 50);
double volume = shape.calc(shape, s -> s.getHeight() * s.getWidth() * s.getDepth());
System.out.println("rectangle volume: " + volume);
}
}
```
> 더 나은 구조와 코드가 있을 수 있겠지만 소정의 목표인 **동작 파라미터화**를 이루었다. 넓이를 구하는 공식인 `rectangleAreaFormula`와 부피를 구하는 `rectangleVolumnFormula`를 각각 파라미터로 주면 기대하는 결과를 얻을 수 있다. (`double volume`의 경우 직접 `Lambda`식을 전달할 수도 있다는 것을 보여준다.)
이렇게 `Lambda`를 이용한 **동작 파라미터화**를 적용하면 **객체지향 코드는 새로운 메서드를 추가하기 어렵다**는 문제를 해결할 수 있다고 생각했다. `Lambda`를 이용하면 딱히 **절차지향 코드**와의 상호보완적인 역할에 대해서 생각할 필요없이 **객체지향 코드**에만 집중한 코드를 작성할 수 있지 않을까 생각한다. 더 나은, 더 보기좋은 코드를 작성하고자 항상 더 많은 노력을 해야 될 것 같다.