GOF 디자인패턴을 이용한 Java Web Architecture
(부제 : Java Web Project Series 1)
1. 서론
2. Mediator
3. Command
4. Factory Method
5. Singleton
6. Abstract Factory
7. Strategy
8. 결론
작성자 : 안승규
작성일 : 2003.06.18
버 전 : 1.0
1. 서론
자바 프로그래밍을 최초로 배울 때 프로그래머들은 자바 클래스를 만들어 객체를 생성하고 해당 객체레퍼런스로 메소드를 호출하여 자신이 원하는 결과값을 얻는다. 즉 자바클래스는 객체지향언어의 특징 중 단순히 캡슐화를 나타낼 뿐이며 모든 비즈니스로직은 하나의 혹은 두개의 메소드로 전부 처리한다. 심지어 하나의 메소드가 몇백라인이 되는 메소드들도 있으며 이를 유지보수 한다는 것은 정말 경이로울 수 밖에 없다.
에러가 없는 프로그램은 잘 작성된 프로그램인가? 결국 프로그램은 결과값으로 그 가치를 평가하는가?
대부분의 관리자들은 프로그래머를 평가할 때 납기와 에러율을 가지고 평가한다. 이 두가지 요소는 프로그래머에게 가장 중요한 요소라는 것에 반기를 드는 사람은 없을 것이다. 그러나 정말 이 두 가지만으로 프로그래머를 평가해야 하는가? 좀 더 멀리 내다보는 관리자라면 해당 프로그램의 주인이 바뀐다 하더라도 쉽고 정확하게 관리될 수 있는 요소를 추가해야 할 것이다. 왜냐하면 모든 프로그램은 유지보수를 하게 되어 있으며 이는 유지보수시에 투입되는 인건비와도 관련이 되기 때문이다. 또한 프로그램변경시에 에러율을 얼마나 줄일 수 있느냐도 간과해서는 안된다.
그렇다면 에러율도 적고 유지보수도 편한 그러한 프로그램은 어떻게 만들 수 있는 것일까?
굳이 그 해답을 찾으라고 한다며 디자인패턴을 꼽을 수 있을 것이다. 물론 디자인패턴이 모든 것의 해결책이 될 수는 없다. 우리는 납기율을 준수하기 위한 방법(eXtreme Programming)도 알아야 하고 효과적으로 데이터베이스를 쿼리하기 위한 sql 문도 알아야 하며 유지보수시에 추가된 소스를 정리하기 위해서 리팩토링도 알아야 하며 에러율을 줄이기위해서 수시로 테스트 하면서 개발하는 방법도 알아야 한다. (Test Driven Development)
여기서 우리의 현실을 보자.
새로운 프로그래머가 입문하면 우선 자바프로그래밍을 일주일에 거쳐서 배운다. 그리고 일주일은 JSP 를 배운다. (이제는 Servlet 을 설명하는 과정도 별로 없다. 실제로는 Servlet Spec 을 많이 쓰는데도 말이다.) 아무리 길어봤자 약 한달간의 교육을 마친 인원들이 실무 프로젝트에 투입이 되는 것이다. 그러한 인원들이 프로그램을 일년간 배우고 다시 다른 프로젝트에 투입되어 전에 수행했던 소스를 가지고 앞에서 해온 것과 같은 과정을 그대로 반복한다. 만약 해당 프로그래머가 꾸준히 새로운 기술을 연구하고 이를 적용하는 노력을 기울인다면 이 프로그래머는 향후 2~3년 후에 매우 발전된 프로그래머가 될 것이다. 그러나 그렇지 않다면 자바를 객체지향적으로 프로그래밍하기에는 몇 년의 시간이 더 소요될 것이다.
많은 프로그래머들에게 이러한 시간을 줄일 수 있는 방법은 좋은 프로그래밍하는 방법을 배우는 것이며 그것의 첫걸음이 곧 디자인패턴이다.
그러나 디자인패턴은 초보자들이 이해하기가 어려우며 많은 경험이 없으면 해당 패턴을 시기적절하게 적용하기가 어렵다. 그래서 이 문서에서는 디자인 패턴의 가장 근본이 되는 GOF (Gang of Four) 의 디자인패턴을 소개하고 이에 대해 자바 웹프로그래밍시 어떻게 적용할 것인가를 예를 들어 설명한다. (일반적으로 J2EE 프로그래밍에서 말하는 EJB 패턴을 설명하는 것은 아니다. EJB 패턴은 대부분이 GOF 에서 출발했으며 향후에 Sun 에서 제공하는 PetStore 예제를 통하여 해당 패턴들을 자세히 설명할 것이다.)
또한, Servlet 과 JSP 및 Sun 에서 말하는 Web Architeture MVC Model 2 (Servlet Centric Model) 를 이해하고 있다면 이 문서를 이해하기 쉬울것이며 이후에 계속적으로 앞에서 말한 좋은 프로그램을 만들기 위한 요소들을 아래의 제목에 맞추어 설명할 것이다. (이후 Java Web Project Series)
l 효과적인 개발환경 구축 (Jakarta Project)
l Servlet Spec 의 이해
l PetStore 분석을 통한 EJB 디자인패턴의 이해
2. Mediator
객체들간의 상호참조를 없애고 중간에 중재자 객체를 추가함으로써 객체들끼리의 결합도를 낮추는 패턴이다.
A 라는 객체가 B 라는 객체를 호출하기 위해서는 B 객체의 레퍼런스를 가져야 가능하다. 만약 여러 개의 객체가 존재하고 이들의 비즈니스로직을 처리함에 있어 상호연관이 있다면 객체들과의 연관관계는 거미줄처럼 역일 것이다. 그렇게 되면 객체의 독립성이 떨어져 결국에는 프로그램자체가 하나의 커다란 덩어리로 존재하게 된다.
이러한 문제를 해결하기 위해서 모든 객체의 레퍼런스를 가지고 있는 중재자 객체를 만들면 서로의 연관관계는 없어지고 중재자 객체에 모든 연관관계가 귀속되어 차후 비즈니스 로직을 바꿀 경우 중재자 객체만 수정하면 쉽게 변경할 수 있게 된다. 또한 각각의 객체 역시 중재자 객체의 레퍼런스만 가지고 있으면 되므로 로직이 단순화 될 수 있다.
다음은 클래스 다이어그램을 설명한 것이다.
<v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><v:f></v:f><o:lock v:ext="edit" aspectratio="t"></o:lock>
Colleague 객체는 Mediator 객체의 레퍼런스를 가지고 있으며 ConcreteCollegue1 객체가 Mediator 객체의 메소드를 호출하면 Mediator 객체는 ConcreteCollegue1 객체와 ConcreteCollegue2 객체의 메소드를 호출하여 결국 ConcreteCollegue1 객체가 ConcreteCollegue2 를 호출하는 효과를 얻을 수 있다.
다음은 Mediator 패턴의 시퀀스 다이어그램을 보여준다. 여기서 aListBox는 ConcreteCollegue1 객체를 anEntityField는 ConcreteCollegue2 객체를 나타낸다.
Java Web Programming에 적용
웹은 특성상 하나의 화면에서 다른 하나의 화면으로 이동하게 되어 있다. 화면을 client 라고 한다면 그 화면을 보여주는 JSP 는 클래스 객체인 것이다. 즉 화면에서 화면으로의 이동은 JSP 클래스 객체의 이동이며 입력화면과 결과화면은 서로연관관계가 있다고 말할 수 있다. 이러한 연관관계를 중간에 Servlet 이라는 중재자를 두어 처리한 것이 Sun 의 Web Architecture MVC Model2 인 것이다.
아래의 Servlet 소스를 보면 이해하기가 훨씬 수월할 것이다.
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TestServlet extends HttpServlet
{
private String error = "test/Error.jsp";
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException
{
String path = "/test/";
String nextFile=null;
String comandString = req.getParameter("cmd");
try
{
Command cmd = CommandFactory.getCommand(comandString);
nextFile = cmd.execute(req);
}
catch (Exception e)
{
req.setAttribute("javax.servlet.jsp.jspException", e);
path = "/";
nextFile = error;
}
RequestDispatcher rd;
rd = getServletContext().getRequestDispatcher(path + nextFile);
try {
rd.forward(req, res);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
|
JSP1 으로 만든 화면에서 request 를 요청하면 url mapping 으로 해당 Servlet 을 호출한다. 즉 여기서 request 자체가 앞서 시퀀스 다이어그램에서 본 WidgetChanged() 메소드가 되는 것이다. (WidgetChanged 는 메소드임에도 불구하고 첫문자가 대문자로 되어 있다. GOF 에서 일반적인 언어로 표현했기 때문에 자바의 코딩컨벤션에 맞지 않는 경우가 종종 있다.) request 가 Servlet 으로 넘어오면 Servlet 에서는 request.getParameter(String) 으로 원하는 데이터를 가져올 수 있다. 이것은 GetSelection() 메소드가 되며 cmd.operate(req); 을 수행하고 리턴된 String 은 url path 가 되며 이는 RequestDispatcher 를 이용하여 다른 JSP2 를 호출할 수 있다. 소스 중간에 보면 Command 객체를 생성하여 execute 메소드를 호출하는 부분이 있는데 이 메소드의 내부에는 request.setAttribute(String, Object) 를 호출하여 Colleague 객체의 SetText() 메소드와 같은 역할을 수행한다.
3. Command
클라이언트의 요청 자체를 객체화하여 처리하는 패턴이다. 즉 요청시 마다 Command 객체를 만들어 클라이언트와 처리할 어플리케이션을 연결한다. 그러므로 Command 패턴을 사용하면 오퍼레이션을 호출하는 객체와 이를 수행하는 객체를 분리시켜 사용자 인터페이스 작성시에 융통성을 부여할 수 있다.
그림에서 보면 클라이언트는 Invoker 를 통해서 특정 메소드를 호출하면 Invoker 는 Command Type 의 ConcreteCommand 객체를 가지고 있다가 Execute() 메소드를 호출한다. 이때 Command 는 Receiver 를 가지고 있어 해당 Receiver 를 호출하는 것이다.
만약 클라이언트의 새로운 요청이 생긴다면 클라이언트는 invoker 와 새로운 ConcreteCommand 를 지정하면 되고 새로운 ConcreteCommand 를 Recevier 와 연결하면 된다. 새로운 ConcreteCommand 의 생성방법을 뒤에서 설명할 Factory Method 패턴을 사용한다면 기존의 소스는 전혀 수정할 필요가 없게 된다.
좀 더 자세한 구현방법을 시퀀스 다이어그램을 통해서 알아보자.
간혹 디자인패턴을 처음 접하는 분들은 대부분의 패턴이 서로 비슷하다고 느낀다. 그 이유는 디자인패턴 자체가 모든 객체의 결합도를 떨어뜨리기 위한 방법들이기 때문이다. Command 패턴도 Receiver 와 Command 객체사이에 어떠한 결합도 없고 모두 Invoker 를 통해서 통신하기 때문에 언뜻보면 Mediator 패턴과 비슷하다고 볼 수 도 있는데 이는 의미적으로 큰 차이가 있다. Mediator 는 어플리케이션의 객체와 상호통신을 하며 의도자체가 객체간의 통신방법을 중재자를 통해서 한다는 것이다. Command 는 단방향 통신을 하며 클라이언트의 Request 마다 객체를 만든다는 것에 중점을 둔다는 것이다.
[CommandClient.java]
interface Command {
public void execute();
}
class Invoker {
public static void storeCommand(Command command) {
command.execute();
}
}
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.action();
}
}
class Receiver {
public void action() {
System.out.println("Receiver called !!");
}
}
public class CommandClient {
public static void main(String args[]) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker.storeCommand(command);
}
}
|
Java Web Programming에 적용
TestServlet 예제를 보면 Command 객체의 operate() 메소드를 호출하는데 이 부분이 Command 패턴을 쓴 부분이다. GOF에서는 Command 객체의 아규먼트로 Invoker 객체를 사용했으며 그 Invoker 객체가 Command -> Receiver 순으로 호출했지만 웹프로그래밍에서는 Client 가 객체를 생성할 수 없으므로 Servlet (Invoker) 객체가 Command를 직접 생성하여 Command -> Receiver 순으로 호출하였다. Command 객체의 execute() 메소드에는 특정 객체를 생성하여 호출하는 로직이 담겨져 있다.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ConcreteCommand implements Command
{
String jspPage;
public ConcreteCommand(String aJspPage)
{
this.jspPage = aJspPage;
}
public String execute(HttpServletRequest req) throws EPException
{
try
{
String name = req.getParameter("name");
EmpolyeeVO = new Employee().getEmployeeList(name);
req.setAttribute("employeeVO", EmpolyeeVO);
return this.jspPage;
}
catch (Exception e)
{
req.setAttribute("errorCode", " ERROR");
req.setAttribute("isPopup","Y");
req.setAttribute("isScript","Y");
throw e;
}
}
}
|
ConcreteCommand 의 execute() 메소드에 보면 Employee 객체를 호출하는 부분이 있다. 이부분이 Receiver 가 되며 이 때 호출된 메소드가 Action메소드가 된다.
4. Factory Method
객체 생성과 관련된 패턴으로 객체 생성에 대한 인터페이스를 정의한 후 실제 객체는 인터페이스를 구현한 서브클래스에서 정의하는 패턴이다. 이 패턴은 앞에서도 말했듯이 Command 패턴과 연관시켜 사용하면 더욱 객체지향적인 프로그램이 될 수 있다. 우선은 해당 클래스다이어그램을 먼저 본 후에 어떻게 Command 패턴과 연관시키는 지를 알아보자.
다이어그램을 보면 클라이언트가 ConcreteCreator 객체를 생성한 후 AnOperation() 메소드를 호출하면 내부에서는 FactoryMethod() 메소드를 호출하여 특정 ConcreteProduct 를 생성하여 리턴시키며 생성된 Product 객체를 가지고 특정 메소드를 수행시킨다.
이를 Java Web 프로그래밍의 예제와 연관시켜 설명하면 CommandFactory (ConcreteCreator) 객체가 ConcreteCommand(ConcreteProduct) 객체를 생성시킨 후 ConcreteCommand 객체의 execute() 메소드를 호출한다는 것이다.
interface Product {
public void dispalyName();
}
class ConcreteProductA implements Product {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductA !!");
}
}
class ConcreteProductB implements Product {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductB !!");
}
}
abstract class Creator {
public abstract Product factoryMethod(String name);
public void anOperation(String name) {
Product p = factoryMethod(name);
p.dispalyName();
}
}
class ConcreteCreator extends Creator {
public Product factoryMethod(String name) {
Product p = null;
if (name.equals("A")) {
p = new ConcreteProductA();
} else if (name.equals("B")) {
p = new ConcreteProductB();
}
return p;
}
}
public class FactoryMethodClient {
public static void main(String[] args) {
Creator c = new ConcreteCreator();
c.anOperation("A");
}
}
|
Java Web Programming에 적용
만약 Command 객체를 생성할 때에 Factory Method 객체를 사용하지 않는다면 다음과 같은 코드가 나올 것이다.
TestServlet.java
…
Command cmd = null;
if (“add”.equals(commandString))
{
cmd = new AddCommand(“add.jsp”);
}
else if (“mod”.equals(commandString))
{
cmd = new ModCommand(“mod.jsp”);
}
…
|
이를 Factory Method 를 적용하면 TestServlet.java 에 해당 내용의 소스들이 전부 포함되어야 한다. 다음은 패턴을 적용한 소스이다. (소스에 import 는 생략하였다.)
TestServlet.java
…
Factory factory = new CommandFactory();
factory.operate(req, res, commandString);
…
|
Factory.java
public abstract class Factory
{
public abstract Command factoryMethod(String cmd);
public void operate(HttpServletRequest req, HttpServletResponse res,
String cmd)
{
Command = factoryMethod(cmd);
Command.execute(req, res);
}
}
|
CommandFactory.java
public class CommandFactory implements Factory
{
public Command factoryMethod(String cmd)
{
if (“add”.equals(cmd))
{
return new AddCommand(“add.jsp”);
}
else if (“mod”.equals(cmd))
{
return new ModCommand(“mod.jsp”);
}
}
}
|
5. Singleton
클래스의 인스턴스를 오직 하나만 만들어 사용하는 패턴이다.
프로젝트를 하다보면 하나의 인스턴스만이 필요할 때가 있다. 예를 들면 컨넥션풀을 관리하는 컨넥션풀매니저나 Configuration 파일등을 로딩하여 저장하는 클래스가 해당된다. 또한 Factory Method 패턴에서 Factory 클래스의 경우 객체생성시에 필요한 클래스이므로 이를 Singleton으로 사용하기에 적합하다.
Singleton 패턴에서는 static 으로 선언된 Instance() 메소드를 호출함으로써 해당 객체가 생성된다. 일단 객체가 생성되면 그것을 멤버변수로 할당하여 레퍼런스를 저장하고 이후 Instance() 메소도를 호출할 때마다 레퍼런스를 체크하여 저장된 레퍼런스를 리턴하다. 간혹 Singleton 패턴을 사용할 때 Double Checking Lock 패턴을 사용하는 경우가 많은데 Java 에서는 C 나 C++ 과 다르게 JVM 에서 메모리 사용하는 방법이 다르므로 하나의 인스턴스가 아니라 그 이상의 인스턴스가 생길 수 있는 경우가 있으므로 Double Checking Lock 패턴은 적용하지 말아야 한다.
[Singleton.java]
class Singleton {
private static Singleton instance = null;
private String say = "Hello World";
private Singleton() {}
public static Singleton instance() {
if (instance == null ) {
instance = new Singleton();
}
return instance;
}
public void setSay(String say) {
this.say = say;
}
public String getSay() {
return say;
}
}
public class SingletonClient {
public static void main(String args[]) {
Singleton first = Singleton.instance();
Singleton second = Singleton.instance();
System.out.println(first.getSay());
System.out.println(second.getSay());
first.setSay("Hello World again");
System.out.println(first.getSay());
System.out.println(second.getSay());
}
}
|
Java Web Programming에 적용
TestServlet.java 에서 Factory Method 패턴을 사용한 다음과 같은 코드를 Singleton 패턴을 적용하여 수정하여 보자.
[이전의 TestServlet.java]
…
Factory factory = new CommandFactory();
factory.operate(req, res, commandString);
…
|
매번 CommandFactory() 를 생성해서 사용하는 것을 수정하였다.
[수정후 TestServlet.java]
Factory factory = CommandFactory.getInstance();
factory.operate(req, res, commandString);
|
[수정후 CommandFactory.java]
public class CommandFactory implements Factory
{
private static CommandFactory instance = null;
private CommandFactory() {}
public static CommandFactory getInstance()
{
if (instance == null ) {
instance = new CommandFactory();
}
return instance;
}
public Command factoryMethod(String cmd)
{
if (“add”.equals(cmd))
{
return new AddCommand(“add.jsp”);
}
else if (“mod”.equals(cmd))
{
return new ModCommand(“mod.jsp”);
}
}
}
|
6. Abstract Factory
여러 객체의 군(무리)을 생성하기 위한 패턴이다. 즉 비슷한 성질의 객체를 묶어서 여러 영역으로 나누었을 경우 그러한 비슷한 성질의 객체를 만들기 위한 패턴이다. 해당 패턴은 이름에서도 그러하듯이 Factory Method 패턴자체를 Abstract 하게 만들어서 사용한다. 즉 하나의 객체군은 하나의 Factory 를 이용하며 이러한 Factory 는 상위 Factory 상속받아서 사용한다.
클래스다이어그램을 보고 좀 더 자세히 설명하겠다.
두개의 클래스다이어그램은 모두 같은 것을 나타내는데 두번째 좀 더 구체화된 다이어그램을 가지고 설명하자면 Window 와 ScrollBar 를 생성하기 위해서는 PM 과 Modif 등 여러가지의 타입이 있을 수 있다. 여기서 PM 타입을 하나의 객체군으로 볼 수 있으며 이를 생성하기 위해서는 PMWidgetFactory 를 사용한다는 것이다. 그렇게 되면 해당 Factory 를 가지고 PMWindow 와 PMScrollBar 를 생성할 수 있다. 여기서 새로운 객체군이 추가되면 그에 해당하는 Factory 를 만들면 되므로 소스의 변경 및 관리가 쉬워진다.
[AbstractFactoryClient.java]
interface AbstractProductA {
public void dispalyName();
}
class ConcreteProductA1 implements AbstractProductA {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductA1 !!");
}
}
class ConcreteProductA2 implements AbstractProductA {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductA2 !!");
}
}
interface AbstractProductB {
public void dispalyName();
}
class ConcreteProductB1 implements AbstractProductB {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductB1 !!");
}
}
class ConcreteProductB2 implements AbstractProductB {
public void dispalyName() {
System.out.println("My Class name is ConcreteProductB2 !!");
}
}
abstract class AbstractFactory {
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}
class ConcreteFactory1 extends AbstractFactory {
public AbstractProductA createProductA() {
AbstractProductA apa = null;
apa = new ConcreteProductA1();
return apa;
}
public AbstractProductB createProductB() {
AbstractProductB apb = null;
apb = new ConcreteProductB1();
return apb;
}
}
class ConcreteFactory2 extends AbstractFactory {
public AbstractProductA createProductA() {
AbstractProductA apa = null;
apa = new ConcreteProductA2();
return apa;
}
public AbstractProductB createProductB() {
AbstractProductB apb = null;
apb = new ConcreteProductB2();
return apb;
}
}
public class AbstractFactoryClient {
public static void main(String[] args) {
AbstractFactory af = new ConcreteFactory1();
AbstractProductA apa = af.createProductA();
apa.dispalyName();
}
}
|
Abstract Factory 패턴 역시 Factory 생성시에는 Singleton 패턴과 같이 사용하는 것이 좋다.
Java Web Programming에 적용
J2EE 패턴에 보면 DAO(Data Access Object) 패턴이라는 것이 있다. 이는 데이터를 접근할 때 데이터 접근 로직을 객체화 하여 따로 관리함으로써 DataSource 에 상관없이 프로그램을 유연하게 코딩할 수 있다. 이러한 DAO 패턴은 Abstract Factory 패턴을 적용한 것이라 할 수 있다. DataSource 에는 RDBMS, XML, OODB 등 여러가지 제품군이 있을 수 있으며 각각의 제품군에 맞게 생성하기 위해서 해당 패턴을 사용하는 것이다.
2001년 여름 필자가 XX 프로젝트에 투입된 적이 있었다. 이는 해당 SI프로젝트를 수행하면서 프로그램을 패키지화 하는 프로젝트였는데 이 때 패키지라는 명목으로 특정 DB 에 영향을 받지 않는 프로그램을 코딩하기 위해서 ORACLE 의 특성을 제외한 표준 SQL만을 사용하고 있었다. 필자가 투입되었을 때는 이미 개발이 거의 완성된 단계였는데 모두 알다시피 표준 SQL 만으로는 퍼포먼스 튜닝하기가 거의 불가능하며 그로인해 어떤 서브시스템은 표준SQL 만 사용하고 어떤 시스템은 벤더가 제공하는 파워풀한 SQL 을 사용하는 등 결국은 초기의 생각과는 많이 다른 방향으로 흘러가고 있었던 것이다.
이는 패키지에 대한 초기 접근 방법이 틀렸다고 할 수 있는데 아마 여기서 Abstract Factory 패턴을 사용했더라면 좋았을 것이다. 즉 해당 DataSource 에 맞는 DAO 를 만들고 다른 DataSource 에 적용할려면 그에대한 DAO 를 더 만들어서 Factory 에서 어떤 DAO를 생성할 지 결정한다면 된다는 것이다.
앞에서 보여준 ConcreteCommand.java 의 예제를 보면 다음과 같은 소스가 있는 것을 기억할 것이다.
…
String name = req.getParameter("name");
EmpolyeeVO = new Employee().getEmployeeList(name);
req.setAttribute("employeeVO", EmpolyeeVO);
…
|
여기서 new Employee().getEmployeeList(name); 부분을 보면 Employee 클래스는 Entity 클래스로 직원을 나타내는 클래스라는 것을 직관적으로 알 수 있다. 또한 GetEmployeeList() 메소드는 이름을 넣어 직원들의 리스트를 조회하는 메소드라는 것을 알 수 있다. 그렇다면 이렇게 DataSource 에 접근하기 위해서 다음과 같은 코드를 유추할 수 있을 것이다.
[Employee.java]
{
public EmployeeVO getEmployeeList(String name)
{
MyConfigure configure = Configure.getInstance();
DAOFactory factory = DAOFactory.getDAOFactory("ORACLE");
EmployeeDAO dao = factory.getEmployeeDAO();
return dao.getEmployeeList (name);
}
}
|
public abstract class DAOFactory
{
public static DAOFactory getDAOFactory(String dataBase)
{
if ("ORACLE".equals(dataBase))
{
return OracleDAOFactory.getInstance();
}
return OracleDAOFactory.getInstance();
}
public abstract EmployeeDAO getEmployeeDAO();
}
|
[OracleDAOFactory.java]
public class OracleDAOFactory extends DAOFactory
{
private static OracleDAOFactory instance = null;
private OracleDAOFactory() {}
public static OracleDAOFactory getInstance()
{
if (instance == null)
{
instance = new OracleDAOFactory();
}
return instance;
}
public EmployeeDAO getEmployeeDAO()
{
return new EmployeeOracleDAO();
}
}
|
[EmployeeDAO.java]
public interface EmployeeDAO
{
public EmployeeVO getEmployeeList(String name);
}
|
[EmployeeOracleDAO.java]
public class EmployeeOracleDAO implements EmployeeDAO
{
public EmployeeVO getEmployeeList(String name)
{
…
}
}
|
7. Strategy
알고리즘을 객체화 하여 다양한 알고리즘을 적용할 수 있는 패턴이다. 해당 패턴은 State 패턴과 아주 유사하며 알고리즘을 적용한다는 점에서 많이 사용되는 패턴중의 하나이다.
클래스 다이어그램을 보면 각각의 서브클래스마다 알고리즘을 구현한 것을 볼 수 있다. 즉 Algorithmenterface() 메소드는 클래스마다 구현한 것이 바뀌어 있으며 클라이언트는 Context 객체의 ContextInterface() 를 호출하고 이는 내부적으로 가지고 있는 Strategy 객체를 호출한다. 여기서 Strategy 의 서브클래스 객체는 Factory Method 패턴을 이용하여 적절한 서브클래스를 생성하면 된다.
[StrategyClient.java]
interface Strategy
{
public String algorithmInterface();
}
class ConcreteStrategyA implements Strategy
{
public String algorithmInterface()
{
return ("called Strategy A");
}
}
class ConcreteStrategyB implements Strategy
{
public String algorithmInterface()
{
return ("called Strategy B");
}
}
class Context
{
private Strategy strategy;
public Context()
{
strategy = new ConcreteStrategyA();
}
public void setStrategy(Strategy strategy)
{
this.strategy = strategy;
}
public String contextInterface()
{
return strategy.algorithmInterface();
}
}
public class StrategyClient
{
public static void main(String args[])
{
Context context = new Context();
System.out.println(context.contextInterface());
context.setStrategy(new ConcreteStrategyA());
System.out.println(context.contextInterface());
context.setStrategy(new ConcreteStrategyB());
System.out.println(context.contextInterface());
}
}
|
Java Web Programming에 적용
대부분의 if 문으로 분기되는 로직은 모두 Strategy 패턴을 사용가능하다. 왜냐하면 if 문 안의 로직자체가 알고리즘을 나타내기 때문이다. 그렇다고 모든 if 문을 적용하기에는 너무나 많은 객체가 생성될 수 도 있고 또한 너무 많은 클래스가 생성되어 오히려 관리하기가 어려울 수 도 있다. 그러므로 모든 프로그램이 그렇듯이 어떻게 적용할 것인가는 개발자에 달려있으며 이를 적절히 사용해야 한다. (너무 많은 객체의 생성을 막기 위하여 Flyweight 패턴을 사용하면 된다. 사실 Strategy 패턴의 이러한 문제로 Flyweight 패턴과 쌍으로 구현하는 경우가 대부분이다.)
여기서는 Employee 클래스의 getEmployeeList() 메소드를 가지고 설명하겠다. 일반적으로 임직원을 검색할 경우 여러가지 조건이 있을 수 있다. 성명, id, 주민번호, 전화번호 등등…
이럴경우 대부분은 DAO 의 SQL 문을 다음과 같이 짤것이다.
String queryScope = …. (input 변수)
String key = … (input 변수)
String sql = “ select * from EMP “
if (“NAME”.equlas(queryScope))
{
sql = sql + “ where name = ‘” + value + “’”;
}
else if (“ID”.equlas(queryScope))
{
sql = sql + “ where id = ‘” + value + “’”;
}
……
|
간단한 SQL 이라면 눈에 보기 쉽지만 만약 이것이 복잡한 SQL 이면 정말 유지보수가 힘들어 진다. (예를 들어 100 라인 이상되는 SQL 문)
이럴경우 Strategy 패턴을 쓰면 아래와 같이 쉽게 될 수 있다.
EmpolyeeDAO 의 서브클래스, 여기서는 EmployeeOracleDAO 를 Context 로 보면 각각의 쿼리조건마다 다음과 같은 Strategy 를 만들 수 있으며 EmployeeOracleDAO 의 getEmployeeList() 메소드에서 FactoryMethod 패턴을 적용하면 된다.
[EmployeeOracleDAO.java]
…
// SQL 문 쿼리를 직접 사용하던 것을 수정함
EmployeeOracleDAOListStrategy strategy =
StrategyFactory.getEmployeeOracleDAO(queryScope);
retuern Strategy.getEmpList(value);
// 기타 다른 메소드의 SQL 은 그대로 해당 클래스에서 처리됨
|
EmployeeOracleDAOListStrategy.java
public interface EmployeeOracleDAOListStrategy
{
public EmployeeVO getEmpList(String value);
}
|
[NameEmployeeOracleDAOListStrategy.java]
public class NameEmployeListStrategy implements EmployeListStrategy
{
public EmployeeVO getEmpList(String value)
{
// 성명으로 조회하는 SQL 문을 작성
}
}
|
[IDEmployeeOracleDAOListStrategy.java]
public class IDEmployeListStrategy implements EmployeListStrategy
{
public EmployeeVO getEmpList(String value)
{
// ID로 조회하는 SQL 문을 작성
}
}
|
클래스는 많아졌지만 해당 패턴에 익숙한 사람은 각각의 로직이 클래스에 분리되어 있으므로 더 쉽게 유지보수를 할 수 있다.
8. 결론
앞에서 살펴본바와 같이 패턴을 사용하면 각각의 로직이 분산되어 클래스가 많아진다. 또한 어느 하나의 패턴이 독립적으로 사용된다기 보다는 서로 연관되어 서로를 보완해 주면서 결합되는 경우가 많다. GOF 에서 보여주는 패턴들은 대부분 위와 같이 아주 조그마한 곳에서 적용할 수 있는 패턴들이다. 기존의 J2EE 패턴들에서 처럼 거의 독립적으로 큰 그림으로 시스템 설계시에 적용되는 것과는 조금 다르다고 느낄 수 도 있을 것이다. 하지만 모든 패턴들이 그렇듯이 용도에 따라 조금씩 발전 변경된 것이므로 내부 개념을 보면 대부분 GOF 의 패턴을 이용했음을 알 수 있다.
여기서 한가지 중요한 점은 패턴을 적용했다고 해서 속도가 빠른 것은 절대 아니라는 것이다. 물론 속도과 연관된 패턴들이 나와있긴 하다. (J2EE 패턴중에서 ServiceLocator 라든지 ValueObject 패턴등) 그러나 패턴은 어디까지나 객체지향적인 구현을 위한 것이다.
만약에 모두 프로그래머들이 패턴을 안다고 생각해 보자. 그러면 설계나 구현시에 어떠한 어려움이 직면했을 경우 “아 여기서는 객체가 너무 많이 생성되니 Flyweight 패턴을 쓰며 되겠군” 이라고 한명이 이야기 한다면 모두들 그 의도를 이해하고 머리속엔 이미 구현 코드들이 떠오를 것이다.
서론에서도 말했듯이 필자는 개발자들이 훌륭한 시스템을 만들기 위해서는 여러가지 능력이 필요하다고 생각한다. 그중에서 코드의 간결함과 가독성이 중요하다고 생각하여 패턴을 먼저 설명한 것이며 차후에 계속해서 내용을 이어갈 것이다.
마지막으로 여기서 설명한 패턴은 언제든지 여러분들이 때에 맞추어 조금씩 변경할 수 있다. 또한 그렇게 하는 것이 더욱 효과적일 때도 있다. 하지만 잊지말아야 할 것은 해당 패턴의 의도를 정확히 파악하고 그에 맞게 사용해야 한다는 것이다.
댓글
댓글 쓰기