Framework이란?

Java 2014. 8. 30. 23:17

아래 내용은 다음 article들의 내용을 간단히 정리해 본 것이다.

http://www.programcreek.com/2011/09/how-to-design-a-java-framework/

http://www.programcreek.com/2011/09/what-is-the-difference-between-a-java-library-and-a-framework/


1. Framework?

Library와 Framework의 차이를 살펴보면 다음과 같다.

 특징

Library

Framework

Inversion of Control
(IoC)

사용자의 코드가 Library를 호출한다.

사용자가 Control 한다.

Framework가 사용자의 코드를 호출한다.
Framework가 Control한다. (IoC)

Reusability

Library의 코드(Code)를 재사용 하게 된다. Framework의 흐름(Flow)를 재사용 하게 된다.


Framework의 목적은 프로그램의 전반적인 뼈대를 제공하여 이를 사용하는 개발자로 하여금 각각의 요구사항에 맞는 특정 함수를 구현하는 것 만으로 빠른 개발을 가능하도록 한다.


2. 간단한 Framework 예제

 
 public class Main {
    public static void main(String[] args) {
        Human h = new Human(new Walk());
        h.doMove();
    }
}

public abstract class Move {
    public abstract void action();
}

public class Human {
    private Move move;
    public Human(Move m){
        this.move = m;
    }
    public void doMove(){
        this.move.action();
    }
}

public class Walk extends Move {
    @Override
    public void action(){
        System.out.println("walk");
    }
}


  • Main.java는 Framework의 진입점이다.
  • Move.java는 개발자가 정의 / 확장할수 있는 함수이다.(이를 Hook이라고 한다.)
  • Human.java는 탬플릿이다.
  • Walk.java는 Move.java를 구현한 구현체이다. 다른 형태의 움직임이 필요할 경우 이와 같이 Move를 상속받아 action()을 요구사항에 맞게 구현하면 된다.


3. 결론

위 예제는 Framework의 가장 간단한 형태로, Hook과 Template에 대해 간단히 보여주고 있다. 당연하겠지만, 실제 Framework들은 이보다 훨씬 더 복잡하다. 실제 Framework는 Template간 관계, 성능 및 사용성 향상과 관련된 부분들도 있기 때문이다.



'Java' 카테고리의 다른 글

Fluent Interface - Setter에 대한 고찰  (0) 2014.07.19
Posted by 제리곰
,

기존의 Spring Framework기반의 Application을 개발하다 보면 꽤 많은 시간을 Application 설정하는데에 소비하곤 했다. 보통은 내게 익숙한 스타일의 설정이 아니어서이기도 하였고, 가끔은 그 설정이 잘못되어 수정하는데 시간을 보내기도 했다.


가끔은 library 의존성에 의해 설정 오류가 발생하기도 하였지만, 대부분의 경우 설정에 시간을 쏟은 원인은 동일한 Spring Application 설정을 하는 방법이 Xml기반, Annotation기반, 그리고 Java Configuration기반이 존재하기 때문이었다. 하나의 동일한 설정을 하기 위한 방법이 무궁무진하게 존재하여 프로젝트마다 다른 설정이 적용되는 경우도 있었고, 새로운 기술을 도입하기 위해 참고자료들을 찾아 적용해 보려 하는데 예제가 특정 형태의 환경설정만 사용하고 있어 다른 스타일로 변경하기 위한 시간을 쏟기도 했다. 가끔은 그 스타일 변환이 쉽게 되지 않아 스타일을 섞어서 사용하게 되었고, 그 경우 예상치 못한 side-effect를 발생시키기도 하였다.


이러한 점 때문에 Spring Application 설정에 대해 좀 더 자세히 찾아보기로 하였고, 그에 앞서 각각의 Application Configuration 방법의 장단점에 대해 정리한 포스팅이 있어 이를 간단히 소개하고자 한다. 



이하 내용은 http://www.javacodebook.com/2013/07/08/spring-book-chapter-5-application-configuration-simplified/ 에서 발췌함.


Configuration Type 장점 단점
XML
  • Class파일들과 설정을 분리할 수 있고, 관심사의 분리(Separation of concerns) 관점이 적용된다.
  • 설정을 한눈에 보기에 용이하다.
  • 간단한 설정 변경은 별도의 컴파일 없이도 적용 가능하다.
  • 오타가 발생했을 때 debugging이 어렵다.
    (역주: 최근의 IDE들은 잘 잡아주기도 한다.)
  • XML의 값 들은 모두 String형이기 때문에 type safe하지 않다. 또한 Java Context에서 이 값들을 적절한 Java type으로 변환하는 과정에 JVM에 부하가 생긴다.
