정의

프로토타입 인스턴스를 사용하여 생성할 객체의 종류를 지정하고 이 프로토타입을 복사하여 새로운 객체를 생성

 

코드

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class PrototypeEx {

	static abstract class Shape implements Cloneable {
		public int x;
		public int y;
		public String color;

		public Shape() {}

		protected Shape(Shape target) {
			if (target != null) {
				this.x = target.x;
				this.y = target.y;
				this.color = target.color;
			}
		}

		public abstract Shape clone();

		@Override
		public boolean equals(Object object2) {
			if (!(object2 instanceof Shape))
				return false;
			Shape shape2 = (Shape) object2;
			return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color);
		}
	}

	static class Circle extends Shape {
		public int radius;

		public Circle() {
		}

		public Circle(Circle target) {
			super(target);
			if (target != null) {
				this.radius = target.radius;
			}
		}

		// 공변 반환타입
		@Override
		public Circle clone() {
			return new Circle(this);
		}

		@Override
		public boolean equals(Object object2) {
			if (!(object2 instanceof Circle) || !super.equals(object2))
				return false;
			Circle shape2 = (Circle) object2;
			return shape2.radius == radius;
		}
	}

	static class Rectangle extends Shape {
		public int width;
		public int height;

		public Rectangle() {
		}

		public Rectangle(Rectangle target) {
			super(target);
			if (target != null) {
				this.width = target.width;
				this.height = target.height;
			}
		}

		// 공변 반환타입
		@Override
		public Rectangle clone() {
			return new Rectangle(this);
		}

		@Override
		public boolean equals(Object object2) {
			if (!(object2 instanceof Rectangle) || !super.equals(object2))
				return false;
			Rectangle shape2 = (Rectangle) object2;
			return shape2.width == width && shape2.height == height;
		}
	}

	public static void main(String[] args) {
		List<Shape> shapes = new ArrayList<>();
		List<Shape> shapesCopy = new ArrayList<>();

		Circle circle = new Circle();
		circle.x = 10;
		circle.y = 20;
		circle.radius = 15;
		circle.color = "red";

		Circle circle2 = circle.clone();
		circle2.x = 11;

		// 1
		shapes.add(circle);
		shapesCopy.add(circle);
		// 2
		shapes.add(circle);
		shapesCopy.add(circle2);

		Rectangle rectangle = new Rectangle();
		rectangle.width = 10;
		rectangle.height = 20;
		rectangle.color = "blue";

		Rectangle rectangle2 = rectangle.clone();
		// 3
		shapes.add(rectangle);
		shapesCopy.add(rectangle2);

		cloneAndCompare(shapes, shapesCopy);
	}

	private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {

		for (int i = 0; i < shapes.size(); i++) {
			if (shapes.get(i) != shapesCopy.get(i)) {
//            	System.out.println(System.identityHashCode(shapes.get(i))+" "+System.identityHashCode(shapesCopy.get(i)));
				System.out.print(i + ": 도형이 서로 다른 객체입니다.");
				if (shapes.get(i).equals(shapesCopy.get(i))) {
					System.out.println(i + ": 그리고 같은 값을 가집니다.");
				} else {
					System.out.println(i + ": 그리고 다른 값입니다.");
				}
			} else {
				System.out.println(i + ": 도형이 서로 같은 객체입니다.");
			}
		}
	}
}

결과

0: 도형이 서로 같은 객체입니다.
1: 도형이 서로 다른 객체입니다.1: 그리고 다른 값입니다.
2: 도형이 서로 다른 객체입니다.2: 그리고 같은 값을 가집니다.

 

예시로만 알아두고, 실제 "Cloneable"는 제한적으로 사용해야 한다.

예시는 슈퍼클래스가 abstract라 사용했다. 

이펙티브 자바에 아이템 13 부분에선 Cloneable 대신 복사 생성자나 복사 팩터리를 사용하라 권고한다.(배열은 제외)

위 예시에서 복사 생성자를 사용했다.

 

정의

flyweight는 다른 유사한 개체와 가능한 한 많은 데이터를 공유하여 메모리 사용을 최소화하는 패턴

하나의 인스턴스만으로 상태값만 변경해가며, 사용

주 목적은 메인메모리(RAM) 절약

 

코드

import java.time.LocalDate;
import java.time.Month;

