Repository navigation
Design Document
- LCS Algorithm
- MVC
- OO Principles
- Designed to be testable by unit-test tools
LCS 알고리즘은 아래와 같은 클래스 인터페이스 하에 호출하도록 설계하였다.
public class LCS<T extends Comparable<T>> implements Diff.Algorithm<T> {
@Override
public List<Pair<Integer>> diff(List<T> l, List<T> r) { ... }
...
}LCS 클래스의 diff 메소드는 List 2개를 받아 비교하여 공통되는 순열의 리스트 인덱스 쌍을 반환하는 메소드이다. diff 메소드의 구현은 리스트의 요소가 공통되는 요소인지 확인할 수 있는 비교 메커니즘이 필요하다. 이를 위해 Comparable의 서브타입만 LCS 클래스를 생성할 수 있도록 제약을 걸어두었다.
따라서 이후에 텍스트를 String보다 복잡한 클래스를 사용하더라도 compareTo 메소드만 구현하면 LCS 클래스를 사용할 수 있다.
아래는 diff 메소드의 반환값에 대한 설명이다.
각 Pair<Integer>는 LCS 알고리즘 하에서 매칭되는 인덱스 쌍이다.
예를 들어 List<T> L, R이 T=Character 일 때 아래 처럼 들어왔다고 가정하면
L: ['a', 'b', 'c']
R: ['a', 'c', 'd']
이면 LCS는 ['a', 'c'] 이다.
LCS의 첫 요소인 'a'에 대해서는 Pair<Integer>는 (0, 0) 이다.
> L에서는 index 0 위치에 'a'가 있고, R에서도 index 0 위치에 'a'가 있기 때문이다.
LCS의 두번째 요소인 'c'에 대해서는 Pair<Integer>는 (2, 1) 이다.
> L에서는 index 2 위치에 'c'가 있고, R에서는 index 1 위치에 'c'가 있기 때문이다.
위의 시퀀스 다이어그램은 각 EditPanel에 텍스트 파일이 Load 된 이후
- Compare 버튼을 누르고
- Copy to right 버튼을 눌렀을 때
이벤트가 MVC 구조에서 전달되는 모습을 보여준다. Model인 Merger 클래스는 Controller로부터 유저 입력을 전달받아 Text 강조 변경, Text 치환 이벤트 등을 Controller에 인터페이스를 통해 반환한다.
EditPanel class는 VBox를 상속하여 JavaFX의 GUI Component처럼 쓸 수 있다.
EditPanel.java
public class EditPanel extends VBox implements Initializable {
...
}애플리케이션의 메인 뷰를 정의하는 fxml 파일에 EditPanel을 GUI Component로 쓰고 있는 것을 확인할 수 있다.
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import SimpleMerge.control.EditPanel?>
<AnchorPane fx:id="root" prefHeight="400" prefWidth="800" xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1" fx:controller="SimpleMerge.Controller">
...
<items>
<EditPanel fx:id="leftEditPanel"></EditPanel>
<EditPanel fx:id="rightEditPanel"></EditPanel>
</items>
...
</AnchorPane>MVC 모델에서 Model-Controller 관계와, GUI Component 계층에서 상위 컴포넌트와 하위 컴포넌트 관계에서 DIP와 ISP 원칙이 적용되었다.
Text 비교 및 하이라이팅/병합 작업을 하는 Model인 Merger는 Controller에 이벤트를 직접 전달하지 않고 내부 인터페이스를 통해 전달한다. Merger는 3개 인터페이스를 받는다.
Merger.java
public class Merger<T extends Comparable<T>> {
...
public interface MergeEventListener {
void onStart();
void onEnd();
}
public interface UpdateBlockStyleEventListener {
void onUpdateBlockStyle(Block block, BlockState style);
}
public interface UpdateItemsEventListener<T> {
void onUpdateItems(Block block, List<T> items, boolean includeLastItem);
}
}main 컴포넌트가 EditPanel 컴포넌트를 하위 컴포넌트로 가지고 있다. 따라서 EditPanel은 main 컴포넌트에 의존하지 않고 인터페이스에 의존하여 main 컴포넌트에 이벤트를 전달해야 한다. 추가적으로 EditPanel은 파일을 Load 하기 위해 다른 컴포넌트에 의존하는데, 이 또한 인터페이스에 의존하도록 하였다.
EditPanel.java
public class EditPanel extends VBox implements Initializable {
...
private void emitLoad() {
if (eventListener == null)
return;
eventListener.onLoad();
}
private void emitEdit() {
if (eventListener == null)
return;
eventListener.onEdit();
}
private void emitSave() {
if (eventListener == null)
return;
eventListener.onSave();
}
private void emitTextChanged() {
if (eventListener == null)
return;
eventListener.onTextChanged();
}
...
public void setEventListener(EditPanelEventListener eventListener) {
this.eventListener = eventListener;
}
public void setFileSelector(FileSelector selector) {
this.selector = selector;
}
...
}EditPanelEventListener.java
public interface EditPanelEventListener {
void onLoad();
void onSave();
void onEdit();
void onTextChanged();
}FileSelector.java
public interface FileSelector {
File getFile();
File saveFile();
}- 각각의 컴포넌트나 기능들이 서로 의존적이지 않아 Mock이 테스트를 할 수 있도록 Interface로 분리.
- Merger, LCS 등 MVC에서 모델은 의존하는 구체 클래스가 없어 별도의 Mocking 없이도 유닛 테스트가 가능하다.
- MVC에서 Controller에 해당하는 EditPanel은 의존하는 컴포넌트를 인터페이스(FileSelector)에 의존하도록 만들어 Mocking하여 테스트되도록 하였다.