Annotation-based
  • Java code로 설정을 하기 때문에 type safe하며, Spring이 빠르게 container를 설정하게 한다.
  • Annotation이 클래스(또는 메소드) 상단에 추가되면서 설정이 구성되기 때문에 클래스 파일만 보아도 어떤 스프링 설정이 적용된 클래스인지 한눈에 알 수 있다.
  • POJO 개념을 흐트러놓는다.
  • 설정이 변경될 경우 Application code 클래스 파일까지 수정해 주어야 한다.
  • 변경사항이 있을 경우 컴파일이 필요하다.
Java Configuration
  • Java code로 설정을 하기 때문에 type safe하며, Spring이 빠르게 container를 설정하게 한다.
  • 설정이 별도의 class file로 분리되어 관심사의 분리(separation of concerns) 관점이 적용된다.
  • Application Configuration을 나타내기 위해 Single resource 가 사용된다.
  • For systems which needs change constantly, configuration can change without changing the application code which is key for such systems.
  • 변경사항이 있을 경우 컴파일이 필요하다.

결론적으로 Java Configuration을 이용한 Spring Application 방법이 여느 다른 방법에 비해 단점이 적은 방법이다. 하지만 그렇다고 다른 방식의 설정 방법을 무시해버릴 수는 없다. (역주. 다른 설정방법에 대해 고려해야 하는 이유는 많은 legacy code들은 여전히 xml 또는 annotation기반의 설정을 이용하고 있고, 그리고 경우에 따라서는 각각의 설정 방법이 갖고 있는 장점을 살리는 설정도 효과적이기 때문일 것이다.)


'Spring Framework' 카테고리의 다른 글

[troubleshooting] Spring Security redirect to /favicon.ico  (0) 2015.01.21
Posted by 제리곰
,

아래의 코드는 자바 개발하며 흔히 볼 수 있는 entity 객체다.

 
public class TestEntity {
    private String name;
    private String description;
    private Long sequence;
    public Long getSequence() {
        return sequence;
    }
    public void setSequence(Long sequence) {
        this.sequence = sequence;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

지금까지 개발해 오면서 클래스를 만들고 멤버변수들을 만들고 난 뒤 getter와 setter는 IDE가 알아서(?) 만들어 주기에 크게 신경쓰지 않고 있었다. 최근 자바스크립를(정확하게는 jQuery를) 한창 하면서, 그리고 또 팀에 새로 오신분의 코딩 스타일을 보면서 setter에 대해 Builder Pattern과 비슷한 다음의 형태로 바꾸는 것에 대해 생각해 보게 되었다.

//in TestEntity
 public TestEntity setSequence(Long sequence) {
        this.sequence = sequence;
        return this;
 }


이런 식으로 setter를 구성할 경우(특히 테스트케이스를 만들 때) 어딘가 지저분한 느낌이었던 코드 일부가 깔끔해 지는 걸 볼 수 있었다.


//before
TestEntity entity = new TestEntity();
entity.setSequence(1);
entity.setName("devjerry");
entity.setDescription("now in tistory");

//after
TestEntity entity = new TestEntity()
                                   .setSequence(1)
                                   .setName("devjerry")
                                   .setDescription("now in tistory");


그럼 왜 이러한 형태가 일반적으로 쓰이지 않는 것일까? (IDE가 자동으로 생성해 주는 패턴이 아닌 것으로 보아..)

구글링을 통해 다음의 페이지를 찾을 수 있었다.

(당연하게도 내가 생각한건 이미 다른사람들이 다 생각을 마친 뒤인 경우가 많다.)


http://stackoverflow.com/questions/31584/design-java-and-returning-self-reference-in-setter-methods


대충 답변을 보니 사용 상 큰 문제는 없는 것 같았다. 그리고 이러한 방법을 Fluent Interface 라고 하는 것을 알 수 있었다.


위키를 참조한 결과, Fluent Interface의 경우 logging, debugging, 그리고 subclass 만들기의 복잡함 정도의 불편함이 있다고 한다. Subclass 만드는 게 좀 걸리긴 하지만.. 나머지 경우는 실제 프로그래밍에 있어서 큰 문제가 되지는 않기에 한정적인 환경에서는 한번 제대로 써보는 것도 나쁘지 않다는 생각이 든다.


Fluent Interface에 대해 간단히 정리하며 이 생각은 우선은 마치겠다. ( 나중에 또 할지 모르지만.. )


이하는 http://en.wikipedia.org/wiki/Fluent_interface 위키 페이지를 대충 정리한 것이다.


Fluent Interface는 (as first coined by Eric Evans and Martin Fowler) 가독성 향상에 목적을 둔 객체 지향 API 구현 방법이다.

A fluent interface is normally implemented by using method cascading (concretely method chaining) to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining). Generally, the context is