public class FlyweightEx {
	//공통 인터페이스
	static interface Tree {
		public void display(int x, int y);
		public default boolean isWithinRange(LocalDate aDate) {
			Month month = aDate.getMonth();
			return (month.getValue() > 2) && (month.getValue() < 11);
		}
	}
	static class ConiferTree implements Tree {
		public void display(int x, int y) {
			System.out.println("침엽수 위치 : " + x + ", " + y);
		}
	}
	static class DeciduousTree implements Tree {
		public void display(int x, int y) {
			System.out.println("낙엽수 위치 : " + x + ", " + y);
			if (!this.isWithinRange(LocalDate.now())) {
				System.out.println("현재 계절엔 낙엽이 없습니다.");
			}
		}
	}
	//팩토리로 생성을 관리
	static class TreeFactory {
		Tree d, c = null;
		public TreeFactory() {
			this.d = new DeciduousTree();
			this.c = new ConiferTree();
		}
		public Tree getTree(String type) throws Exception {
			if (type.equals("침엽수")) {
				return this.d;
			} else if (type.equals("낙엽수")) {
				return this.c;
			} else {
				throw new Exception("지원하지 않는 종류");
			}
		}
	}
	
	public static void main(String[] args) {
		//플레이웨이트 클래스에서 상태만을 때어 별도로 관리
		//상태가 바뀔때마다 마치 새로운 인스턴스 처럼 보이지만, 하나의 인스턴스
		int[][] deciduousLocations = {{1, 1}, {33, 50}, {100, 90}};
		int[][] coniferLocations = {{10, 87}, {24, 76}, {2, 64}};
		
		TreeFactory treeFactory = new TreeFactory();
		Tree d, c;
		try {
			d = treeFactory.getTree("낙엽수");
			c = treeFactory.getTree("침엽수");
			for (int[] location : deciduousLocations) {
				d.display(location[0],  location[1]);
			}
			for (int[] location : coniferLocations) {
				c.display(location[0],  location[1]);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

결과

침엽수 위치 : 1, 1
침엽수 위치 : 33, 50
침엽수 위치 : 100, 90
낙엽수 위치 : 10, 87
낙엽수 위치 : 24, 76
낙엽수 위치 : 2, 64

웹 프로그래밍에선 쓰이는 곳을 찾기가 더 힘들 듯하다.

정의

책임 연쇄 패턴은 핸들러들의 체인을 따라 요청을 처리하는 행동 디자인 패턴입니다.

따라서 순서가 굉장히 중요합니다. 

각 핸들러는 주어진 요청에 대한 처리를 담당하게 됩니다. (요청을  처리할지 말지, 다음 체인으로 넘길지 등등)

 

 

코드

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class CoREx {
	//커맨드 객체
	static class Person {
		String name;
		String sex;
		int age;
		public Person(String name, String sex, int age) {
			this.name = name;
			this.sex = sex;
			this.age = age;
		}
		@Override
		public String toString() {
			return "Person [name=" + name + ", sex=" + sex + ", age=" + age + "]";
		}
	}
	//각 요소를 요청이라고 가정한다.
	static List<Person> persons = new ArrayList<>();
	static {
		persons.add(new Person("김필터", "남", 20));
		persons.add(new Person("임자바", "남", 33));
		persons.add(new Person("이자바", "여", 22));
		persons.add(new Person("김자바", "여", 18));
		persons.add(new Person("이육사", "남", 27));
		persons.add(new Person("김육사", "여", 15));
		persons.add(new Person("박세종", "남", 30));
		persons.add(new Person("김세종", "남", 30));
	}
	//핸들러 
	static class Filter<T>{
		//나의 다음 체인 참조변수
		private Filter<T> nextFilter;
		//요구 사항
		private final Predicate<T> predi;
		
		protected Filter(Predicate<T> predi) {
			this.predi = predi;
		}
		@SafeVarargs //필터 체인 형성
		public static <T> Filter<T> createFilterChain(Filter<T> first, Filter<T>...filters){
			Filter<T> nowFilter = first;
			for(Filter<T> filter : filters) {
				nowFilter.nextFilter = filter;
				nowFilter = filter;
			}
			return first;
		}
		public boolean check(T data) {
			return predi.test(data) ? nextCheck(data) : false;
		}
		public boolean nextCheck(T data) {
			return nextFilter == null ? true : nextFilter.check(data);
		}
	}
	
	public static void main(String[] args) {
		//필터체인
		Filter<Person> filterChain = Filter.createFilterChain(
				new Filter<>(per -> per.age>=20)
				,new Filter<>(per -> per.sex.startsWith("남"))
				,new Filter<>(per -> per.name.startsWith("김"))
				);
		for(Person person : persons) {
			if(filterChain.check(person)) {
				System.out.println(person);
			}
		}
	}
}

결과

Person [name=김필터, sex=남, age=20]
Person [name=김세종, sex=남, age=30]

Filter는 사실 인터페이스나 추상클래스로 다루는 것이 더 좋으나, 편의상 일반 클래스로 구현

 

유사한 구조는 스프링 시큐리티에서 볼 수 있다.

 

 

'개발 > 디자인 패턴' 카테고리의 다른 글

생성 - 프로토타입(Prototype)  (0) 2023.05.13
구조 - 플라이웨이트(Flyweight)  (0) 2023.05.09
구조 - 빌더 패턴(Builder)  (0) 2023.05.03

정의

복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다.

이 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있습니다.

 

빌더 패턴은 텔레스코핑 생성자 안티 패턴에 대한 해결책을 제공합니다.

public class BuilderEx {
	static class House{
		String interior; //내장재
		String exterior; //외장재
		int floor; // 층수
		int room;  // 방수
		int window;// 창문수
		
		public House(String interior, String exterior, int floor, int room, int window) {
			super();
			this.interior = interior;
			this.exterior = exterior;
			this.floor = floor;
			this.room = room;
			this.window = window;
		}

		@Override
		public String toString() {
			return "House [interior=" + interior + ", exterior=" + exterior + ", floor=" + floor + ", room=" + room
					+ ", window=" + window + "]";
		}
	}
	
	public static void main(String[] args) {
		System.out.println(new House("황토", "나무", 2, 4, 10));
        //이 코드 한줄만 보고 뭐가 내장재인지, 외장재인지
        //뭐가 층수, 방수, 창문수인지 알 수가 없다.
	}

위 처럼 생성자 인수가 늘어날 수록 순서를 신경써야한다.

 

빌더 적용

	//빌더
	static interface Builder{
		//편의상 메서드 체이닝을 적용한 경우가 많다.
		Builder setInterior(String interior);
		Builder setExterior(String exterior);
		Builder setFloor(int floor);
		Builder setRoom(int room);
		Builder setWindow(int window);
		//생성 메서드는 단일 계층 구조엔 상관없으나, 다중 계층구조이면 서브클래스에 위임한다.
		//리턴하는 객체 타입이 다르기 때문이다.
		House build();
	}
	
	static class HouseBuilder implements Builder{
		//기존 코드가 설정자 메서드를 지원한하기에 임시로 값을 저장해둔다.
		String interior; //내장재
		String exterior; //외장재
		int floor; // 층수
		int room;  // 방수
		int window;// 창문수
		
		public Builder setInterior(String interior) {
			this.interior = interior;
			return this;
		}
		public Builder setExterior(String exterior) {
			this.exterior = exterior;
			return this;
		}
		public Builder setFloor(int floor) {
			this.floor = floor;
			return this;
		}
		public Builder setRoom(int room) {
			this.room = room;
			return this;
		}
		public Builder setWindow(int window) {
			this.window = window;
			return this;
		}
		@Override
		public House build() {
			//검증 로직이 존재할 수도 있을 것, 혹은 기본값으로 설정되지 않은 값을 대신 할 수도 있다.
			if(interior == null && exterior == null 
					&& floor == 0&& room == 0&& window == 0) {
				//기본적으로 빌더 패턴은 사용자가 생성구조를 잘 파악하고 있어야 함을 전제로 한다.
				throw new IllegalArgumentException("인수가 부족합니다.");
			}
			try {
				return new House(interior, exterior, floor, room, window);
			}finally {
				interior = null;
				exterior = null;
				floor = 0;
				room = 0;
				window = 0;
			}
		}
	}
	
	public static void main(String[] args) {
		Builder houseBuilder = new HouseBuilder();
		houseBuilder.setExterior("나무");
		houseBuilder.setInterior("대리석");
		houseBuilder.setFloor(2);
		houseBuilder.setRoom(5);
		houseBuilder.setWindow(10);
		System.out.println(houseBuilder.build());
		houseBuilder.setExterior("콘크리트")
					.setInterior("유리")
					.setFloor(1)
					.setRoom(1)
					.setWindow(10);
		System.out.println(houseBuilder.build());
	}

빌더 패턴에선 기본적으로 사용자가 생성할 객체 구성을 직접 디자인한다. 따라서 잘알고 있다고 가정을 한다.

이렇게 객체 구조를 잘 알아야한다는 것이 단점이다.

디렉터

디렉터 클래스를 두어 자주쓰는 구성 정보를 재사용할 수도 있다.

	static class HouseDirector{
		public void house1(Builder builder) {
			builder.setExterior("나무");
			builder.setInterior("대리석");
			builder.setFloor(2);
			builder.setRoom(5);
			builder.setWindow(10);
		}
		public void house2(Builder builder) {
			builder.setExterior("콘크리트")
				.setInterior("유리")
				.setFloor(1)
				.setRoom(1)
				.setWindow(10);
		}
	}
	
	public static void main(String[] args) {
		Builder houseBuilder = new HouseBuilder();
		HouseDirector houseDirector = new HouseDirector();
		houseDirector.house1(houseBuilder);
		System.out.println(houseBuilder.build());
		houseDirector.house2(houseBuilder);
		System.out.println(houseBuilder.build());
	}

builder.zip
0.00MB

자주 보는 클래스에 빌더 패턴 사례

 

+ Recent posts