개발자입니다
[Java] 예제 소스 정리 - 디자인 패턴 13가지 본문
com.eomcs.design_pattern
GoF 의 디자인 패턴 23가지 중 13가지
생성 패턴
Abstract Factory
Builder
Factory Method
Prototype
Singleton
구조 패턴
Adapter
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
행위 패턴
Chain of Responsibility
Command
Interpreter
Iterator
Mediator
Memento
Observer
State
Strategy
Template Method
Visitor
UML 그림
예제 소스 정리
Abstract Factory
com.eomcs.design_pattern.abstract_factory
추상 팩토리 패턴 - 팩토리 객체를 추상화 시키는 방식. 다양한 팩토리로 대체할 수 있다.
package com.eomcs.design_pattern.abstract_factory;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
// 설계
// UnitFactory 인터페이스: 공장 객체의 사용 규칙을 정의
// TerranUnitFactory 클래스: UnitFactory 구현 클래스. 테란족에 맞춰서 유닛 생성.
// ProtossUnitFactory 클래스: UnitFactory 구현 클래스. 프로토스족에 맞춰서 유닛 생성.
// ZergUnitFactory 클래스: UnitFactory 구현 클래스. 저그족에 맞춰서 유닛 생성.
//
Scanner keyboard = new Scanner(System.in);
int type;
do {
System.out.print("종족을 선택하시오?(1:테란, 2:프로토스, 3:저그) ");
type = keyboard.nextInt();
if (type > 0 && type < 4) {
break;
}
System.out.println("번호가 맞지 않습니다.");
} while (true);
keyboard.close();
UnitFactory unitFactory = null;
switch (type) {
case 1:
System.out.println("테란족을 선택하셨습니다.");
unitFactory = new TerranUnitFactory();
break;
case 2:
System.out.println("프로토스족을 선택하셨습니다.");
unitFactory = new ProtossUnitFactory();
break;
default:
System.out.println("저그족을 선택하셨습니다.");
unitFactory = new ZergUnitFactory();
}
// 식당 짓기
Unit u1 = unitFactory.createUnit(UnitFactory.RESTAURANT);
u1.build(); // 수퍼 클래스에서 상속 받은 메서드를 호출한다.
System.out.println("-----------------------------");
// 훈련소 짓기
Unit u2 = unitFactory.createUnit(UnitFactory.TRAINING_CENTER);
u2.build();
}
}
인터페이스 또는 추상클래스
package com.eomcs.design_pattern.abstract_factory;
// 추상 팩토리 : 팩토리의 사용 규칙만 정의한다.
// => 생산 방식은 같지만 주제에 따라 생산품은 달라진다.
// => 생산 방식이 같으려면 메서드의 사용법이 같아야 한다.
// 그래서 인터페이스로 팩토리 사용 규칙을 정의하는 것이다.
//
public interface UnitFactory {
// 인터페이스의 필드는 모두 public static final 이다.
int RESTAURANT = 1;
int TRAINING_CENTER = 2;
// 공장 객체의 사용 규칙
// => 유닛을 생성하고 싶다면 createUnit()을 호출하라!
// => 사용 규칙은 메서드 시그너처만 선언한다. 구현하지 않는다.
// => 규칙이기에 무조건 public 으로 공개된다.
// => 추상 메서드이다.
Unit createUnit(int building);
}
package com.eomcs.design_pattern.abstract_factory;
// 이 클래스는 직접 사용할 클래스가 아니다.
// => 건물을 짓는데 필요한 기본 필드와 메서드를 서브 클래스에게 상속해주는 용도로 사용한다.
// => 일부 메서드는 추상 메서드이다.
// => 따라서 이 클래스는 추상 클래스이어야 한다.
//
// 결국, 이 클래스는 특정 빌딩을 짓는 서브 클래스를 만들기 위한 틀(template)로서 사용된다.
// 기본 흐름은 build() 메서드에 정의되어 있다.
// 흐름에서 각 단계는 추상 메서드로 선언되어 있다.
// 어떤 작업을 수행할지는 서브 클래스에서 정의하는 것이다.
// 즉 Unit 클래스는 서브 클래스가 구현해야 할 틀
// (template method; prepare(), construct(), install(), interio())를 제공한다.
public abstract class Unit {
// 건물의 type 값을 지정할 때 직접 숫자를 사용하면 나중에 알아보기 힘들다.
// 유지보수가 쉬우려면 숫자 대신 문자를 사용하는 것이 낫다.
// 그래서 상수 변수를 활용하는 것이다.
//
public static final int GENERAL_BUILDING = 0;
public static final int DEFENCE_BUILDING = 1;
public static final int ATTACK_BUILDING = 2;
protected String name;
protected int area;
protected int type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getArea() {
return area;
}
public void setArea(int area) {
this.area = area;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
// 수퍼 클래스에서 기본 흐름을 정의한다.
// => 그래서 build() 메서드는 구현 메서드로 정의한다.
//
public void build() {
prepare(); // 건물을 지을 땅을 준비한다.
construct(); // 건물 뼈대를 짓는다.
install(); // 배선 및 단열, 외장을 설치한다.
interio(); // 내부 인테리어를 한다.
}
// 유닛의 종류에 따라 건물을 짓는 방식이 다르기 때문에
// 구체적인 구현은 서브 클래스에게 맡긴다.
// => 따라서 build()가 호출하는 메서드는 추상 메서드로 선언한다.
//
public abstract void prepare();
public abstract void construct();
public abstract void install();
public abstract void interio();
}
UnitFactory 구현체
package com.eomcs.design_pattern.abstract_factory;
// 추상 팩토리 규칙에 따라 공장 클래스를 만들기
// => 직접 UnitFactory 인터페이스를 구현해도 되지만,
// 서브 클래스에게 상속 해줄 필드나 메서드가 있다면 중간에 추상 클래스 문법을 적용하여
// 추상 클래스를 만드는 것이 좋다.
// => 서브 클래스에게 상속 해줄 필드나 메서드가 없다면 직접 인터페이스를 구현하라.
//
public class TerranUnitFactory implements UnitFactory{
// 객체 생성 과정이 복잡한 경우 직접 객체를 생성하기 보다는
// 객체를 생성해주는 메서드를 호출하는 것이 유지보수에 좋다.
//
public Unit createUnit(int building) {
switch (building) {
case RESTAURANT:
return createRestaurant();
case TRAINING_CENTER:
return createTrainingCenter();
}
return null;
}
private Unit createRestaurant() {
Unit unit = new Restaurant();
unit.setName("테란: 군인식당");
unit.setArea(50);
unit.setType(Unit.GENERAL_BUILDING);
return unit;
}
private Unit createTrainingCenter() {
Unit unit = new TrainingCenter();
unit.setName("테란: 훈련소");
unit.setArea(500);
unit.setType(Unit.GENERAL_BUILDING);
return unit;
}
}
package com.eomcs.design_pattern.abstract_factory;
// 추상 팩토리 규칙에 따라 공장 클래스를 만들기
// => 직접 UnitFactory 인터페이스를 구현해도 되지만,
// 서브 클래스에게 상속 해줄 필드나 메서드가 있다면 중간에 추상 클래스 문법을 적용하여
// 추상 클래스를 만드는 것이 좋다.
// => 서브 클래스에게 상속 해줄 필드나 메서드가 없다면 직접 인터페이스를 구현하라.
//
public class ZergUnitFactory implements UnitFactory{
// 객체 생성 과정이 복잡한 경우 직접 객체를 생성하기 보다는
// 객체를 생성해주는 메서드를 호출하는 것이 유지보수에 좋다.
//
public Unit createUnit(int building) {
switch (building) {
case RESTAURANT:
return createRestaurant();
case TRAINING_CENTER:
return createTrainingCenter();
}
return null;
}
private Unit createRestaurant() {
Unit unit = new Restaurant();
unit.setName("저그: 군인식당");
unit.setArea(200);
unit.setType(Unit.ATTACK_BUILDING);
return unit;
}
private Unit createTrainingCenter() {
Unit unit = new TrainingCenter();
unit.setName("저그: 훈련소");
unit.setArea(100);
unit.setType(Unit.ATTACK_BUILDING);
return unit;
}
}
package com.eomcs.design_pattern.abstract_factory;
// 추상 팩토리 규칙에 따라 공장 클래스를 만들기
// => 직접 UnitFactory 인터페이스를 구현해도 되지만,
// 서브 클래스에게 상속 해줄 필드나 메서드가 있다면 중간에 추상 클래스 문법을 적용하여
// 추상 클래스를 만드는 것이 좋다.
// => 서브 클래스에게 상속 해줄 필드나 메서드가 없다면 직접 인터페이스를 구현하라.
//
public class ProtossUnitFactory implements UnitFactory{
// 객체 생성 과정이 복잡한 경우 직접 객체를 생성하기 보다는
// 객체를 생성해주는 메서드를 호출하는 것이 유지보수에 좋다.
//
public Unit createUnit(int building) {
switch (building) {
case RESTAURANT:
return createRestaurant();
case TRAINING_CENTER:
return createTrainingCenter();
}
return null;
}
private Unit createRestaurant() {
Unit unit = new Restaurant();
unit.setName("프로토스: 군인식당");
unit.setArea(70);
unit.setType(Unit.DEFENCE_BUILDING);
return unit;
}
private Unit createTrainingCenter() {
Unit unit = new TrainingCenter();
unit.setName("프로토스: 훈련소");
unit.setArea(300);
unit.setType(Unit.ATTACK_BUILDING);
return unit;
}
}
Unit 상속 클래스
package com.eomcs.design_pattern.abstract_factory;
public class Restaurant extends Unit {
@Override
public void prepare() {
System.out.println(this.name + " : 간단히 땅을 고른다.");
}
@Override
public void construct() {
System.out.println(this.name + " : 조립 파넬을 세운다.");
}
@Override
public void install() {
System.out.println(this.name + " : 내부 배선과 창을 붙인다.");
}
@Override
public void interio() {
System.out.println(this.name + " : 식탁과 의자를 배치한다.");
}
}
package com.eomcs.design_pattern.abstract_factory;
public class TrainingCenter extends Unit {
@Override
public void prepare() {
System.out.println(this.name + " : 운동장을 고르고, 숙소 건물 땅에 콘크리트 포장한다.");
}
@Override
public void construct() {
System.out.println(this.name + " : 운동장에 잔디를 깔고, 숙소 건물을 짓는다.");
}
@Override
public void install() {
System.out.println(this.name + " : 운동장에 연설대를 설치하고 숙소의 내부 배선과 창을 붙인다.");
}
@Override
public void interio() {
System.out.println(this.name + " : 숙소의 내부 침실을 꾸민다.");
}
}
Builder
com.eomcs.design_pattern.builder
빌더 패턴: 여러 개의 객체를 조립하여 한 객체를 생성할 때 사용하는 방법.
=> 복합 객체처럼 복잡한 객체의 생성 과정을 캡슐화(한 클래스에 감춰)하였기 때문에
유지보수가 쉽다.
package com.eomcs.design_pattern.builder;
public class Test {
public static void main(String[] args) {
CarBuilder carBuilder = new CarBuilder();
Car c1 = carBuilder.build("비트자동차 모델1");
System.out.println(c1);
System.out.println("---------------------");
Car c2 = carBuilder.build("비트자동차 모델2");
System.out.println(c2);
System.out.println("---------------------");
Car c3 = carBuilder.build("비트자동차 모델3");
System.out.println(c3);
System.out.println("---------------------");
}
}
package com.eomcs.design_pattern.builder;
public class CarBuilder {
Body[] bodyList = {
new Body("비트차체I", "소형"),
new Body("비트차체II", "중형"),
new Body("현대차체I", "대형")
};
Engine[] engineList = {
new Engine("엔진I","800",4,16),
new Engine("엔진II","1500",4,16),
new Engine("엔진III","1980",4,16),
new Engine("엔진IV","3000",8,32)
};
Door[] doorList = {
new Door("스포츠-앞"),
new Door("스포츠-뒤"),
new Door("SUV-앞"),
new Door("SUV-뒤")
};
public Car build(String carModel) {
switch (carModel) {
case "비트자동차 모델1":
Car car = new Car(bodyList[0], engineList[1]);
car.addDoor(doorList[0]);
car.addDoor(doorList[0]);
return car;
case "비트자동차 모델2":
car = new Car(bodyList[2], engineList[3]);
car.addDoor(doorList[2]);
car.addDoor(doorList[2]);
car.addDoor(doorList[3]);
car.addDoor(doorList[3]);
return car;
default:
throw new RuntimeException("유효한 모델명이 아닙니다.");
}
}
}
package com.eomcs.design_pattern.builder;
import java.util.ArrayList;
public class Car {
Body body;
Engine engine;
ArrayList<Door> doors = new ArrayList<>();
public Car(Body body, Engine engine) {
this.body = body;
this.engine = engine;
}
public void addDoor(Door door) {
doors.add(door);
}
@Override
public String toString() {
return "Car [body=" + body + ", engine=" + engine + ", doors=" + doors + "]";
}
}
package com.eomcs.design_pattern.builder;
public class Body {
String model;
String type;
public Body(String model, String type) {
this.model = model;
this.type = type;
}
@Override
public String toString() {
return "Body [model=" + model + ", type=" + type + "]";
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
package com.eomcs.design_pattern.builder;
public class Engine {
String model;
String cc;
int cylinder;
int valve;
public Engine(String model, String cc, int cylinder, int valve) {
this.model = model;
this.cc = cc;
this.cylinder = cylinder;
this.valve = valve;
}
@Override
public String toString() {
return "Engine [model=" + model + ", cc=" + cc + ", cylinder=" + cylinder + ", valve=" + valve
+ "]";
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getCc() {
return cc;
}
public void setCc(String cc) {
this.cc = cc;
}
public int getCylinder() {
return cylinder;
}
public void setCylinder(int cylinder) {
this.cylinder = cylinder;
}
public int getValve() {
return valve;
}
public void setValve(int valve) {
this.valve = valve;
}
}
package com.eomcs.design_pattern.builder;
public class Door {
String model;
public Door(String model) {
this.model = model;
}
@Override
public String toString() {
return "Door [model=" + model + "]";
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
Factory Method
com.eomcs.design_pattern.factory_method
팩토리 메서드 적용 전
package com.eomcs.design_pattern.factory_method;
class Car {
String model;
int cc;
boolean sunroof;
}
public class Test01 {
public static void main(String[] args) {
// 인스턴스를 만들 때 사용에 적합하도록 직접 초기화시켜야 한다.
Car c1 = new Car();
c1.model = "티코";
c1.cc = 890;
c1.sunroof = false;
Car c2 = new Car();
c2.model = "티코 골드";
c2.cc = 890;
c2.sunroof = true;
Car c3 = new Car();
c3.model = "소나타";
c3.cc = 1980;
c3.sunroof = false;
Car c4 = new Car();
c4.model = "소나타 골드";
c4.cc = 1980;
c4.sunroof = true;
}
}
팩토리 메서드 적용 후
package com.eomcs.design_pattern.factory_method;
class Car2 {
String model;
int cc;
boolean sunroof;
}
// 인스턴스 생성과정이 복잡할 때 사용하는 설계 기법이다.
// 복잡한 객체 생성 코드를 메서드에 캡슐화한다.
// 메서드 호출을 통해 인스턴스를 리턴 받는다.
// => 이런 메서드를 '팩토리 메서드'라 부른다.
// => 보통 인스턴스의 팩토리 역할을 하는 클래스는 'XxxFactory'라는 이름을 짓는다.
//
class Car2Factory {
// 인스턴스를 생성해주는 메서드를 정의한다.
// => 이렇게 설계하는 기법을 "팩토리 메서드"라 한다.
public static Car2 create(String product) {
Car2 c = new Car2();
switch (product) {
case "tc":
c.model = "티코";
c.cc = 890;
c.sunroof = false;
break;
case "tcg":
c.model = "티코 골드";
c.cc = 890;
c.sunroof = true;
break;
case "sn":
c.model = "소나타";
c.cc = 1980;
c.sunroof = false;
break;
case "sng":
c.model = "소나타 골드";
c.cc = 1980;
c.sunroof = true;
break;
default:
return null;
}
return c;
}
}
public class Test02 {
public static void main(String[] args) {
Car2 c1 = Car2Factory.create("tc");
Car2 c2 = Car2Factory.create("tcg");
Car2 c3 = Car2Factory.create("sn");
Car2 c4 = Car2Factory.create("sng");
Car2 c5 = Car2Factory.create("ok");
System.out.println(c1.model);
System.out.println(c2.model);
System.out.println(c3.model);
System.out.println(c4.model);
System.out.println(c5.model);
}
}
Singleton
com.eomcs.design_pattern.singleton
Singleton 적용 전
package com.eomcs.design_pattern.singleton;
class Car1 {
String model;
int cc;
// 생성자를 정의하지 않으면 다음의 기본 생성자가 자동으로 추가된다.
//public Car1() {}
}
public class Test01 {
public static void main(String[] args) {
// 인스턴스 생성
// => 인스턴스를 여러 개 생성할 수 있다.
Car1 c1 = new Car1();
Car1 c2 = new Car1();
System.out.println(c1 == c2);
}
}
Singleton 적용 후
인스턴스를 딱 한 개만 생성하여 공유하고 싶다면,
Singleton 설계 방식으로 클래스를 정의하라!
class Car2 {
String model;
int cc;
// 인스턴스 주소를 받을 클래스 필드를 선언한다.
private static Car2 instance;
// 1) 생성자를 정의하고 private으로 선언하여 비공개로 만들어라.
// => 비공개 생성자를 외부에서 호출할 수 없다.
// => 오직 내부에서만 호출할 수 있다.
private Car2() {}
// 2) 인스턴스를 생성해주는 메서드를 정의한다.
public static Car2 getInstance() {
if (Car2.instance == null) {
// 아직 인스턴스를 생성한 적이 없다면 즉시 인스턴스를 생성한다.
Car2.instance = new Car2();
}
// 기존에 변수에 저장된 인스턴스 주소를 리턴한다.
return Car2.instance;
}
}
public class Test02 {
public static void main(String[] args) {
// 생성자가 존재하지만 private으로 비공개 되어 있기 때문에 직접 호출할 수 없다.
// 생성자를 호출할 수 없으면 인스턴스를 생성할 수 없다.
// => 다른 메서드를 호출하여 인스턴스를 생성하라는 의미다.
//Car2 c1 = new Car2(); // 컴파일 오류!
// 인스턴스를 생성해주는 메서드를 통해 인스턴스를 얻는다.
Car2 c2 = Car2.getInstance();
Car2 c3 = Car2.getInstance();
Car2 c4 = Car2.getInstance();
System.out.println(c2 == c3);
System.out.println(c2 == c4);
}
}
Singleton 연습 : 김밥 인스턴스를 한 개만 생성하도록 Singleton 패턴을 적용하라.
class Kimbap {
// 클래스 안의 코드를 완성하시오.
}
public class Test03 {
public static void main(String[] args) {
// 다음 코드는 컴파일 오류를 발생시켜야 한다.
//Kimbap bap1 = new Kimbap();
Kimbap bap2 = Kimbap.getInstance();
Kimbap bap3 = Kimbap.getInstance();
System.out.println(bap2 == bap3); // true
}
}
Composite
com.eomcs.design_pattern.composite
package com.eomcs.design_pattern.composite;
public class Test01 {
public static void main(String[] args) {
// Composite 디자인 패턴:
// => tree 구조로 전체-부분 관계의 객체를 표현할 때 사용한다.
// => 보통 조직도를 표현할 때 적합한 설계 기법이다.
// => OS의 파일시스템도 이 설계 기법으로 구현할 수 있다.
//
// 다음은 OS의 파일과 디렉토리 관계를 Composite 패턴을 사용하여 tree 구조로 표현한 것이다.
//
Directory root = new Directory("/");
root.add(new File("a.gif", "image/gif", 3456));
root.add(new File("b.gif", "image/gif", 34530));
root.add(new File("test.txt", "text/plain", 8700));
Directory src = new Directory("/src");
src.add(new File("Hello.java", "text/java", 320));
src.add(new File("Hello2.java", "text/java", 400));
root.add(src);
Directory bitcamp = new Directory("/bitcamp");
bitcamp.add(new File("A.java", "text/java", 200));
src.add(bitcamp);
display(root, "");
}
static void display(Directory dir, String path) {
System.out.println(path);
for (Node node : dir.childs) {
if (node instanceof File) {
System.out.printf("%s/%s\n", path, node.getTitle());
} else if (node instanceof Directory) {
display((Directory)node, path + node.getTitle());
}
}
}
}
package com.eomcs.design_pattern.composite;
public abstract class Node {
protected String title;
public String getTitle() {
return this.title;
}
public abstract void getFileInfo();
}
package com.eomcs.design_pattern.composite;
import java.util.ArrayList;
public class Directory extends Node {
ArrayList<Node> childs = new ArrayList<>();
public Directory(String title) {
this.title = title;
}
@Override
public void getFileInfo() {
System.out.printf("디렉토리명: %s\n", this.title);
}
public void add(Node node) {
childs.add(node);
}
public Node remove(int index) {
return childs.remove(index);
}
public Node get(int index) {
return childs.get(index);
}
}
package com.eomcs.design_pattern.composite;
public class File extends Node {
String type;
int length;
public File(String title, String type, int length) {
this.title = title;
this.type = type;
this.length = length;
}
@Override
public void getFileInfo() {
System.out.printf("파일명: %s\n", this.title);
System.out.printf("타입: %s\n", this.type);
System.out.printf("크기: %d\n", this.length);
}
}
Decorator
com.eomcs.design_pattern.decorator
ex01
before
package com.eomcs.design_pattern.decorator.ex01.before;
public class A {
public void f1(String name) {
System.out.println("이름: " + name);
}
}
package com.eomcs.design_pattern.decorator.ex01.before;
public class B extends A {
// 기존 기능에 새 기능을 덧붙이기 위하여 오버라이딩을 이용한다.
@Override
public void f1(String name) {
// 새 기능 추가
// => 머리말을 출력하는 기능을 덧붙인다.
System.out.println("[머리말]---------------");
super.f1(name); // 기존 기능은 그대로 수행
}
}
package com.eomcs.design_pattern.decorator.ex01.before;
public class C extends B {
@Override
public void f1(String name) {
super.f1(name); // 기존 기능은 그대로 실행
// 새 기능을 덧붙인다.
// => 꼬리말을 출력한다.
System.out.println("********************[끝]");
}
}
package com.eomcs.design_pattern.decorator.ex01.before;
public class Test01 {
public static void main(String[] args) {
A a = new A();
a.f1("홍길동");
}
}
package com.eomcs.design_pattern.decorator.ex01.before;
public class Test02 {
public static void main(String[] args) {
B b = new B();
b.f1("홍길동");
}
}
package com.eomcs.design_pattern.decorator.ex01.before;
public class Test03 {
public static void main(String[] args) {
C c = new C();
c.f1("홍길동");
}
}
after
package com.eomcs.design_pattern.decorator.ex01.after;
public abstract class Printer {
public abstract void f1(String name);
}
package com.eomcs.design_pattern.decorator.ex01.after;
public abstract class PlugIn extends Printer {
Printer origin;
public PlugIn(Printer origin) {
this.origin = origin;
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class A extends Printer {
@Override
public void f1(String name) {
System.out.println("이름: " + name);
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class B extends PlugIn {
// 기존 기능을 수행하는 객체를 보관해야 한다.
public B(Printer origin) {
super(origin);
}
// 기존 기능에 새 기능을 덧붙이기 위하여 오버라이딩을 이용한다.
@Override
public void f1(String name) {
// 새 기능 추가
// => 머리말을 출력하는 기능을 덧붙인다.
System.out.println("[머리말]---------------");
// 기존 기능을 수행하기 위해서는 상속 받은 메서드를 호출해서는 안된다.
// 생성자에서 파라미터로 받은 객체를 실행해야 한다.
// 왜? 바로 그 객체에 기능을 덧붙이는 것이기 때문이다.
origin.f1(name);
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class C extends PlugIn {
// C 클래스가 기능을 덧붙여야 하는 대상이 되는 객체를 생성자로 받는다.
public C(Printer origin) {
super(origin);
}
@Override
public void f1(String name) {
// 생성자에서 받은 객체에 대해 먼저 기능을 실행한다.
origin.f1(name);
// 새 기능을 덧붙인다.
// => 꼬리말을 출력한다.
System.out.println("********************[끝]");
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class Test01 {
public static void main(String[] args) {
A a = new A();
a.f1("홍길동");
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class Test02 {
public static void main(String[] args) {
A a = new A();
// A 객체의 기능에 머리말을 덧붙이기
B b = new B(a);
b.f1("홍길동");
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class Test03 {
public static void main(String[] args) {
// 1) 실제 작업을 할 객체를 준비한다.
A a = new A();
// 2) A 객체에 기능을 덧붙인다.
B b = new B(a);
// 3) B 객체에 기능을 덧붙인다.
C c = new C(b);
c.f1("홍길동");
}
}
package com.eomcs.design_pattern.decorator.ex01.after;
public class Test04 {
public static void main(String[] args) {
// 1) 실제 작업을 할 객체를 준비한다.
A a = new A();
// 2) 머리말 기능을 빼고 싶으면 언제든 뺄 수 있다.
//B b = new B(a);
// 3) A에 객체에 C 객체의 기능을 덧붙인다.
C c = new C(a);
c.f1("홍길동");
}
}
ex02
before
package com.eomcs.design_pattern.decorator.ex02.before;
public class Test01 {
public static void main(String[] args) {
// 전기차 트럭을 만들어 보자!
// => Hibrid를 상속 받고 트럭 기능을 덧붙인다.
// => 트럭에는 Sedan 기능이 필요 없지만,
// 상속은 중간의 특정 기능을 뺄 수 없다.
// 무조건 상속 받을 수 밖에 없다.
HybridTruck c1 = new HybridTruck();
c1.dump();
System.out.println("-------------------");
// 만약 Hybrid 이면서 Convertiable 기능을 갖는 자동차를 만들고 싶다면?
// => Hybrid나 Convertible 둘 중 한 개를 상속 받아서 구현해야 한다.
// => 하위 클래스는 다른 클래스의 기능을 중복해서 개발해야 한다.
//
HybridConvertible c2 = new HybridConvertible();
c2.open(true);
c2.run2();
// Convertible 자동차에 간단히 물건을 내리는 덤프 기능을 포함한다면?
// 위의 경우와 마찬가지로 Truck 클래스나 Convertible 클래스 둘 중 하나를 상속 받아야 하고,
// 둘 중 하나의 기능을 중복 작성해야 한다.
// => 이것이 상속의 한계다.
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public abstract class Car {
protected int speed;
public void start() {
System.out.println("시동 건다!");
}
public void stop() {
System.out.println("시동 끈다!");
}
public abstract void run();
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class Sedan extends Car {
@Override
public void run() {
System.out.println("달린다!");
}
public void openSunRoof() {
System.out.println("썬루프를 연다.");
}
public void closeSunRoof() {
System.out.println("썬루프를 닫는다.");
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class Convertible extends Sedan {
boolean openRoof;
public void open(boolean open) {
this.openRoof = open;
}
public void run2() {
if (openRoof) {
System.out.println("뚜껑 연다.");
} else {
System.out.println("뚜껑 닫는다.");
}
this.run();
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class Truck extends Car {
@Override
public void run() {
System.out.println("덜컬덜컹 달린다!");
}
public void dump() {
System.out.println("짐을 내린다.");
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class Hybrid extends Car {
@Override
public void run() {
if (this.speed <= 60) {
System.out.println("모터로 달린다.");
} else {
System.out.println("달린다!");
}
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class HybridTruck extends Hybrid {
public void dump() {
this.stop();
System.out.println("짐을 내린다.");
this.run();
}
}
package com.eomcs.design_pattern.decorator.ex02.before;
public class HybridConvertible extends Hybrid {
// Hybrid를 상속 받았기 때문에
// Hybrid 기능은 그래도 사용한다.
// 문제는 Convertible 기능을 갖기 위해
// Convertible 클래스에 있는 코드를 그대로 중복해서 작성해야 한다.
// => 코드 중복은 항상 문제가 된다.
//
boolean openRoof;
public void open(boolean open) {
this.openRoof = open;
}
public void run2() {
if (openRoof) {
System.out.println("뚜껑 연다.");
} else {
System.out.println("뚜껑 닫는다.");
}
this.run();
}
}
after
package com.eomcs.design_pattern.decorator.ex02.after;
public abstract class Car {
protected int speed;
protected int capacity;
public void start() {
System.out.println("시동 건다!");
}
public void stop() {
System.out.println("시동 끈다!");
}
public abstract void run();
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Sedan extends Car {
@Override
public void run() {
System.out.println("달린다!");
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Truck extends Car {
int weight;
@Override
public void run() {
System.out.println("덜컬덜컹 달린다!");
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
// 자동차에 추가 기능을 덧붙이는 역할을 한다.
// 다른 부속품의 수퍼 클래스 역할을 한다.
// => 그래서 추상 클래스로 정의한다.
public abstract class Decorator extends Car {
Car car;
// 비록 Car를 상속 받았지만 스스로 Car 일을 하지는 않는다.
// 단지 다른 자동차에 기능을 덧붙이다.
// => 생성자에서 반드시 기능을 덧불일 대상이 되는 자동차를 받아야 한다.
public Decorator(Car car) {
this.car = car;
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Convertible extends Decorator {
boolean openRoof;
public Convertible(Car car) {
super(car);
}
@Override
public void run() {
// 생성자에서 받은 원래의 자동차를 실행한다.
this.car.run();
// 생성자에서 받은 자동차에 덧붙인 자동차 지붕 열기 기능을 실행한다.
if (this.openRoof) {
System.out.println("지붕을 연다!");
} else {
System.out.println("지붕을 닫는다.");
}
}
public void openRoof(boolean openRoof) {
this.openRoof = openRoof;
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Dump extends Decorator {
public Dump(Car car) {
super(car);
}
@Override
public void run() {
// 생성자에서 받은 자동차에 덧붙인 짐내리기 기능을 실행한다.
this.car.stop();
this.dump();
// 생성자에서 받은 원래의 자동차를 실행한다.
this.car.run();
}
public void dump() {
System.out.println("짐을 내린다.");
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Hybrid extends Decorator {
public Hybrid(Car car) {
super(car);
}
@Override
public void run() {
// 생성자에서 받은 자동차에 덧붙인 전기 모터 기능을 실행한다.
System.out.print("전기 모터를 켜고, ");
// 생성자에서 받은 원래의 자동차를 실행한다.
this.car.run();
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class SunRoof extends Decorator {
boolean openSunRoof;
public SunRoof(Car car) {
super(car);
}
@Override
public void run() {
// 생성자에서 받은 자동차에 덧붙인 썬루프 기능을 실행한다.
if (openSunRoof) {
System.out.print("썬루프를 연채로 ");
} else {
System.out.print("썬루프를 닫은채로 ");
}
// 생성자에서 받은 원래의 자동차를 실행한다.
this.car.run();
}
public void openSunRoof() {
this.openSunRoof = true;
}
public void closeSunRoof() {
this.openSunRoof = false;
}
}
package com.eomcs.design_pattern.decorator.ex02.after;
public class Test01 {
public static void main(String[] args) {
// Decorator 패턴이 적용된 자동을 만들어 써보자!
//
// Decorator 디자인 패턴의 목표:
// => 기능을 플러그인처럼 붙였다 뗐다를 자유롭게 하기 위함이다.
//
// 전기차 트럭을 만들어 보자!
Truck c1 = new Truck();
Hybrid c2 = new Hybrid(c1);
c2.run();
System.out.println("-------------------");
// 만약 Hybrid 이면서 Convertiable 기능을 갖는 자동차를 만들고 싶다면?
Convertible c3 = new Convertible(c2);
c3.openRoof(true);
c3.run();
System.out.println("-------------------");
// Hybrid 기능 빼고 Sedan에 Convertible 기능을 달자.
Sedan s1 = new Sedan();
Convertible c4 = new Convertible(s1);
c4.openRoof(true);
c4.run();
System.out.println("-------------------");
Dump c5 = new Dump(c4);
c5.run();
System.out.println("-------------------");
Dump c6 = new Dump(c3);
c6.run();
}
}
Flyweight
com.eomcs.design_pattern.flyweight
before
package com.eomcs.design_pattern.flyweight.before;
import java.util.Scanner;
public class BrushTest {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
while (true) {
System.out.print("패턴? ");
String pattern = keyboard.nextLine();
System.out.print("선 길이? ");
int length = Integer.parseInt(keyboard.nextLine());
if (length < 0)
break;
// 사용자가 입력한 패턴의 브러시 객체를 생성한다.
// - 매번 생성하고 쓰고 버린다.
// - 가비지가 계속 생성되는 문제가 있다.
// - 해결책? 생성한 객체를 보관하여 재 사용하면 된다.
Brush brush = new Brush(pattern);
brush.draw(length);
}
keyboard.close();
}
}
package com.eomcs.design_pattern.flyweight.before;
public class Brush {
String pattern;
public Brush(String pattern) {
this.pattern = pattern;
}
public void draw(int length) {
for (int i = 0; i < length; i++) {
System.out.print(pattern);
}
System.out.println();
}
}
after
package com.eomcs.design_pattern.flyweight.after;
import java.util.Scanner;
public class BrushTest {
public static void main(String[] args) {
BrushPool brushPool = new BrushPool();
Scanner keyboard = new Scanner(System.in);
while (true) {
System.out.print("패턴? ");
String pattern = keyboard.nextLine();
System.out.print("선 길이? ");
int length = Integer.parseInt(keyboard.nextLine());
if (length < 0)
break;
// 사용자가 입력한 패턴의 브러시를 브러시풀에서 꺼내 쓴다.
// - 매번 생성하지 않기 때문에 가비지 생성이 줄어든다.
// - 이것이 "Flyweight" 패턴을 사용하는 이유이다.
//
// 이렇게 객체를 생성한 후 보관해 두었다가 재사용하는 방식을
// "Pooling 기법" 이라고 한다.
// - 디자인 패턴에서는 "Flyweight 패턴"으로 정리되어 있다.
//
Brush brush = brushPool.getBrush(pattern);
brush.draw(length);
}
keyboard.close();
}
}
package com.eomcs.design_pattern.flyweight.after;
import java.util.HashMap;
import java.util.Map;
// 생성한 객체를 보관해 두었다가
// 필요할 때마다 꺼내주는 일을 한다.
// 이렇게 생성된 객체를 재사용할 수 있도록
// 보관해 두었다가 꺼내주는 방식의 설계 기법을 "Flyweight 디자인 패턴" 이라 한다.
// => 메모리를 효율적으로 사용하는 방법이다.
//
public class BrushPool {
Map<String,Brush> brushMap = new HashMap<>();
// 브러시를 리턴하는 메서드
public Brush getBrush(String pattern) {
Brush brush = brushMap.get(pattern);
if (brush == null) {
System.out.printf("%s 브러시 생성\n", pattern);
brush = new Brush(pattern);
brushMap.put(pattern, brush);
}
return brush;
}
}
Proxy
com.eomcs.design_pattern
1단계 - 데스크톱 용 계산기 애플리케이션 만들기
package com.eomcs.design_pattern.proxy.before1;
public class App {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.plus(100, 200));
System.out.println(calc.minus(100, 200));
}
}
package com.eomcs.design_pattern.proxy.before1;
public class Calculator {
public int plus(int a, int b) {
return a + b;
}
public int minus(int a, int b) {
return a - b;
}
}
2단계 - C/S(Client/Server) 계산기 애플리케이션 만들기
client
package com.eomcs.design_pattern.proxy.before2.client;
import java.util.Scanner;
public class App {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
// 클라이언트 개발자가 원격에 있는 객체를 사용하기 위해서
// 원격 서버와 통신하는 코드를 프로토콜(데이터를 주고 받는 규칙)에 맞춰 직접 작성하였다.
//
CalculatorClient calcStub = new CalculatorClient();
while (true) {
System.out.print("계산식>(예: 100 + 200) ");
String input = keyboard.nextLine();
if (input.equalsIgnoreCase("quit"))
break;
String[] values = input.split(" ");
try {
System.out.println(calcStub.compute(
Integer.parseInt(values[0]),
Integer.parseInt(values[2]),
values[1]));
} catch (Exception e) {
System.out.println("식 또는 계산 오류: " + e.getMessage());
}
}
keyboard.close();
}
}
package com.eomcs.design_pattern.proxy.before2.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
// 클라이언트 요청을 서버에 전달하고 서버의 작업 결과를 클라이언트에게 리턴하는 일을 한다.
// => 즉 중계인 역할을 수행한다.
// => 이렇게 원격에서 객체를 사용할 수 있도록 중계인의 역할을 수행하는 객체를
// "Object Request Broker(ORB)"라 부른다.
// => 객체가 있는 서버 측에서 요청과 응답을 대행하는 ORB를 "스켈레톤(skeleton)"이라 부른다.
// => 객체를 사용하는 클라이언트 측에서 요청과 응답을 대행하는 ORB를 "스텁(stub)"이라 부른다.
//
public class CalculatorClient {
public int compute(int a, int b, String op) throws Exception {
try (Socket s = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(s.getOutputStream());
DataInputStream in = new DataInputStream(s.getInputStream())) {
out.writeInt(a);
out.writeInt(b);
out.writeUTF(op);
if (in.readUTF().equalsIgnoreCase("OK")) {
return in.readInt();
} else {
throw new RuntimeException(in.readUTF());
}
}
}
}
server
package com.eomcs.design_pattern.proxy.before2.server;
public class Calculator {
public int plus(int a, int b) {
return a + b;
}
public int minus(int a, int b) {
return a - b;
}
}
package com.eomcs.design_pattern.proxy.before2.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
// 클라이언트 요청을 실제 일을 하는 객체에게 전달하고,
// 객체가 작업한 결과를 클라이언트에게 보내주는 일을 한다.
// => 즉 중계인 역할을 수행한다.
// => 이렇게 원격에서 객체를 사용할 수 있도록 중계인의 역할을 수행하는 객체를
// "Object Request Broker(ORB)"라 부른다.
// => 객체가 있는 서버 측에서 요청과 응답을 대행하는 ORB를 "스켈레톤(skeleton)"이라 부른다.
// => 객체를 사용하는 클라이언트 측에서 요청과 응답을 대행하는 ORB를 "스텁(stub)"이라 부른다.
//
public class CalculatorServer {
public static void main(String[] args) {
// 실제 계산 작업을 수행할 객체
Calculator calc = new Calculator();
try (ServerSocket ss = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
while (true) {
try (Socket s = ss.accept();
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream())) {
int a = in.readInt();
int b = in.readInt();
String op = in.readUTF();
switch (op) {
case "+":
// 클라이언트 요청을 처리하기 위해 실제 작업을 수행하는 객체를 사용한다.
out.writeUTF("OK");
out.writeInt(calc.plus(a, b));
break;
case "-":
out.writeUTF("OK");
out.writeInt(calc.minus(a, b));
break;
default:
out.writeUTF("FAIL");
out.writeUTF("해당 연산자를 지원하지 않습니다.");
}
out.flush();
} catch (Exception e) {
System.out.println("클라이언트 요청 처리 중에 오류 발생!");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3단계 - 프록시 패턴을 적용하여 C/S(Client/Server) 계산기 애플리케이션 사용하기
client
package com.eomcs.design_pattern.proxy.after.client;
import java.util.Scanner;
import com.eomcs.design_pattern.proxy.after.server.Calculator;
import com.eomcs.design_pattern.proxy.after.server.CalculatorStub;
public class App {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
// 클라이언트 개발자가 원격에 있는 객체를 사용하기 위해서
// 원격 서버와 통신하는 코드를 프로토콜(데이터를 주고 받는 규칙)에 맞춰 직접 작성하였다.
//
Calculator calc = new CalculatorStub();
while (true) {
System.out.print("계산식>(예: 100 + 200) ");
String input = keyboard.nextLine();
if (input.equalsIgnoreCase("quit"))
break;
String[] values = input.split(" ");
try {
int a = Integer.parseInt(values[0]);
int b = Integer.parseInt(values[2]);
String op = values[1];
switch (op) {
// 이렇게 Calculator를 로컬에서 사용하는 것처럼
// CalculatorStub 이라는 프록시를 통해 작업을 수행할 수 있다.
// Calculator를 사용해야 하는 클라이언트 개발자는
// Calculator를 사용하기 위해 서버와 통신하는 코드를 작성할 필요가 없다.
// 서버 개발자가 프록시 객체를 만들어 제공해 줄 것이다.
//
case "+": System.out.println(calc.plus(a,b)); break;
case "-": System.out.println(calc.minus(a,b)); break;
default:
System.out.println("해당 연산자를 지원하지 않습니다.");
}
} catch (Exception e) {
System.out.println("식 또는 계산 오류: " + e.getMessage());
}
}
keyboard.close();
}
}
server
package com.eomcs.design_pattern.proxy.after.server;
// 실제 일을 하는 객체와 프록시 객체가 공통으로 따라야 하는 규칙을 정의한다.
public interface Calculator {
int plus(int a, int b);
int minus(int a, int b);
}
package com.eomcs.design_pattern.proxy.after.server;
// 실제 일을 하는 객체는 인터페이스의 규칙에 따라 동작하도록 구현되어야 한다.
public class CalculatorImpl implements Calculator {
public int plus(int a, int b) {
return a + b;
}
public int minus(int a, int b) {
return a - b;
}
}
package com.eomcs.design_pattern.proxy.after.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
// 클라이언트 요청을 서버에 전달하고 서버의 작업 결과를 클라이언트에게 리턴하는 일을 한다.
// => 즉 중계인 역할을 수행한다.
// => 이렇게 원격에서 객체를 사용할 수 있도록 중계인의 역할을 수행하는 객체를
// "Object Request Broker(ORB)"라 부른다.
// => 객체가 있는 서버 측에서 요청과 응답을 대행하는 ORB를 "스켈레톤(skeleton)"이라 부른다.
// => 객체를 사용하는 클라이언트 측에서 요청과 응답을 대행하는 ORB를 "스텁(stub)"이라 부른다.
//
// 스텁은 실제 일을 하는 객체를 대행하기 때문에 같은 규칙에 따라 구현되어야 한다.
// => 클라이언트는 이 스텁 클래스가 실제 일을 하는 객체인양 그대로 사용한다.
// => 이렇게 실제 일을 하는 객체와 같은 규칙을 따르지만 메서드가 호출될 때
// 자신이 직접 일을 하지 않고 , 실제 일을 하는 객체에게 위임한다.
//
// 이런 식으로 설계하는 것을 "프록시(proxy) 디자인 패턴"이라 한다.
//
public class CalculatorStub implements Calculator {
private int compute(int a, int b, String op) throws Exception {
try (Socket s = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(s.getOutputStream());
DataInputStream in = new DataInputStream(s.getInputStream())) {
out.writeInt(a);
out.writeInt(b);
out.writeUTF(op);
if (in.readUTF().equalsIgnoreCase("OK")) {
return in.readInt();
} else {
throw new RuntimeException(in.readUTF());
}
}
}
@Override
public int plus(int a, int b) {
try {
return compute(a, b, "+");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int minus(int a, int b) {
try {
return compute(a, b, "-");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package com.eomcs.design_pattern.proxy.after.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
// 클라이언트 요청을 실제 일을 하는 객체에게 전달하고,
// 객체가 작업한 결과를 클라이언트에게 보내주는 일을 한다.
// => 즉 중계인 역할을 수행한다.
// => 이렇게 원격에서 객체를 사용할 수 있도록 중계인의 역할을 수행하는 객체를
// "Object Request Broker(ORB)"라 부른다.
// => 객체가 있는 서버 측에서 요청과 응답을 대행하는 ORB를 "스켈레톤(skeleton)"이라 부른다.
// => 객체를 사용하는 클라이언트 측에서 요청과 응답을 대행하는 ORB를 "스텁(stub)"이라 부른다.
//
public class CalculatorSkel {
public static void main(String[] args) {
// 실제 계산 작업을 수행할 객체
Calculator calc = new CalculatorImpl();
try (ServerSocket ss = new ServerSocket(8888)) {
System.out.println("서버 실행 중...");
while (true) {
try (Socket s = ss.accept();
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream())) {
int a = in.readInt();
int b = in.readInt();
String op = in.readUTF();
switch (op) {
case "+":
// 클라이언트 요청을 처리하기 위해 실제 작업을 수행하는 객체를 사용한다.
out.writeUTF("OK");
out.writeInt(calc.plus(a, b));
break;
case "-":
out.writeUTF("OK");
out.writeInt(calc.minus(a, b));
break;
default:
out.writeUTF("FAIL");
out.writeUTF("해당 연산자를 지원하지 않습니다.");
}
out.flush();
} catch (Exception e) {
System.out.println("클라이언트 요청 처리 중에 오류 발생!");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4단계 - RMI 기술을 사용하여 C/S 계산기 애플리케이션 만들기
client
package com.eomcs.design_pattern.proxy.rmi.client;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Scanner;
import com.eomcs.design_pattern.proxy.rmi.server.Calculator;
public class App {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
// RMI 기술을 사용하면 개발자가 skeleton이나 stub 객체를 직접 작성할 필요가 없다.
// RMI Registry에 등록된 stub을 받아서 사용하면 된다.
//
// 1) RMI Registry 서버에서 스텁 객체를 받기 위해 사용할 도구를 준비한다.
//
// => 127.0.0.1 로 접속하여 찾을 수 있다.
// => localhost 로 접속하여 찾을 수 있다.
// => /etc/hosts 파일에 가상의 호스트 이름을 등록한 후에 접속하여 찾을 수 있다.
Registry registry = LocateRegistry.getRegistry("localhost");
// 2) 이 도구를 사용하여 RMI Registry 서버에 등록된 스텁을 등록된 이름으로 찾는다.
// => lookup() : RMI Registry 서버에서 해당 이름으로 등록된 스텁을 찾아서 리턴한다.
Calculator calc = (Calculator) registry.lookup("calc");
while (true) {
System.out.print("계산식>(예: 100 + 200) ");
String input = keyboard.nextLine();
if (input.equalsIgnoreCase("quit"))
break;
String[] values = input.split(" ");
try {
int a = Integer.parseInt(values[0]);
int b = Integer.parseInt(values[2]);
String op = values[1];
switch (op) {
// 이렇게 Calculator를 로컬에서 사용하는 것처럼
// CalculatorStub 이라는 프록시를 통해 작업을 수행할 수 있다.
// Calculator를 사용해야 하는 클라이언트 개발자는
// Calculator를 사용하기 위해 서버와 통신하는 코드를 작성할 필요가 없다.
// 서버 개발자가 프록시 객체를 만들어 제공해 줄 것이다.
//
case "+": System.out.println(calc.plus(a,b)); break;
case "-": System.out.println(calc.minus(a,b)); break;
default:
System.out.println("해당 연산자를 지원하지 않습니다.");
}
} catch (Exception e) {
System.out.println("식 또는 계산 오류: " + e.getMessage());
}
}
keyboard.close();
}
}
server
// RMI 기술 사용하기 - 원격 인터페이스 정의하기
package com.eomcs.design_pattern.proxy.rmi.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
// 원격 인터페이스(remote interface)
// => Remote 객체의 사용규칙을 정의한 인터페이스이다.
// => RMI 기술을 적용하려면 java.rmi.Remote 인터페이스를 상속 받아야 한다.
//
public interface Calculator extends Remote {
// 메서드는 java.rmi.RemoteException이 발생할 수 있다.
// 따라서 throws 절을 선언해야 한다.
int plus(int a, int b) throws RemoteException;
int minus(int a, int b) throws RemoteException;
}
// RMI 기술 사용하기 - 원격 인터페이스 구현하기 : 원격 객체 정의하기
package com.eomcs.design_pattern.proxy.rmi.server;
import java.rmi.RemoteException;
// 원격 객체(remote object)
// => remote interface 구현체이다.
// => 인터페이스 규칙에 따라 실제 일을 하는 객체이다.
// => 클라이언트는 이 객체의 stub을 이용하여 이 객체를 사용한다.
//
public class CalculatorImpl implements Calculator {
protected CalculatorImpl() throws RemoteException {
super();
}
public int plus(int a, int b) {
return a + b;
}
public int minus(int a, int b) {
return a - b;
}
}
// RMI 기술 사용하기 - 원격 객체를 RMI 레지스트리 서버에 등록하기
package com.eomcs.design_pattern.proxy.rmi.server;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RemoteObjectServer {
public static void main(String[] args) {
// [실행 방법]
// 1) 먼저 RMI Registry 서버를 실행하라!
// > rmiregistry
// => 주의! RMI Registry 서버가 클래스를 찾을 수 있도록 .class 파일이 있는 디렉토리에서 실행하라.
// java-basic/bin/main> rmiregistry
// 2) 그리고 이 클래스를 실행한다!
// => CalculatorImpl 객체를 원격에서 사용할 수 있도록 stub 객체를
// RMI Registry 서버에 등록한다.
// 3) client 패키지에 있는 App 클래스를 실행한다.
// => RMI Registry 서버에서 CalculatorImpl의 stub을 받아서
// CalculatorImpl의 기능을 사용한다.
//
// [원격 객체를 사용할 수 있도록 RMI Registry 서버에 등록하는 방법]
// 1) 보안 관리자 등록
// => 원격에서 접속할 때 사용할 수 있는 자원의 범위를 통제하는 객체
//
// if (System.getSecurityManager() == null) {
// System.setSecurityManager(new SecurityManager());
// }
try {
// 2) 원격 객체(실제 일을 하는 객체) 생성
//
Calculator calcObj = new CalculatorImpl();
// 3) 원격 객체와 통신을 담당할 프록시(클라이언트측 ORB)를 생성한다.
// => 첫 번째 파라미터 : 원격 객체
// => 두 번째 파라미터 : 포트 번호. 0으로 지정하면 임의의 유효한 포트 번호가 지정된다.
// => 이렇게 RMI 기술을 사용하면 개발자가 skeleton(서버 측 ORB) 이나
// stub(클라이언트 측 ORB)을 작성할 필요가 없다.
//
Calculator stub = (Calculator) UnicastRemoteObject.exportObject(calcObj, 0);
// 4) RMI Registry 서버에 스텁 객체를 등록해줄 도구를 준비한다.
//
Registry registry = LocateRegistry.getRegistry("localhost");
// 5) Registry 도구를 사용하여 RMI Registry 서버에 stub을 등록한다.
// => rebind() : 해당 이름으로 스텁 객체를 등록한다.
// 만약 그 이름으로 등록된 스텁이 있다면 기존에 등록된 스텁 객체를 대체한다.
// => bind() : 해당 이름으로 스텁 객체를 등록한다.
// 만약 그 이름으로 등록된 스텁이 있다면 예외가 발생한다.
//
registry.rebind("calc", stub);
System.out.println("Calculator 객체의 프록시(stub)가 RMI Registry 서버에 등록되었다.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Chain of Responsibility
com.eomcs.design_pattern.chain_of_responsibility
package com.eomcs.design_pattern.chain_of_responsibility;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
TerminalHandler terminalHandler = new TerminalHandler();
PlusHandler plusHandler = new PlusHandler();
plusHandler.setNext(terminalHandler);
// - 연산을 처리하고 싶다면 그 작업을 수행하는 객체를 만들어 체인에 연결하라!
MinusHandler minusHandler = new MinusHandler();
minusHandler.setNext(plusHandler);
// * 연산을 처리하고 싶다면 그 작업을 수행하는 객체를 만들어 체인에 연결하라!
MultipleHandler multipleHandler = new MultipleHandler();
multipleHandler.setNext(minusHandler);
// 이렇게 기능을 체인으로 엮어 가는 설계 기법을 "Chain of Responsibility" 라 부른다.
while (true) {
System.out.print("a? ");
int a = keyScan.nextInt();
System.out.print("b? ");
int b = keyScan.nextInt();
System.out.print("연산자? ");
String op = keyScan.next();
multipleHandler.handle(a, b, op);
}
}
}
package com.eomcs.design_pattern.chain_of_responsibility;
public interface Handler {
void handle(int a, int b, String op);
}
package com.eomcs.design_pattern.chain_of_responsibility;
public abstract class AbstractHandler implements Handler {
Handler next;
public AbstractHandler() {
}
public AbstractHandler(Handler next) {
this.next = next;
}
public Handler getNext() {
return next;
}
public void setNext(Handler next) {
this.next = next;
}
}
package com.eomcs.design_pattern.chain_of_responsibility;
public class PlusHandler extends AbstractHandler {
@Override
public void handle(int a, int b, String op) {
if (op.equals("+")) {
System.out.printf("%d + %d = %d\n", a, b, (a + b));
return;
}
next.handle(a, b, op);
}
}
package com.eomcs.design_pattern.chain_of_responsibility;
public class MinusHandler extends AbstractHandler {
@Override
public void handle(int a, int b, String op) {
if (op.equals("-")) {
System.out.printf("%d - %d = %d\n", a, b, (a - b));
return;
}
next.handle(a, b, op);
}
}
package com.eomcs.design_pattern.chain_of_responsibility;
public class MultipleHandler extends AbstractHandler {
@Override
public void handle(int a, int b, String op) {
if (op.equals("*")) {
System.out.printf("%d * %d = %d\n", a, b, (a * b));
return;
}
next.handle(a, b, op);
}
}
package com.eomcs.design_pattern.chain_of_responsibility;
public class TerminalHandler extends AbstractHandler {
@Override
public void handle(int a, int b, String op) {
// 이 핸들러는 연결의 끝을 표현하기 위해 만든 것이다.
System.out.println("해당 연산자를 지원하지 않습니다.");
}
}
Command
com.eomcs.design_pattern.command
before
package com.eomcs.design_pattern.command.before;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
CommandHandler handler = new CommandHandler();
while (true) {
System.out.print("명령> ");
String input = keyboard.nextLine();
if ("/board/add".equals(input)) {
handler.addBoard();
} else if ("/board/detail".equals(input)) {
handler.detailBoard();
} else if ("/board/list".equals(input)) {
handler.listBoard();
} else if ("/board/update".equals(input)) {
handler.updateBoard();
} else if ("/board/delete".equals(input)) {
handler.deleteBoard();
} else if ("/member/add".equals(input)) {
handler.addMember();
} else if ("/member/detail".equals(input)) {
handler.detailMember();
} else if ("/member/list".equals(input)) {
handler.listMember();
} else if ("/member/update".equals(input)) {
handler.updateMember();
} else if ("/member/delete".equals(input)) {
handler.deleteMember();
} else if ("quit".equals(input)) {
break;
} else {
System.out.println("처리할 수 없는 명령입니다.");
}
System.out.println();
}
keyboard.close();
}
}
package com.eomcs.design_pattern.command.before;
public class CommandHandler {
public void addBoard() {
System.out.println("게시물 입력 처리!");
}
public void detailBoard() {
System.out.println("게시물 상세조회 처리!");
}
public void listBoard() {
System.out.println("게시물 목록조회 처리!");
}
public void updateBoard() {
System.out.println("게시물 변경 처리!");
}
public void deleteBoard() {
System.out.println("게시물 삭제 처리!");
}
public void addMember() {
System.out.println("회원 입력 처리!");
}
public void detailMember() {
System.out.println("회원 상세조회 처리!");
}
public void listMember() {
System.out.println("회원 목록조회 처리!");
}
public void updateMember() {
System.out.println("회원 변경 처리!");
}
public void deleteMember() {
System.out.println("회원 삭제 처리!");
}
}
after
package com.eomcs.design_pattern.command.after;
import java.util.HashMap;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
// Command 패턴
// => 명령어를 처리하는 각 메서드를 클래스로 정의한 후 사용한다.
// => 일관된 사용을 위해 인터페이스로 호출 규칙을 정의한다.
// => 나중에 명령어가 추가되면 그 명령어를 처리할 클래스를 추가하면 된다.
//
HashMap<String,Command> commandMap = new HashMap<>();
commandMap.put("/board/add", new BoardAddCommand());
commandMap.put("/board/detail", new BoardDetailCommand());
commandMap.put("/board/list", new BoardListCommand());
commandMap.put("/board/update", new BoardUpdateCommand());
commandMap.put("/board/delete", new BoardDeleteCommand());
commandMap.put("/member/add", new MemberAddCommand());
commandMap.put("/member/detail", new MemberDetailCommand());
commandMap.put("/member/list", new MemberListCommand());
commandMap.put("/member/update", new MemberUpdateCommand());
commandMap.put("/member/delete", new MemberDeleteCommand());
commandMap.put("hello", new HelloCommand());
while (true) {
System.out.print("명령> ");
String input = keyboard.nextLine();
// 사용자가 입력한 명령어를 가지고 그 명령을 처리할 객체를 찾는다.
Command command = commandMap.get(input);
if (command != null) {
// 명령어 사용 규칙에 따라 메서드를 호출한다.
command.execute();
} else if ("quit".equals(input)) {
break;
} else {
System.out.println("처리할 수 없는 명령입니다.");
}
System.out.println();
}
keyboard.close();
}
}
package com.eomcs.design_pattern.command.after;
// 명령어를 처리하는 객체의 사용규칙을 정의한다.
public interface Command {
void execute();
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class BoardAddCommand implements Command {
@Override
public void execute() {
System.out.println("게시물 입력 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class BoardDeleteCommand implements Command {
@Override
public void execute() {
System.out.println("게시물 삭제 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class BoardDetailCommand implements Command {
@Override
public void execute() {
System.out.println("게시물 상세조회 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class BoardListCommand implements Command {
@Override
public void execute() {
System.out.println("게시물 목록조회 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class BoardUpdateCommand implements Command {
@Override
public void execute() {
System.out.println("게시물 변경 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class MemberAddCommand implements Command {
@Override
public void execute() {
System.out.println("회원 입력 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class MemberDeleteCommand implements Command {
@Override
public void execute() {
System.out.println("회원 삭제 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class MemberDetailCommand implements Command {
@Override
public void execute() {
System.out.println("회원 상세조회 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class MemberListCommand implements Command {
@Override
public void execute() {
System.out.println("회원 목록조회 처리!");
}
}
package com.eomcs.design_pattern.command.after;
// Command 규칙에 따라 동작하는 클래스를 정의한다.
// 각 명령어를 처리하는 메서드를 클래스로 분리한다.
public class MemberUpdateCommand implements Command {
@Override
public void execute() {
System.out.println("회원 변경 처리!");
}
}
package com.eomcs.design_pattern.command.after;
public class HelloCommand implements Command {
@Override
public void execute() {
System.out.println("안녕하세요!");
}
}
Iterator
com.eomcs.design_pattern.iterator
before
package com.eomcs.design_pattern.iterator.before;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
for (int i = 0; i < list2.size(); i++) {
System.out.println(list2.get(i));
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
while (!list3.empty()) {
System.out.println(list3.pop());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
while (!list4.empty()) {
System.out.println(list4.poll());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
String[] arr = list5.toArray(new String[0]);
for (String s : arr) {
System.out.println(s);
}
System.out.println("-------------------------");
// 문제점
// - 자료 구조에 따라 데이터를 꺼내는 방식이 다르다!
// - 데이터 조회에 일관성이 없다.
//
// 해결책
// - 데이터 조회하는 일을 별도의 객체에 맡기자!
// - 단 데이터 조회 방식을 통일하기 위해 인터페이스로 조회 방식을 규격화 한다.
}
}
package com.eomcs.design_pattern.iterator.before;
public class ArrayList<E> {
static final int DEFAULT_SIZE = 5;
Object[] arr;
int size;
public ArrayList() {
this(0);
}
public ArrayList(int capacity) {
if (capacity > DEFAULT_SIZE)
arr = new Object[capacity];
else
arr = new Object[DEFAULT_SIZE];
}
public Object[] toArray() {
Object[] list = new Object[this.size];
for (int i = 0; i < this.size; i++) {
list[i] = this.arr[i];
}
return list;
}
public void add(E value) {
if (this.size == arr.length)
increase();
arr[this.size++] = value;
}
public int insert(int index, E value) {
if (index < 0 || index >= size)
return -1;
if (this.size == arr.length)
increase();
for (int i = size - 1; i >= index; i--)
this.arr[i + 1] = this.arr[i];
this.arr[index] = value;
size++;
return 0;
}
@SuppressWarnings("unchecked")
public E get(int index) {
if (index < 0 || index >= size)
return null;
return (E) this.arr[index];
}
@SuppressWarnings("unchecked")
public E set(int index, E value) {
if (index < 0 || index >= size)
return null;
E old = (E) this.arr[index];
this.arr[index] = value;
return old;
}
@SuppressWarnings("unchecked")
public E remove(int index) {
if (index < 0 || index >= size)
return null;
E old = (E) this.arr[index];
for (int i = index; i < size - 1; i++)
this.arr[i] = this.arr[i+1];
size--;
return old;
}
public int size() {
return this.size;
}
private void increase() {
int originSize = arr.length;
int newSize = originSize + (originSize >> 1);
Object[] temp = new Object[newSize];
for (int i = 0; i < this.arr.length; i++) {
temp[i] = this.arr[i];
}
arr = temp;
}
}
package com.eomcs.design_pattern.iterator.before;
public class LinkedList<E> {
protected Node<E> head;
protected Node<E> tail;
protected int size;
public LinkedList() {
head = new Node<>();
tail = head;
size = 0;
}
public void add(E value) {
tail.value = value;
// 새 노드를 준비한다.
Node<E> node = new Node<>();
// 마지막 노드의 다음으로 새 노드를 가리키게 한다.
tail.next = node;
// 새 노드의 이전으로 마지막 노드를 가리키게 한다.
node.prev = tail;
// tail이 새로 추가된 노드를 가리키게 한다.
tail = node;
// 항목 개수를 증가시킨다.
size++;
}
public int size() {
return size;
}
public E get(int index) {
if (index < 0 || index >= size)
return null;
Node<E> cursor = head;
// 해당 인덱스로 이동한다.
for (int i = 1; i <= index; i++) {
cursor = cursor.next;
}
// cursor가 가리키는 노드의 주소를 리턴?
// => 노드의 값을 리턴
return cursor.value;
}
public Object[] toArray() {
Object[] arr = new Object[size()];
Node<E> cursor = head;
int i = 0;
while (cursor != tail) {
arr[i++] = cursor.value;
cursor = cursor.next;
}
return arr;
}
public E set(int index, E value) {
if (index < 0 || index >= size)
return null;
Node<E> cursor = head;
// 교체할 값이 들어 있는 노드로 이동한다.
for (int i = 1; i <= index; i++) {
cursor = cursor.next;
}
// 변경 전에 이전 값을 보관한다.
E old = cursor.value;
// 값을 변경한다.
cursor.value = value;
// 이전 값을 리턴한다. 쓰든 안쓰든 호출하는 사람이 알아서 할 일이다.
// 다만 변경 전 값을 활용할 경우를 대비해 리턴하는 것이다.
return old;
}
public int insert(int index, E value) {
if (index < 0 || index >= size)
return -1;
// 새 노드를 만들어 값을 담는다.
Node<E> node = new Node<>(value);
// 삽입할 위치에 있는 원래 노드를 찾는다.
Node<E> cursor = head;
for (int i = 1; i <= index; i++) {
cursor = cursor.next;
}
// 새 노드가 찾은 노드를 가리키게 한다.
node.next = cursor;
// 찾은 노드의 이전 노드 주소를 새 노드의 이전 노드 주소로 설정한다.
node.prev = cursor.prev;
// 찾은 노드의 이전 노드로 새 노드를 가리키게 한다.
cursor.prev = node;
if (node.prev != null) {
// 이전 노드의 다음 노드로 새 노드를 가리키게 한다.
node.prev.next = node;
} else {
// 맨 앞에 노드를 추가할 때는 head를 변경해야 한다.
head = node;
}
// 크기를 늘린다.
size++;
return 0;
}
public E remove(int index) {
if (index < 0 || index >= size)
return null;
// index 위치에 있는 노드를 찾는다.
Node<E> cursor = head;
for (int i = 1; i <= index; i++) {
cursor = cursor.next;
}
if (cursor.prev != null) {
// 찾은 노드의 이전 노드가 다음 노드를 가리키게 한다.
cursor.prev.next = cursor.next;
} else {
// 맨 처음 노드를 삭제할 때
head = cursor.next;
}
// 찾은 노드의 다음 노드가 이전 노드를 가리키게 한다.
cursor.next.prev = cursor.prev;
// JVM(Garbage Collection)이 가비지를 효과적으로 계산할 수 있도록
// 가비지가 된 객체는 다른 객체를 가리키지 않도록 한다.
E old = cursor.value;
cursor.value = null;
cursor.prev = null;
cursor.next = null;
// 크기를 줄인다.
size--;
// 호출한 쪽에서 필요하면 사용하라고 삭제된 값을 리턴해 준다.
return old;
}
private static class Node<E> {
E value;
Node<E> prev;
Node<E> next;
Node() {
}
Node(E value) {
this.value = value;
}
}
}
package com.eomcs.design_pattern.iterator.before;
public class Queue<E> extends LinkedList<E> {
public void offer(E value) {
this.add(value);
}
public E poll() {
return this.remove(0);
}
public boolean empty() {
return this.size == 0;
}
}
package com.eomcs.design_pattern.iterator.before;
public class Stack<E> {
public static final int DEFAULT_SIZE = 5;
Object[] list;
int size;
public Stack() {
list = new Object[DEFAULT_SIZE];
}
public void push(E value) {
if (size == list.length) {
Object[] arr = new Object[list.length + (list.length >> 1)];
for (int i = 0; i < list.length; i++) {
arr[i] = list[i];
}
list = arr;
}
list[size++] = value;
}
@SuppressWarnings("unchecked")
public E pop() {
if (size == 0)
return null;
return (E) list[--size];
}
public boolean empty() {
return size == 0;
}
public int size() {
return this.size;
}
}
after
// iterator 디자인 패턴 : 데이터 목록에서 값을 꺼내는 것을 별도의 객체로 분리하는 설계 방식
package com.eomcs.design_pattern.iterator.after;
// Iterator 디자인 패턴
// => 데이터 목록을 관리하는 방식에 상관없이 일관된 방식으로 데이터를 꺼낼 수 있게 해주는 설계 기법
// => 즉 데이터 목록을 관리하는 객체를 직접 사용하여 값을 꺼내는 것이 아니라,
// 값을 꺼내는 주는 별도의 객체의 도움을 받아 값을 꺼낸다.
// => 값을 꺼내주는 객체를 "Iterator"라 부른다.
// 값을 꺼내주는 객체의 사용법을 통일하기 위하여 인터페이스로 사용 규칙을 정의한다.
// => 각각의 데이터 목록 관리 객체는 Iterator 규칙에 따라 값을 꺼내는 객체를 리턴한다.
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
// ArrayList에서 제공하는 메서드를 사용하여 직접 꺼내지 말고
// 값을 꺼내주는 객체의 도움을 받으라!
// 이렇게 하는 이유?
// => 데이터 목록 객체의 종류(ArrayList, LinkedList, Stack, Queue)에 상관없이
// 일관된 방법으로 값을 꺼내기 위함이다.
Iterator<String> iterator = list1.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
System.out.println();
// LinkedList도 ArrayList에서 값을 꺼내는 방식과 동일하게 값을 꺼낼 수 있다.
// 이런 이유로 Iterator를 사용하는 것이다.
iterator = list2.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
System.out.println();
// Stack이 데이터를 어떻게 관리하는가에 상관없이,
// ArrayList에서 값을 꺼내는 방식과 동일하게 값을 꺼낼 수 있다.
// 이런 이유로 Iterator를 사용하는 것이다.
iterator = list3.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
System.out.println();
// Queue가 데이터를 어떻게 관리하는가에 상관없이,
// ArrayList에서 값을 꺼내는 방식과 동일하게 값을 꺼낼 수 있다.
// 이런 이유로 Iterator를 사용하는 것이다.
iterator = list4.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
System.out.println();
}
}
// 값을 꺼내주는 객체의 사용 규칙 정의 - 값을 일관성 있게 꺼내기 위해 사용 규칙을 정의한다.
package com.eomcs.design_pattern.iterator.after;
public interface Iterator<E> {
// 데이터 목록에서 꺼낼 값이 있다면 true, 없다면 false
boolean hasNext();
// 데이터 목록에서 값을 꺼낸다.
E next();
}
package com.eomcs.design_pattern.iterator.after;
public class ArrayList<E> {
/* 생략 */
// 자신이 보유한 데이터를 대신 꺼내주는 일을 하는 객체를 리턴한다.
public Iterator<E> iterator() {
return new Iterator<E>() {
// 이 클래스는 ArrayList에서 값을 꺼내주는 일을 전문적으로 한다.
// => 이런 일을 하는 객체를 "Iterator"라 부른다.
//
int index = 0;
@Override
public boolean hasNext() {
return index < size();
}
@Override
public E next() {
return (E) get(index++);
}
};
}
}
package com.eomcs.design_pattern.iterator.after;
public class LinkedList<E> {
/* 생략 */
// 자신이 보유한 데이터를 대신 꺼내주는 일을 하는 객체를 리턴한다.
public Iterator<E> iterator() {
return new Iterator<E>() {
// 이 클래스는 LinkedList에서 값을 꺼내주는 일을 전문적으로 한다.
// => 이런 일을 하는 객체를 "Iterator"라 부른다.
//
int index = 0;
@Override
public boolean hasNext() {
return index < size();
}
@Override
public E next() {
return (E) get(index++);
}
};
}
}
package com.eomcs.design_pattern.iterator.after;
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// 자신이 보유한 데이터를 대신 꺼내주는 일을 하는 객체를 리턴한다.
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
// 이 클래스는 Queue에서 값을 꺼내주는 일을 전문적으로 한다.
// => 이런 일을 하는 객체를 "Iterator"라 부른다.
//
int index = 0;
@Override
public boolean hasNext() {
return index < size();
}
@Override
public E next() {
return get(index++);
}
};
}
}
package com.eomcs.design_pattern.iterator.after;
public class Stack<E> {
/* 생략 */
// 자신이 보유한 데이터를 대신 꺼내주는 일을 하는 객체를 리턴한다.
public Iterator<E> iterator() {
return new Iterator<E>() {
// 이 클래스는 Stack의 값을 꺼내는 일을 전문적으로 한다.
// => 이런 일을 하는 객체를 "Iterator"라 부른다.
//
int index = 0;
@Override
public boolean hasNext() {
return index < size();
}
@SuppressWarnings("unchecked")
@Override
public E next() {
int lastIndex = size - 1;
return (E) list[lastIndex - (index++)];
}
};
}
}
after1
// Iterator 디자인 패턴 : 1) 패키지 멤버로 Iterator 클래스 정의하기
package com.eomcs.design_pattern.iterator.after1;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
// => ArrayList에게 값을 꺼내는 일을 할 객체를 달라고 요구한다.
Iterator<String> iterator1 = list1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
Iterator<String> iterator2 = list2.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
Iterator<String> iterator3 = list3.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
Iterator<String> iterator4 = list4.iterator();
while (iterator4.hasNext()) {
System.out.println(iterator4.next());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => java.util.HashSet 의 iterator()가 리턴하는 객체는
// 우리가 만든 Iterator 가 아니라,
// java.util.Iterator 구현체를 리턴한다.
// 비록 우리가 만든 Iterator가 아닐지라고 사용법(메서드명)은 같다.
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
java.util.Iterator<String> iterator5 = list5.iterator();
while (iterator5.hasNext()) {
System.out.println(iterator5.next());
}
System.out.println("-------------------------");
// Iterator 설계 패턴의 특징
// - 자료 구조에 상관없이 꺼내는 방식이 같다.
// - 프로그래밍의 일관성을 제공한다.
//
// 문제점
// - ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
// - 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
// - 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
//
// 해결책
// - 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
// - 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
}
}
// 데이터 조회를 수행하는 객체 사용법 정의
public interface Iterator<E> {
// 목록에 조회할 데이터가 있는지 검사할 때 호출
boolean hasNext();
// 목록에서 데이터를 한 개 꺼낼 때 호출
E next();
}
public class ArrayList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new ArrayListIterator<E>(this);
}
}
// ArrayList에서 데이터를 꺼내줄 객체
//
public class ArrayListIterator<E> implements Iterator<E> {
ArrayList<E> list;
int index = 0;
public ArrayListIterator(ArrayList<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size();
}
@Override
public E next() {
return list.get(index++);
}
}
public class LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new LinkedListIterator<E>(this);
}
}
// ListList에서 데이터를 꺼내줄 객체
//
public class LinkedListIterator<E> implements Iterator<E> {
LinkedList<E> list;
int index = 0;
public LinkedListIterator(LinkedList<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size();
}
@Override
public E next() {
return list.get(index++);
}
}
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
@Override
public Iterator<E> iterator() {
return new QueueIterator<E>(this);
}
}
// Queue에서 데이터를 꺼내줄 객체
//
public class QueueIterator<E> implements Iterator<E> {
Queue<E> list;
public QueueIterator(Queue<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return !list.empty();
}
@Override
public E next() {
return list.poll();
}
}
public class Stack<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new StackIterator<E>(this);
}
}
// Stack에서 데이터를 꺼내줄 객체
//
public class StackIterator<E> implements Iterator<E> {
Stack<E> list;
public StackIterator(Stack<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return !list.empty();
}
@Override
public E next() {
return list.pop();
}
}
after2
// Iterator 디자인 패턴 : 2) 클래스 멤버(스태틱 중첩 클래스)로 Iterator 클래스 정의하기
package com.eomcs.design_pattern.iterator.after2;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
// => ArrayList에게 값을 꺼내는 일을 할 객체를 달라고 요구한다.
Iterator<String> iterator1 = list1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
Iterator<String> iterator2 = list2.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
Iterator<String> iterator3 = list3.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
Iterator<String> iterator4 = list4.iterator();
while (iterator4.hasNext()) {
System.out.println(iterator4.next());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => java.util.HashSet 의 iterator()가 리턴하는 객체는
// 우리가 만든 Iterator 가 아니라,
// java.util.Iterator 구현체를 리턴한다.
// 비록 우리가 만든 Iterator가 아닐지라고 사용법(메서드명)은 같다.
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
java.util.Iterator<String> iterator5 = list5.iterator();
while (iterator5.hasNext()) {
System.out.println(iterator5.next());
}
System.out.println("-------------------------");
// Iterator 설계 패턴의 특징
// - 자료 구조에 상관없이 꺼내는 방식이 같다.
// - 프로그래밍의 일관성을 제공한다.
//
// 문제점
// - ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
// - 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
// - 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
//
// 해결책
// - 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
// - 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
}
}
public class ArrayList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new ArrayListIterator<E>(this);
}
// static nested class(스태틱 중첩 클래스)
static class ArrayListIterator<E> implements Iterator<E> {
ArrayList<E> list;
int index = 0;
public ArrayListIterator(ArrayList<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size();
}
@Override
public E next() {
return list.get(index++);
}
}
}
public class LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new LinkedListIterator<E>(this);
}
// 스태틱 중첩 클래스로 정의한다.
static class LinkedListIterator<E> implements Iterator<E> {
LinkedList<E> list;
int index = 0;
public LinkedListIterator(LinkedList<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size();
}
@Override
public E next() {
return list.get(index++);
}
}
}
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
@Override
public Iterator<E> iterator() {
return new QueueIterator<E>(this);
}
// 스태틱 중첩 클래스로 정의한다.
static class QueueIterator<E> implements Iterator<E> {
Queue<E> list;
public QueueIterator(Queue<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return !list.empty();
}
@Override
public E next() {
return list.poll();
}
}
}
public class Stack<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new StackIterator<E>(this);
}
// 스태틱 중첩 클래스로 정의한다.
static class StackIterator<E> implements Iterator<E> {
Stack<E> list;
public StackIterator(Stack<E> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return !list.empty();
}
@Override
public E next() {
return list.pop();
}
}
}
after3
// Iterator 디자인 패턴 : 3) 인스턴스 멤버(논스태틱 중첩 클래스=inner class)로 Iterator 클래스 정의하기
package com.eomcs.design_pattern.iterator.after3;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
// => ArrayList에게 값을 꺼내는 일을 할 객체를 달라고 요구한다.
Iterator<String> iterator1 = list1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
Iterator<String> iterator2 = list2.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
Iterator<String> iterator3 = list3.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
Iterator<String> iterator4 = list4.iterator();
while (iterator4.hasNext()) {
System.out.println(iterator4.next());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => java.util.HashSet 의 iterator()가 리턴하는 객체는
// 우리가 만든 Iterator 가 아니라,
// java.util.Iterator 구현체를 리턴한다.
// 비록 우리가 만든 Iterator가 아닐지라고 사용법(메서드명)은 같다.
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
java.util.Iterator<String> iterator5 = list5.iterator();
while (iterator5.hasNext()) {
System.out.println(iterator5.next());
}
System.out.println("-------------------------");
// Iterator 설계 패턴의 특징
// - 자료 구조에 상관없이 꺼내는 방식이 같다.
// - 프로그래밍의 일관성을 제공한다.
//
// 문제점
// - ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
// - 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
// - 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
//
// 해결책
// - 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
// - 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
}
}
public class ArrayList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
// 생성자를 호출할 때 바깥 클래스의 인스턴스 주소를 넘겨주지 않아도
// 컴파일러가 넘겨주는 코드로 자동 변환한다.
//
return new ArrayListIterator<E>();
// 위의 코드는 컴파일 될 때 다음과 같이 바뀐다.
// return new ArrayListIterator<E>(this);
}
// non-static nested class(스태틱 중첩 클래스) = inner class
// => 바깥 클래스의 인스턴스를 사용하는 경우 논클래스 중첩 클래스로 선언하라!
// => 왜? 바깥 클래스의 인스턴스 주소를 받은 필드와 생성자 파라미터가 자동으로 추가된다.
// => 즉 개발자가 바깥 클래스의 인스턴스 주소를 다룰 필요가 없다.
//
class ArrayListIterator<T> implements Iterator<T> {
// 논스태틱 중첩 클래스인 경우 바깥 클래스의 인스턴스 주소를 받을 필드가 자동 추가된다.
// 따라서 다음과 같이 개발가 직접 추가할 필요가 없다.
// ArrayList<E> this$0; // 자동 생성 된다.
int index = 0;
// 논스태틱 중첩 클래스인 경우 바깥 클래스의 인스턴스 주소를 받는 파라미터가
// 생성자가 자동으로 추가된다.
// 개발자가 직접 추가할 필요가 없다.
// public ArrayListIterator(ArrayList<E> this$0) {
// this.this$0 = this$0;
// }
@Override
public boolean hasNext() {
// 컴파일러가 자동으로 추가한,
// 바깥 클래스의 인스턴스 주소를 담고 있는 변수를 사용하려면 다음과 같이 지정하라!
// 바깥클래스명.this.멤버
//
return index < ArrayList.this.size();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
// 컴파일러가 추가한 바깥 클래스의 인스턴스 주소를 사용하고 싶다면
// ArrayList.this 라고 지정하면 된다.
// ArrayList.get() 메서드의 리턴 타입이 E 이다.
// E가 가리키는 타입 정보는 다음과 같이 결국 ArrayListIterator를 만들 때 넘겨진다.
// new ArrayListIterator<E>();
// 따라서 ArrayListIterator 내부에서 사용하는 T가 곧 ArrayList 의 E와 같다.
// 결론! 다음과 같이 형변환 할 수 있다.
return (T) ArrayList.this.get(index++);
}
}
}
public class LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new LinkedListIterator<E>();
}
// 논스태틱 중첩 클래스로 정의하여 바깥 클래스의 인스턴스 주소를 받는
// 필드와 생성자 파라미터를 자동으로 추가하게 한다.
//
class LinkedListIterator<T> implements Iterator<T> {
int index = 0;
@Override
public boolean hasNext() {
return index < LinkedList.this.size();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) LinkedList.this.get(index++);
}
}
}
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
@Override
public Iterator<E> iterator() {
return new QueueIterator<E>();
}
// 논스태틱 중첩 클래스로 정의하여 바깥 클래스이 인스턴스 주소를 받는 필드와
// 생성자 파라미터를 자동으로 추가하게 한다.
class QueueIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return !Queue.this.empty();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) Queue.this.poll();
}
}
}
public class Stack<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new StackIterator<E>();
}
// 논스태틱 중첩 클래스로 정의하면
// 바깥 클래스의 인스턴스 주소를 받는 필드가 자동으로 추가된다.
// 또한 바깥 클래스의 인스턴스 주소를 받는 파라미터가 가각의 생성자에 자동으로 추가된다.
//
class StackIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return !Stack.this.empty();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) Stack.this.pop();
}
}
}
after4
// Iterator 디자인 패턴 : 4) 로컬 클래스로 Iterator 클래스 정의하기
package com.eomcs.design_pattern.iterator.after4;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
// => ArrayList에게 값을 꺼내는 일을 할 객체를 달라고 요구한다.
Iterator<String> iterator1 = list1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
Iterator<String> iterator2 = list2.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
Iterator<String> iterator3 = list3.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
Iterator<String> iterator4 = list4.iterator();
while (iterator4.hasNext()) {
System.out.println(iterator4.next());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => java.util.HashSet 의 iterator()가 리턴하는 객체는
// 우리가 만든 Iterator 가 아니라,
// java.util.Iterator 구현체를 리턴한다.
// 비록 우리가 만든 Iterator가 아닐지라고 사용법(메서드명)은 같다.
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
java.util.Iterator<String> iterator5 = list5.iterator();
while (iterator5.hasNext()) {
System.out.println(iterator5.next());
}
System.out.println("-------------------------");
// Iterator 설계 패턴의 특징
// - 자료 구조에 상관없이 꺼내는 방식이 같다.
// - 프로그래밍의 일관성을 제공한다.
//
// 문제점
// - ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
// - 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
// - 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
//
// 해결책
// - 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
// - 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
}
}
public class ArrayList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
// local class(로컬 클래스)
// => 바깥 클래스의 인스턴스를 사용하면서 특정 메서드 안에서만 사용할 클래스라면
// 로컬 클래스로 정의하라!
// => 물론 논스태틱 중첩 클래스처럼 바깥 클래스의 인스턴스 주소를 다루는 필드와
// 생성자 파라미터가 자동으로 추가된다.
class ArrayListIterator<T> implements Iterator<T> {
int index = 0;
@Override
public boolean hasNext() {
return index < ArrayList.this.size();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) ArrayList.this.get(index++);
}
}
return new ArrayListIterator<E>();
}
}
public class LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
class LinkedListIterator<T> implements Iterator<T> {
int index = 0;
@Override
public boolean hasNext() {
return index < LinkedList.this.size();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) LinkedList.this.get(index++);
}
}
return new LinkedListIterator<E>();
}
}
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
@Override
public Iterator<E> iterator() {
class QueueIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return !Queue.this.empty();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) Queue.this.poll();
}
}
return new QueueIterator<E>();
}
}
public class Stack<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
class StackIterator<T> implements Iterator<T> {
@Override
public boolean hasNext() {
return !Stack.this.empty();
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) Stack.this.pop();
}
}
return new StackIterator<E>();
}
}
after5
// Iterator 디자인 패턴 : 5) 익명 클래스로 Iterator 클래스 정의하기
package com.eomcs.design_pattern.iterator.after5;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
LinkedList<String> list2 = new LinkedList<>();
list2.add("aaa2");
list2.add("bbb2");
list2.add("ccc2");
list2.add("ddd2");
Stack<String> list3 = new Stack<>();
list3.push("aaa3");
list3.push("bbb3");
list3.push("ccc3");
list3.push("ddd3");
Queue<String> list4 = new Queue<>();
list4.offer("aaa4");
list4.offer("bbb4");
list4.offer("ccc4");
list4.offer("ddd4");
HashSet<String> list5 = new HashSet<String>();
list5.add("aaa5");
list5.add("bbb5");
list5.add("ccc5");
list5.add("ddd5");
// 목록에서 값 꺼내기
// 1) ArrayList 에서 값 꺼내기
// => ArrayList에게 값을 꺼내는 일을 할 객체를 달라고 요구한다.
Iterator<String> iterator1 = list1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
System.out.println("-------------------------");
// 2) LinkedList 에서 값 꺼내기
Iterator<String> iterator2 = list2.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
System.out.println("-------------------------");
// 3) Stack 에서 값 꺼내기
Iterator<String> iterator3 = list3.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
System.out.println("-------------------------");
// 4) Queue 에서 값 꺼내기
Iterator<String> iterator4 = list4.iterator();
while (iterator4.hasNext()) {
System.out.println(iterator4.next());
}
System.out.println("-------------------------");
// 5) HashSet 에서 값 꺼내기
// => java.util.HashSet 의 iterator()가 리턴하는 객체는
// 우리가 만든 Iterator 가 아니라,
// java.util.Iterator 구현체를 리턴한다.
// 비록 우리가 만든 Iterator가 아닐지라고 사용법(메서드명)은 같다.
// => 해시셋은 입력 된 순서가 아니라 해시값의 오름차순으로 꺼낸다.
java.util.Iterator<String> iterator5 = list5.iterator();
while (iterator5.hasNext()) {
System.out.println(iterator5.next());
}
System.out.println("-------------------------");
// Iterator 설계 패턴의 특징
// - 자료 구조에 상관없이 꺼내는 방식이 같다.
// - 프로그래밍의 일관성을 제공한다.
//
// 문제점
// - ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
// - 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
// - 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
//
// 해결책
// - 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
// - 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
}
}
public class ArrayList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
// anonymous class(익명 클래스)
// => 인스턴스를 한 개만 생성하는 클래스를 만들 경우 사용하는 문법이다.
// => 문법
// 인터페이스명 레퍼런스 = new 인터페이스명() {
// 인터페이스에 선언된 메서드 구현
// }
//
Iterator<E> iterator = new Iterator<>() {
int index = 0;
@Override
public boolean hasNext() {
return index < ArrayList.this.size();
}
@Override
public E next() {
return ArrayList.this.get(index++);
}
};
return iterator;
}
}
public class LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new Iterator<E>() {
int index = 0;
@Override
public boolean hasNext() {
return index < LinkedList.this.size();
}
@Override
public E next() {
return LinkedList.this.get(index++);
}
};
}
}
public class Queue<E> extends LinkedList<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
@Override
public boolean hasNext() {
return !Queue.this.empty();
}
@Override
public E next() {
return Queue.this.poll();
}
};
}
}
public class Stack<E> {
/* 생략 */
// Iterator 구현체를 제공한다.
public Iterator<E> iterator() {
return new Iterator<E>() {
@Override
public boolean hasNext() {
return !Stack.this.empty();
}
@Override
public E next() {
return Stack.this.pop();
}
};
}
}
Observer
com.eomcs.design_pattern.observer
before
a
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
}
}
b
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
// 프로젝트 완료한 다음 시간이 지난 후
// 1) 자동차의 시동을 걸 때 안전벨트 착용 여부를 검사하는 기능을 추가한다.
// => 기존 Car 클래스의 start() 메서드에 코드를 추가한다.
//
// 기존 코드를 변경할 때 나타날 수 있는 문제점
// - 어떤 고객은 해당 기능이 필요 없을 수 있다.
// - 이런 경우, 조건문을 추가하여 기능의 동작 여부를 제어해야 한다.
// - 코드가 복잡해진다.
// - 이미 디버깅과 테스트가 완료된 기존 코드를 변경하면 새 버그가 발생할 수 있다.
// - 해결책?
// - 기존 코드를 손대지 않거나 최소한으로 손대는 것이 좋다.
// - 기존 코드를 손대지 않고 새 기능을 추가하는 방법 중에 하나가
// Observer 패턴으로 설계하는 것이다.
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
// 예) 1월 20일 - 자동차 시동을 걸 때 안전 벨트 착용 여부를 검사하는 기능을 추가
System.out.println("안전벨트 착용 여부 검사");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
}
}
c
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 2) 자동차 시동 걸 때 엔진 오일 검사 기능을 추가한다.
// => Car 클래스의 start() 메서드에 해당 기능을 수행하는 코드를 추가한다.
//
// 기존 코드를 변경할 때 나타날 수 있는 문제점
// - 어떤 고객은 해당 기능이 필요 없을 수 있다.
// - 이런 경우, 조건문을 추가하여 기능의 동작 여부를 제어해야 한다.
// - 코드가 복잡해진다.
// - 이미 디버깅과 테스트가 완료된 기존 코드를 변경하면 새 버그가 발생할 수 있다.
// - 해결책?
// - 기존 코드를 손대지 않거나 최소한으로 손대는 것이 좋다.
// - 기존 코드를 손대지 않고 새 기능을 추가하는 방법 중에 하나가
// Observer 패턴으로 설계하는 것이다.
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
// 예) 1월 20일 - 자동차 시동을 걸 때 안전 벨트 착용 여부를 검사하는 기능을 추가
System.out.println("안전벨트 착용 여부 검사");
// 예) 2월 30일 - 자동차 시동을 걸 때 엔진 오일 유무를 검사하는 기능을 추가
System.out.println("엔진 오일 유무 검사");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
}
}
d
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 3) 자동차 시동 걸 때 브레이크 오일 검사 기능을 추가한다.
// => Car의 start() 메서드에 해당 코드 추가
//
// 기존 코드를 변경할 때 나타날 수 있는 문제점
// - 어떤 고객은 해당 기능이 필요 없을 수 있다.
// - 이런 경우, 조건문을 추가하여 기능의 동작 여부를 제어해야 한다.
// - 코드가 복잡해진다.
// - 이미 디버깅과 테스트가 완료된 기존 코드를 변경하면 새 버그가 발생할 수 있다.
// - 해결책?
// - 기존 코드를 손대지 않거나 최소한으로 손대는 것이 좋다.
// - 기존 코드를 손대지 않고 새 기능을 추가하는 방법 중에 하나가
// Observer 패턴으로 설계하는 것이다.
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
// 예) 1월 20일 - 자동차 시동을 걸 때 안전 벨트 착용 여부를 검사하는 기능을 추가
System.out.println("안전벨트 착용 여부 검사");
// 예) 2월 30일 - 자동차 시동을 걸 때 엔진 오일 유무를 검사하는 기능을 추가
System.out.println("엔진 오일 유무 검사");
// 예) 3월 2일 - 자동차 시동을 걸 때 브레이크 오일 유무를 검사하는 기능을 추가
System.out.println("브레이크 오일 유무 검사");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
}
}
e
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 4) 시동 끌 때 자동차 전조등을 자동으로 끄는 기능을 추가한다.
// => Car의 stop() 메서드에 해당 코드 추가
//
// 기존 코드를 변경할 때 나타날 수 있는 문제점
// - 어떤 고객은 해당 기능이 필요 없을 수 있다.
// - 이런 경우, 조건문을 추가하여 기능의 동작 여부를 제어해야 한다.
// - 코드가 복잡해진다.
// - 이미 디버깅과 테스트가 완료된 기존 코드를 변경하면 새 버그가 발생할 수 있다.
// - 해결책?
// - 기존 코드를 손대지 않거나 최소한으로 손대는 것이 좋다.
// - 기존 코드를 손대지 않고 새 기능을 추가하는 방법 중에 하나가
// Observer 패턴으로 설계하는 것이다.
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
// 예) 1월 20일 - 자동차 시동을 걸 때 안전 벨트 착용 여부를 검사하는 기능을 추가
System.out.println("안전벨트 착용 여부 검사");
// 예) 2월 30일 - 자동차 시동을 걸 때 엔진 오일 유무를 검사하는 기능을 추가
System.out.println("엔진 오일 유무 검사");
// 예) 3월 2일 - 자동차 시동을 걸 때 브레이크 오일 유무를 검사하는 기능을 추가
System.out.println("브레이크 오일 유무 검사");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
// 예) 4월 15일 - 자동차 시동을 끌 때 전조등 자동 끄기 기능을 추가
System.out.println("전조등을 끈다.");
}
}
f
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 5) 시동 끌 때 썬루프를 자동으로 닫기
// => Car의 stop() 메서드에 해당 코드 추가
// 결론!
// => 기존의 프로그래밍 방식은 특정 상태에서 수행하는 기능을 추가할 때
// 기존 클래스에 계속 코드를 추가해야 했다.
// => 기존 코드에 계속 새 코드를 추가하는 방식은 유지보수에 좋지 않다.
// => Observer 패턴을 적용하면 기존 클래스를 손대지 않고
// 특정 상태에서 수행하는 작업을 쉽게 추가할 수 있다.
}
}
public class Car {
public void start() {
System.out.println("시동을 건다.");
// 예) 1월 20일 - 자동차 시동을 걸 때 안전 벨트 착용 여부를 검사하는 기능을 추가
System.out.println("안전벨트 착용 여부 검사");
// 예) 2월 30일 - 자동차 시동을 걸 때 엔진 오일 유무를 검사하는 기능을 추가
System.out.println("엔진 오일 유무 검사");
// 예) 3월 2일 - 자동차 시동을 걸 때 브레이크 오일 유무를 검사하는 기능을 추가
System.out.println("브레이크 오일 유무 검사");
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
// 예) 4월 15일 - 자동차 시동을 끌 때 전조등 자동 끄기 기능을 추가
System.out.println("전조등을 끈다.");
// 예) 5월 5일 - 자동차 시동을 끌 때 썬루프 자동 닫기 기능을 추가
System.out.println("썬루프를 닫는다.");
}
}
after
a
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.run();
car.stop();
}
}
a~h 패키지 모두 동일한 인터페이스 사용한다.
public interface CarObserver {
// 자동차 시동을 켤 때 호출될 메서드
// => 보통 메서드의 이름은 동사로 시작하는데,
// 옵저버에게 통지할 때 호출하는 메서드는
// 명사구의 상태 이름으로 정의할 수 있다.
void carStarted();
// 자동차 시동을 끌 때 호출될 메서드
void carStopped();
}
import java.util.ArrayList;
import java.util.List;
public class Car {
//-------------------------------------------------------------------
// Observer 디자인 패턴 적용:
// - publisher 쪽에 추가해야 하는 필드와 메서드
// 관찰자(observer/listener/subscriber)의 객체 주소를 보관한다.
List<CarObserver> observers = new ArrayList<>();
// 자동차의 상태 변경을 보고 받을 관찰자(Observer)를 등록한다.
public void addCarObserver(CarObserver observer) {
observers.add(observer);
}
// 자동차의 상태 변경을 보고 받는 관찰자를 제거한다.
public void removeCarObserver(CarObserver observer) {
observers.remove(observer);
}
//-------------------------------------------------------------------
public void start() {
System.out.println("시동을 건다.");
// ---------------------------------------------------
// Observer 디자인 패턴:
// - publisher의 상태가 바뀌었을 때 subscriber에게 통지한다.
// - 즉 subscriber(observer/listener)에 대해 규칙(CarObserver 인터페이스)에 따라 메서드를 호출한다.
// 예) 자동차의 시동을 걸면, 등록된 관찰자들에게 알린다.
for (CarObserver observer : observers) {
observer.carStarted();
}
// ---------------------------------------------------
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
// ---------------------------------------------------
// Observer 디자인 패턴:
// - publisher의 상태가 바뀌었을 때 subscriber에게 통지한다.
// - 즉 subscriber(observer/listener)에 대해 규칙(CarObserver 인터페이스)에 따라 메서드를 호출한다.
// 예) 자동차의 시동을 끄면, 등록된 관찰자들에게 보고한다.
for (CarObserver observer : observers) {
observer.carStopped();
}
// ---------------------------------------------------
}
}
b
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
// 새 기능이 들어 있는 객체를 Car(publisher)에 등록한다.
// - Car 클래스를 손대지 않고 새 기능을 추가하는 방법이다.
// - 이것이 Observer 패턴으로 구조화시킨 이유이다.
car.addCarObserver(new SafeBeltCarObserver());
car.start();
// Car 객체는 start()가 호출되면
// 등록된 모든 subscriber(observer/listener)에게 통지(메서드 호출)한다.
car.run();
car.stop();
// 프로젝트 완료한 다음 시간이 지난 후
// 1) 자동차의 시동을 걸 때 안전벨트 착용 여부를 검사하는 기능을 추가한다.
// - 자동차의 시동을 걸릴 때 보고를 받을 객체(SafeBeltCarObserver)를 준비한다.
// - 시동 걸 때 수행할 기능을 정의한다. 즉 carStarted() 메서드 정의
// - Car 객체에 관찰자를 등록한다.
}
}
b~f 패키지까지 동일한 객체 사용한다.
import java.util.ArrayList;
import java.util.List;
public class Car {
// 관찰자의 객체 주소를 보관한다.
List<CarObserver> observers = new ArrayList<>();
// 자동차의 상태 변경을 보고 받을 관찰자(Observer)를 등록한다.
public void addCarObserver(CarObserver observer) {
observers.add(observer);
}
// 자동차의 상태 변경을 보고 받는 관찰자를 제거한다.
public void removeCarObserver(CarObserver observer) {
observers.remove(observer);
}
public void start() {
System.out.println("시동을 건다.");
// 자동차의 시동을 걸면,
// 등록된 관찰자들에게 알린다.
for (CarObserver observer : observers) {
observer.carStarted();
}
}
public void run() {
System.out.println("달린다.");
}
public void stop() {
System.out.println("시동을 끈다.");
// 자동차의 시동을 끄면,
// 등록된 관찰자들에게 보고한다.
for (CarObserver observer : observers) {
observer.carStopped();
}
}
}
public class SafeBeltCarObserver implements CarObserver {
@Override
public void carStarted() {
// 자동차의 시동을 걸 때 호출되는 메서드이다.
// - 시동 걸 때 뭔가 작업하고 싶다면 이 메서드에 그 코드를 작성하면 된다.
System.out.println("안전벨트 착용 여부 검사");
}
@Override
public void carStopped() {
// 자동차의 시동을 끌 때 호출되는 메서드이다.
// - 시동 끌 때 뭔가 작업하고 싶다면 이 메서드에 그 코드를 작성하면 된다.
}
}
c
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
// 엔진 오일을 검사할 옵저버를 등록한다.
car.addCarObserver(new EngineOilCarObserver());
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 2) 자동차 시동 걸 때 엔진 오일 검사 기능을 추가한다.
// => 엔진오일 검사하는 옵저버(EngineOilCarObserver)를 정의한다.
// => Car 객체에 등록한다.
//
}
}
추가
public class EngineOilCarObserver implements CarObserver {
@Override
public void carStarted() {
System.out.println("엔진 오일 유무 검사");
}
@Override
public void carStopped() {}
}
d
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
car.addCarObserver(new EngineOilCarObserver());
// 브레이크 오일을 검사할 옵저버를 추가한다.
// - 기존 구조에서는 Car 클래스에 코드를 추가하였다!!!
// - 옵저버 패턴으로 구조를 바꾼 후에는 이렇게 새 클래스를 정의하여 등록한다.
car.addCarObserver(new BrakeOilCarObserver());
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 3) 자동차 시동 걸 때 브레이크 오일 검사 기능을 추가한다.
// => 브레이크 오일 검사하는 옵저버(BreakOilCarObserver)를 정의한다.
// => Car 객체에 등록한다.
//
}
}
추가
public class BrakeOilCarObserver implements CarObserver {
@Override
public void carStarted() {
System.out.println("브레이크 오일 유무 검사");
}
@Override
public void carStopped() {}
}
e
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
car.addCarObserver(new EngineOilCarObserver());
car.addCarObserver(new BrakeOilCarObserver());
// 전조등을 끄는 옵저버를 추가한다.
// - 기존 구조에서는 Car 클래스에 코드를 추가하였다!!!
// - 옵저버 패턴으로 구조를 바꾼 후에는 이렇게 새 클래스를 정의하여 등록한다.
car.addCarObserver(new LightOffCarObserver());
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 4) 시동 끌 때 자동차 전조등을 자동으로 끄는 기능을 추가한다.
// => 전조등을 자동으로 끄는 옵저버(LightOffCarObserver)를 정의한다.
// => Car 객체에 등록한다.
//
}
}
추가
public class LightOffCarObserver implements CarObserver {
@Override
public void carStarted() {}
@Override
public void carStopped() {
System.out.println("전조등을 끈다.");
}
}
f
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
car.addCarObserver(new EngineOilCarObserver());
car.addCarObserver(new BrakeOilCarObserver());
car.addCarObserver(new LightOffCarObserver());
// 썬루프를 닫는 옵저버를 추가한다.
// - 기존 구조에서는 Car 클래스에 코드를 추가하였다!!!
// - 옵저버 패턴으로 구조를 바꾼 후에는 이렇게 새 클래스를 정의하여 등록한다.
car.addCarObserver(new SunRoofCloseCarObserver());
car.start();
car.run();
car.stop();
// 업그레이드를 수행한 다음 시간이 지난 후
// 5) 시동 끌 때 썬루프를 자동으로 닫기
// => 썬루프 자동으로 닫는 옵저버(SunRoofCloseCarObserver)를 정의한다.
// => Car 객체에 등록한다.
//
}
}
추가
public class SunRoofCloseCarObserver implements CarObserver {
@Override
public void carStarted() {}
@Override
public void carStopped() {
System.out.println("썬루프를 닫는다.");
}
}
g
Car 클래스를 리팩토링 한다.
- 옵저버에 통지하는 코드를 별도의 메서드로 분리하여 유지보수 하기 좋게 만든다.
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
car.addCarObserver(new EngineOilCarObserver());
car.addCarObserver(new BrakeOilCarObserver());
car.addCarObserver(new LightOffCarObserver());
car.addCarObserver(new SunRoofCloseCarObserver());
car.start();
car.run();
car.stop();
}
}
// 인터페이스 구현체가 메서드를 정의하기 쉽도록
// 이 클래스에서 미리 모든(또는 일부) 메서드를 구현하였다.
// 이 클래스의 존재 이유는 인터페이스 구현체가 메서드를 정의하기 쉽도록
// 미리 구현된 메서드를 상속해주는 일을 한다.
// 즉 이 클래스 자체를 사용하려는 것이 아니다.
// 이런 경우 추상 클래스로 정의하면 좋다.
// - 추상 메서드가 없지만, 추상 클래스로 선언함으로써
// 개발자에게 이 클래스의 역할을 알리는 효과가 있다.
//
// 인터페이스를 구현한 추상 클래스는
// 보통 그 클래스 이름을 'Abstract-'로 시작한다.
//
public abstract class AbstractCarObserver implements CarObserver {
@Override
public void carStarted() {
// 서브 클래스에게 구현된 메서드를 상속해주기 위해
// 수퍼 클래스에서 미리 구현한다.
// 단, 아무런 코드를 넣지 않는다.
}
@Override
public void carStopped() {
// 서브 클래스에게 구현된 메서드를 상속해주기 위해
// 수퍼 클래스에서 미리 구현한다.
// 단, 아무런 코드를 넣지 않는다.
}
}
// 이전 버전에서는 인터페이스를 직접 구현했지만,
// (그래서 관심도 없는 carStopped() 메서드까지 정의했다)
// 이번 버전에서는 추상 클래스를 상속 받아 간접적으로 구현한다.
public class BrakeOilCarObserver extends AbstractCarObserver {
@Override
public void carStarted() {
System.out.println("브레이크 오일 유무 검사");
}
// 어? carStopped() 구현하지 않았네요?
// - 수퍼 클래스에서 구현했습니다.
// - 물론 빈 코드를 가진 메서드이죠!
//
}
// 이전 버전에서는 인터페이스를 직접 구현했지만,
// (그래서 관심도 없는 carStopped() 메서드까지 정의했다)
// 이번 버전에서는 추상 클래스를 상속 받아 간접적으로 구현한다.
public class EngineOilCarObserver extends AbstractCarObserver {
@Override
public void carStarted() {
System.out.println("엔진 오일 유무 검사");
}
}
// 이전 버전에서는 인터페이스를 직접 구현했지만,
// (그래서 관심도 없는 carStopped() 메서드까지 정의했다)
// 이번 버전에서는 추상 클래스를 상속 받아 간접적으로 구현한다.
public class LightOffCarObserver extends AbstractCarObserver {
@Override
public void carStopped() {
System.out.println("전조등을 끈다.");
}
}
// 이전 버전에서는 인터페이스를 직접 구현했지만,
// (그래서 관심도 없는 carStopped() 메서드까지 정의했다)
// 이번 버전에서는 추상 클래스를 상속 받아 간접적으로 구현한다.
public class SafeBeltCarObserver extends AbstractCarObserver {
@Override
public void carStarted() {
System.out.println("안전벨트 착용 여부 검사");
}
}
// 이전 버전에서는 인터페이스를 직접 구현했지만,
// (그래서 관심도 없는 carStopped() 메서드까지 정의했다)
// 이번 버전에서는 추상 클래스를 상속 받아 간접적으로 구현한다.
public class SunRoofCloseCarObserver extends AbstractCarObserver {
@Override
public void carStopped() {
System.out.println("썬루프를 닫는다.");
}
}
h
CarObserver 구현체를 만들 때,
- 인터페이스에 선언된 모든 메서드를 구현해야 한다.
- 관심없는 메서드라도 구현해야 한다.
- 예)
SafeBeltCarObserver는 시동 걸 때 작업을 수행한다.
그래서 carStarted() 메서드에 코드를 삽입하였다.
그런데 인터페이스를 구현하려면 모든 메서드를 정의해야 하기 때문에
관심이 없는데도 불구하고 carStopped() 메서드도 구현하였다.
물론 코드가 없는 빈 메서드이다.
인터페이스를 좀 더 쉽게 구현하는 방법:
- 위와 같은 경우에,
(여러 개의 메서드 중에서 주로 일부 메서드만 구현하는 경우)
추상 클래스를 사용하여 메서드를 미리 구현해 놓으면
인터페이스 구현체를 정의하기 편하다!
이번 실습,
- CarObserver 를 미리 구현한 AbstractCarObserver를 만들고,
구현체는 이 추상 클래스를 상속 받도록 한다.
public class Test01 {
public static void main(String[] args) {
Car car = new Car();
car.addCarObserver(new SafeBeltCarObserver());
car.addCarObserver(new EngineOilCarObserver());
car.addCarObserver(new BrakeOilCarObserver());
car.addCarObserver(new LightOffCarObserver());
car.addCarObserver(new SunRoofCloseCarObserver());
car.start();
car.run();
car.stop();
}
}
나머지 위와 동일
Template Method
com.eomcs.design_pattern.template_method
템플릿 메서드 패턴 - 수퍼 클래스에 기본 실행 흐름을 정의하고 서브 클래스에서 세부 항목을 구현한다.
package com.eomcs.design_pattern.template_method;
public class Test01 {
public static void main(String[] args) {
// 예: 스타크래프트에서 건물 짓기
// => 건물을 짓는 과정은 같다.
// => 그러나 어떤 건물이냐에 따라 출력되는 화면이 다른다.
// 식당 짓기
Restaurant u1 = new Restaurant();
u1.setName("군인식당");
u1.setArea(50);
u1.setType(Unit.GENERAL_BUILDING);
u1.build(); // 수퍼 클래스에서 상속 받은 메서드를 호출한다.
System.out.println("-----------------------------");
// 훈련소 짓기
TrainingCenter u2 = new TrainingCenter();
u2.setName("훈련소");
u2.setArea(500);
u2.setType(Unit.GENERAL_BUILDING);
u2.build();
}
package com.eomcs.design_pattern.template_method;
// 이 클래스는 직접 사용할 클래스가 아니다.
// => 건물을 짓는데 필요한 기본 필드와 메서드를 서브 클래스에게 상속해주는 용도로 사용한다.
// => 일부 메서드는 추상 메서드이다.
// => 따라서 이 클래스는 추상 클래스이어야 한다.
//
// 결국, 이 클래스는 특정 빌딩을 짓는 서브 클래스를 만들기 위한 틀(template)로서 사용된다.
// 기본 흐름은 build() 메서드에 정의되어 있다.
// 흐름에서 각 단계는 추상 메서드로 선언되어 있다.
// 어떤 작업을 수행할지는 서브 클래스에서 정의하는 것이다.
// 즉 Unit 클래스는 서브 클래스가 구현해야 할 틀
// (template method; prepare(), construct(), install(), interio())를 제공한다.
public abstract class Unit {
// 건물의 type 값을 지정할 때 직접 숫자를 사용하면 나중에 알아보기 힘들다.
// 유지보수가 쉬우려면 숫자 대신 문자를 사용하는 것이 낫다.
// 그래서 상수 변수를 활용하는 것이다.
//
public static final int GENERAL_BUILDING = 0;
public static final int DEFENCE_BUILDING = 1;
public static final int ATTACK_BUILDING = 2;
protected String name;
protected int area;
protected int type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getArea() {
return area;
}
public void setArea(int area) {
this.area = area;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
// 수퍼 클래스에서 기본 흐름을 정의한다.
// => 그래서 build() 메서드는 구현 메서드로 정의한다.
//
public void build() {
prepare(); // 건물을 지을 땅을 준비한다.
construct(); // 건물 뼈대를 짓는다.
install(); // 배선 및 단열, 외장을 설치한다.
interio(); // 내부 인테리어를 한다.
}
// 유닛의 종류에 따라 건물을 짓는 방식이 다르기 때문에
// 구체적인 구현은 서브 클래스에게 맡긴다.
// => 따라서 build()가 호출하는 메서드는 추상 메서드로 선언한다.
//
public abstract void prepare();
public abstract void construct();
public abstract void install();
public abstract void interio();
}
package com.eomcs.design_pattern.template_method;
public class Restaurant extends Unit {
@Override
public void prepare() {
System.out.println("간단히 땅을 고른다.");
}
@Override
public void construct() {
System.out.println("조립 파넬을 세운다.");
}
@Override
public void install() {
System.out.println("내부 배선과 창을 붙인다.");
}
@Override
public void interio() {
System.out.println("식탁과 의자를 배치한다.");
}
}
package com.eomcs.design_pattern.template_method;
public class TrainingCenter extends Unit {
@Override
public void prepare() {
System.out.println("운동장을 고르고, 숙소 건물 땅에 콘크리트 포장한다.");
}
@Override
public void construct() {
System.out.println("운동장에 잔디를 깔고, 숙소 건물을 짓는다.");
}
@Override
public void install() {
System.out.println("운동장에 연설대를 설치하고 숙소의 내부 배선과 창을 붙인다.");
}
@Override
public void interio() {
System.out.println("숙서 내부 침실을 꾸민다.");
}
}
템플릿 메서드 패턴 + 팩토리 메서드 패턴
package com.eomcs.design_pattern.template_method;
public class Test02 {
public static void main(String[] args) {
// 유닛 객체를 직접 생성하지 않고 공장 객체를 통해 생성한다.
UnitFactory unitFactory = new UnitFactory();
// 식당 짓기
Unit u1 = unitFactory.createUnit(UnitFactory.RESTAURANT);
u1.build(); // 수퍼 클래스에서 상속 받은 메서드를 호출한다.
System.out.println("-----------------------------");
// 훈련소 짓기
Unit u2 = unitFactory.createUnit(UnitFactory.TRAINING_CENTER);
u2.build();
}
}
package com.eomcs.design_pattern.template_method;
// 템플릿 메서드 패턴 + 팩토리 메서드 패턴
public class UnitFactory {
public static final int RESTAURANT = 1;
public static final int TRAINING_CENTER = 2;
// 객체 생성 과정이 복잡한 경우 직접 객체를 생성하기 보다는
// 객체를 생성해주는 메서드를 호출하는 것이 유지보수에 좋다.
//
public Unit createUnit(int building) {
switch (building) {
case RESTAURANT:
return createRestaurant();
case TRAINING_CENTER:
return createTrainingCenter();
}
return null;
}
private Unit createRestaurant() {
Unit unit = new Restaurant();
unit.setName("군인식당");
unit.setArea(50);
unit.setType(Unit.GENERAL_BUILDING);
return unit;
}
private Unit createTrainingCenter() {
Unit unit = new TrainingCenter();
unit.setName("훈련소");
unit.setArea(500);
unit.setType(Unit.GENERAL_BUILDING);
return unit;
}
}
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[Java] 예제 소스 정리 - 스레드 (0) | 2023.02.04 |
---|---|
[Java] 예제 소스 정리 - 네트워킹(HTTP, 연결 방식) (0) | 2023.02.03 |
[비트캠프] 63일차(13주차5일) - Java(네트워킹 연결방식, 스레드), myapp-33~35 (31) | 2023.02.03 |
[Java] 예제 소스 정리 - 네트워킹(Client/Server, 연결 방식) (0) | 2023.02.02 |
[비트캠프] 62일차(13주차4일) - Java(Client, Server 아키텍처), myapp-32-1~2 (0) | 2023.02.02 |