  • defined through the return value of a called method
  • self-referential, where the new context is equivalent to the last context
  • terminated through the return of a void context.


History

The term "fluent interface" was coined in late 2005, though this overall style of interface dates to the invention of method cascading in Smalltalk in the 1970s, and numerous examples in the 1980s. The most familiar is the iostream library in C++, which uses the << or >> operators for the message passing, sending multiple data to the same object and allowing "manipulators" for other method calls. Other early examples include the Garnet system (from 1988 in Lisp) and the Amulet system (from 1994 in C++) which used this style for object creation and property assignment.


Problems


1. Debugging & error reporting


Fluent Interface형태의 api를 사용할 경우, chained 선언을 한줄에 붙여 쓰게 되면 debugger로 chain 중간에 breakpoint를 잡기 어렵다는 단점이 있다. 해당 라인에 breakpoint를 잡더라도 Step을 몇번 더 타고 들어가야 하기에 디버깅이 불편하다.

또다른 문제로, 특히 chained 선언시 동일 메소드가 여러번 호출 되는 경우, 어느 메소드가 익셉션의 원인인지 명확하게 알기 어렵다. 이러한 문제는 아래와 같이 chained선언을 여러 줄에 걸쳐 줄바꿈을 통해 선언하면 조금은 해결할 수 있다.(However, some debuggers always show the first line in the exception backtrace, although the exception has been thrown on any line.)

java.nio.ByteBuffer.
    allocate(10).
    rewind().
    limit(100);

2. Logging


또다른 이슈는 로깅하는 데 있다.

ByteBuffer buffer = ByteBuffer.allocate(10).rewind().limit(100);

예를 들어, 위 선언에서 rewind이후의 buffer상태를 로깅하고 싶다면 아래와 같이 fluent call을 깨뜨려야만 가능하다.

ByteBuffer buffer = ByteBuffer.allocate(10).rewind();
log.debug("First byte after rewind is " + buffer.get(0));
buffer.limit(100);

3. Subclasses

Strongly typed languages (C++, Java, C#, etc.) 에서는 subclass구현 시, parent class에서 fluent interface로 구현된 method를 모두 override하여 return type을 바꾸어 주어야 하는 불편함이 있다.

class A {
    public A doThis() { ... }
}
class B extends A{
    public B doThis() { super.doThis(); } // return type to B.
    public B doThat() { ... }
}
...
A a = new B().doThat().doThis(); // It works even without overriding A.doThis().
B b = new B().doThis().doThat(); // It would fail without overriding A.doThis().


Override 없이 subclass를 구현하기 위해서는 parent class를 둘로 나누어 fluent interface method를 모아 abstract class로 빼는 방법을 생각할 수 있다. ( the class A with no content (it would only contain constructors if those were needed). )


abstract class AbstractA<T extends AbstractA<T>> {
    @SuppressWarnings("unchecked")
    public T doThis() { ...; return (T)this; }
}   
class A extends AbstractA<A> {}
 
class B extends AbstractA<B> {
    public B doThat() { ...; return this; }
}
 
...
B b = new B().doThis().doThat(); // Works!
A a = new A().doThis();          // Also works.

이를 확장하면 sub-subclasses도 아래와 같이 선언할 수 있다.

abstract class AbstractB<T extends AbstractB<T>> extends AbstractA<T> {
    @SuppressWarnings("unchecked")
    public T doThat() { ...; return (T)this; }
}
class B extends AbstractB<B> {}
 
abstract class AbstractC<T extends AbstractC<T>> extends AbstractB<T> {
    @SuppressWarnings("unchecked")
    public T foo() { ...; return (T)this; }
}
class C extends AbstractC<C> {}
...
C c = new C().doThis().doThat().foo(); // Works!
B b = new B().doThis().doThat();       // Still works.



살짝 다른 이야기로, stackoverflow 페이지의 첫 답변으로 달린 http://tech.puredanger.com/java7#chained (void 형태의 메소드가 항상 클래스를 리턴하도록 만들어 주도록 하자는 Java7 제안) 에 대한 이야기 중 아래의 이야기는 인상적이었다.


 This is "magic" behaviour, and all magic is bad.


나는 비슷한 이유로 선언하지 않아도 자동으로 getter/setter등을 만들어주는 롬복(http://projectlombok.org/) 의 적용은 별로 선호하지 않는 편이다.





'Java' 카테고리의 다른 글

Framework이란?  (0) 2014.08.30
Posted by 제리곰
,