본문 바로가기

디자인 패턴/Java언어로 배우는 디자인패턴 입문 책 정리

Visitor 패턴

상태값을 가지는 클래스와 상태값을 이용한 비즈니스 로직을 가지는 클래스를 분리하여

로직 클래스가 상태값 클래스를 방문하면서 로직을 수행하가는 패턴


1. 코드

Element: 상태값 클래스의 부모 클래스

public interface Element {
	void accept(Visitor visitor); // 로직 클래스의 로직을 실행시키는 메소드
}

 

File: 상태값 클래스1

public class File implements Element {
	
	private String name;
	private int size;
	
	public File(String name, int size) {
		this.name = name;
		this.size = size;
	}
	
	public String getName() {
		return this.name;
	}
	
	public int getSize() {
		return this.size;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

 

Directory: 상태값 클래스2

public class Directory implements Element {
	
	private String name;
	List<Element> list;
	
	public Directory(String name) {
		this.name = name;
	}
	
	public void add(Element element) {
		if (list == null) {
			list = new ArrayList<>();
		}
		list.add(element);
	}
	
	public String getName() {
		return this.name;
	}
	
	public List<Element> getList() {
		return list;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

 

Visitor: 로직 클래스의 부모 클래스

public interface Visitor {
	void visit(File file);
	void visit(Directory directory);
}

 

PrintNameVisitor: 로직 클래스1 (directory를 탐색하며 경로 출력 하는 로직)

public class PrintNameVisitor implements Visitor {

	private String curLocationName = "";
	
	@Override
	public void visit(File file) {
		System.out.println(curLocationName + "/" + file.getName());
	}

	@Override
	public void visit(Directory directory) {
		String tmp = curLocationName;
		curLocationName += "/" + directory.getName();
		System.out.println(curLocationName);
		
		List<Element> elements = directory.getList();
		for (Element element : elements) {
			element.accept(this);
		}
		
		curLocationName = tmp;
	}
}

 

SizeCalculateVisitor: 로직 클래스2 (현재 디렉토리안에 있는 파일들의 size 합을 구하는 로직)

public class SizeCalculateVisitor implements Visitor {

	private int size = 0;
	
	public int getSize() {
		return this.size;
	}
	
	@Override
	public void visit(File file) {
		size += file.getSize();
	}

	@Override
	public void visit(Directory directory) {
		List<Element> elements = directory.getList();
		
		for (Element element : elements) {
			element.accept(this);
		}
	}
}

 

Main: 테스트 클래스

public class Main {

	public static void main(String[] args) {
		Directory root = new Directory("root");
		Directory dir1 = new Directory("dir1");
		Directory dir2 = new Directory("dir2");
		Directory dir3 = new Directory("dir3");
		root.add(dir1);
		root.add(dir2);
		root.add(dir3);
		
		dir1.add(new File("dir1_f1", 10));
		dir1.add(new File("dir1_f2", 20));
		Directory dir1_1 = new Directory("dir1_1");
		dir1.add(dir1_1);
		dir1_1.add(new File("dir1_1_f1", 23));
		
		dir2.add(new File("dir2_f1", 22));
		
		dir3.add(new File("dir3_f1", 2));
		dir3.add(new File("dir3_f2", 2));
		
		root.accept(new PrintNameVisitor());
		SizeCalculateVisitor sizeCalculateVisitor = new SizeCalculateVisitor();
		root.accept(sizeCalculateVisitor);
		System.out.println(sizeCalculateVisitor.getSize());
	}
}

 

2. 특징

- 로직 클래스의 visit 메소드와 상태값 클래스의 accept 메소드가 서로를 호출 함

- 상태값과 로직을 분리

- 새로운 로직이 필요할 때 로직 클래스를 따로 생성만 하면 됨 (상태값 클래스는 변하지 않음)

        -> OCP 원칙 (확장은 열고 기존 클래스의 수정은 닫는 원칙)

- 상태값 클래스가 늘어나는 상황이 예상 되는 상황에서는 비효율일 수 있음

        -> 새로운 상태값 클래스에 대해 모든 로직 클래스에서 대응이 필요하다

- 여러 형태의 상태값 클래스에서 공통 로직이 필요할 때 로직 클래스에서 공통 메소드를 사용하면 됨

        -> 로직이 상태값 클래스에 있으면 공통 로직을 빼기 애매해짐

- 로직이 각 상태값 클래스에서 어떻게 실행되는지 한눈에 파악 가능 / 모든 로직 파악은 한눈에 파악 불가

 

3. 해당 패턴을 고려해볼만한 상황

- 여러 상태가 존재하고 그 상태들의 관계가 복잡한 경우 (복잡한 상태와 로직을 분리)

- 여러 상태값들이 가지는 로직 중 공통적으로 사용해야하는 로직이 있는 경우

- 상태값의 변동이 없고 로직의 추가가 예상되는 경우

'디자인 패턴 > Java언어로 배우는 디자인패턴 입문 책 정리' 카테고리의 다른 글

Facade 패턴  (0) 2021.02.12
Chain Of Responsibility 패턴  (0) 2021.02.11
Decorator 패턴  (0) 2021.01.30
Composite 패턴  (0) 2021.01.24
strategy 패턴  (0) 2021.01.24