헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

메소드 호출을 캡슐화

커맨드 패턴은 작업을 요청하는 쪽과 작업을 처리하는 쪽을 분리할 수 있다.

 

커맨드 객체는 특정 객체에 관한 특정 작업 요청을 캡슐화한다.

커맨드 패턴 소개

구성요소 클라이언트, 인보커, 커맨드, 리시버

클라이언트는 커맨드 객체를 생성한다.

커맨드 객체 안에는 리시버가 있다.

리시버는 실제로 동작을 수행하는 역할을 한다.

커맨드 객체는 인보커에 저장된다. 

인보커는 클라이언트 요청으로 저장된 커맨드 객체를 실행한다.

커맨드 객체에 동작은 리시버가 한다.

 

public class CommandTest {
	//리시버
	static class Light {
		public Light() {	}
		public void on() {
			System.out.println("Light is on");
		}
		public void off() {
			System.out.println("Light is off");
		}
	}
	static  class GarageDoor {
		public GarageDoor() {
		}

		public void up() {
			System.out.println("Garage Door is Open");
		}

		public void down() {
			System.out.println("Garage Door is Closed");
		}

		public void stop() {
			System.out.println("Garage Door is Stopped");
		}

		public void lightOn() {
			System.out.println("Garage light is on");
		}

		public void lightOff() {
			System.out.println("Garage light is off");
		}
	}
	
	//커맨드
	@FunctionalInterface
	static interface Command{
		void execute();
	}
	static class LightOnCommand implements Command{
		Light light;
		
		public LightOnCommand(Light light) {
			this.light = light;
		}
		public void execute() {
			light.on();
		}
	}
	static class GarageDoorOpenCommand implements Command{
		GarageDoor garageDoor;
		public GarageDoorOpenCommand(GarageDoor garageDoor) {
			this.garageDoor = garageDoor;
		}
		@Override
		public void execute() {
			garageDoor.up();
		}
	}
	//인보커
	static class SimpleRemoteControl{
		Command slot;
		public SimpleRemoteControl() {	}
		
		public void setCommand(Command slot) {
			this.slot = slot;
		}
		public void buttonWasPressed() {
			slot.execute();
		}
	}
	
	//클라이언트(편의상 main이 이 역할), 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
	public static void main(String[] args) {
		//인보커, 내부에 커맨드 객체를 저장하고, 다시 요청이 오면 커맨드 객체를 실행
		SimpleRemoteControl remoteControl = new SimpleRemoteControl();
		//리시버, 실제 동작을 수행하는 객체
		Light light = new Light();
		//커맨드, 외부와  내부에 리시버에게 동작을 위임한다.
		LightOnCommand lightOnCommand = new LightOnCommand(light);
		//인보커에 커맨드 저장
		remoteControl.setCommand(lightOnCommand);
		//클라이언트가 저장한 커맨드 객체 실행 요청, 이 메서드는 단순히 리시버에 동작을 위임한다.
		remoteControl.buttonWasPressed();
		
		GarageDoor garageDoor = new GarageDoor();
		GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);
		
		remoteControl.setCommand(garageDoorOpenCommand);
		remoteControl.buttonWasPressed();
	}

}
Light is on
Garage Door is Open

커맨드 패턴의 정의

요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다.

요청을 따로 큐 같은 자료구조에 저장해 실행 전에 작업 취소 같은 기능 구현도 가능하다.

import java.util.*;

public class 심플커맨드 {
	static interface Command{
		void execute();
	}
	static class Invoker{
		//큐 예시
		private final Queue<Command> commands = new LinkedList<>();
		public void addCommand(Command command) {
			this.commands.add(command);
		}
		public void action() {
			if(!commands.isEmpty())
				commands.poll().execute();
			else System.out.println("더 이상의 요청은 없어요!");
		}
	}
	public static void main(String[] args) {
		Invoker invoker = new Invoker();
		invoker.addCommand(()->System.out.println("커맨드 객체는 요청을 캡슐화합니다."));
		invoker.addCommand(()->System.out.println("커맨드 객체는 요청을 캡슐화합니다."));
		
		invoker.action();
		invoker.action();
		invoker.action();
	}
}
커맨드 객체는 요청을 캡슐화합니다.
커맨드 객체는 요청을 캡슐화합니다.
더 이상의 요청은 없어요!

 

켜고 끄는 슬롯이 존재하는 커맨드 패턴 구현

public class CommandTest2 {
    // 리시버
    static class Light {
        String location = "";
        public Light(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " light is on");
        }
        public void off() {
            System.out.println(location + " light is off");
        }
    }


    static class TV {
        String location;
        int channel;
        public TV(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println("TV is on");
        }
        public void off() {
            System.out.println("TV is off");
        }
        public void setInputChannel() {
            this.channel = 3;
            System.out.println("Channel is set for VCR");
        }
    }
    static class Stereo {
        String location;
        public Stereo(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " stereo is on");
        }
        public void off() {
            System.out.println(location + " stereo is off");
        }
        public void setCD() {
            System.out.println(location + " stereo is set for CD input");
        }
        public void setDVD() {
            System.out.println(location + " stereo is set for DVD input");
        }
        public void setRadio() {
            System.out.println(location + " stereo is set for Radio");
        }
        public void setVolume(int volume) {
            // code to set the volume
            // valid range: 1-11 (after all 11 is better than 10, right?)
            System.out.println(location + " stereo volume set to " + volume);
        }
    }
    static class Hottub {
        boolean on;
        int temperature;
        public Hottub() {
        }
        public void on() {
            on = true;
        }
        public void off() {
            on = false;
        }
        public void bubblesOn() {
            if (on) {
                System.out.println("Hottub is bubbling!");
            }
        }
        public void bubblesOff() {
            if (on) {
                System.out.println("Hottub is not bubbling");
            }
        }
        public void jetsOn() {
            if (on) {
                System.out.println("Hottub jets are on");
            }
        }
        public void jetsOff() {
            if (on) {
                System.out.println("Hottub jets are off");
            }
        }
        public void setTemperature(int temperature) {
            this.temperature = temperature;
        }
        public void heat() {
            temperature = 105;
            System.out.println("Hottub is heating to a steaming 105 degrees");
        }
        public void cool() {
            temperature = 98;
            System.out.println("Hottub is cooling to 98 degrees");
        }
    }
    static class CeilingFan {
        String location = "";
        int level;
        public static final int HIGH = 2;
        public static final int MEDIUM = 1;
        public static final int LOW = 0;
        public CeilingFan(String location) {
            this.location = location;
        }
        public void high() {
            // turns the ceiling fan on to high
            level = HIGH;
            System.out.println(location + " ceiling fan is on high");
        }
        public void medium() {
            // turns the ceiling fan on to medium
            level = MEDIUM;
            System.out.println(location + " ceiling fan is on medium");
        }
        public void low() {
            // turns the ceiling fan on to low
            level = LOW;
            System.out.println(location + " ceiling fan is on low");
        }
        public void off() {
            // turns the ceiling fan off
            level = 0;
            System.out.println(location + " ceiling fan is off");
        }
        public int getSpeed() {
            return level;
        }
    }
    static class GarageDoor {
        String location;
        public GarageDoor(String location) {
            this.location = location;
        }
        public void up() {
            System.out.println(location + " garage Door is Up");
        }
        public void down() {
            System.out.println(location + " garage Door is Down");
        }
        public void stop() {
            System.out.println(location + " garage Door is Stopped");
        }
        public void lightOn() {
            System.out.println(location + " garage light is on");
        }
        public void lightOff() {
            System.out.println(location + " garage light is off");
        }
    }


    // 커맨드
    @FunctionalInterface
    static interface Command {
        void execute();
    }
    static class NoCommand implements Command {
        public void execute() {
        }
    }
    static class LightOnCommand implements Command {
        Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
    }
    static class LightOffCommand implements Command {
        Light light;
        public LightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
    }
    static class GarageDoorUpCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorUpCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
    }
    static class GarageDoorDownCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorDownCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
    }
    static class StereoOnWithCDCommand implements Command {
        Stereo stereo;
        public StereoOnWithCDCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
    }
    static class StereoOffCommand implements Command {
        Stereo stereo;
        public StereoOffCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.off();
        }
    }
    static class CeilingFanOnCommand implements Command {
        CeilingFan ceilingFan;
        public CeilingFanOnCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            ceilingFan.high();
        }
    }
    static class CeilingFanOffCommand implements Command {
        CeilingFan ceilingFan;
        public CeilingFanOffCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            ceilingFan.off();
        }
    }
    static class LivingroomLightOnCommand implements Command {
        Light light;
        public LivingroomLightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
    }
    static class LivingroomLightOffCommand implements Command {
        Light light;
        public LivingroomLightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
    }
    static class HottubOnCommand implements Command {
        Hottub hottub;
        public HottubOnCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
    }
    static class HottubOffCommand implements Command {
        Hottub hottub;
        public HottubOffCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.cool();
            hottub.off();
        }
    }
    // 인보커
    static class RemoteControl {
        Command[] onCommands;
        Command[] offCommands;
        public RemoteControl() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            Command noCommand = new NoCommand();
            for (int i = 0; i < offCommands.length; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
        }
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
        }
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("\n------ 리모컨 ------\n");
            for (int i = 0; i < offCommands.length; i++) {
                sb.append("[slot" + i + "]" + onCommands[i].getClass().getSimpleName());
                sb.append("    " + offCommands[i].getClass().getSimpleName()+"\n");
            }
            return sb.toString();
        }
    }
    // 클라이언트, 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
         
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        CeilingFan ceilingFan= new CeilingFan("Living Room");
        GarageDoor garageDoor = new GarageDoor("Garage");
        Stereo stereo = new Stereo("Living Room");
       
        LightOnCommand livingRoomLightOn =
                new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff =
                new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn =
                new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff =
                new LightOffCommand(kitchenLight);


        CeilingFanOnCommand ceilingFanOn =
                new CeilingFanOnCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff =
                new CeilingFanOffCommand(ceilingFan);


        GarageDoorUpCommand garageDoorUp =
                new GarageDoorUpCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown =
                new GarageDoorDownCommand(garageDoor);


        StereoOnWithCDCommand stereoOnWithCD =
                new StereoOnWithCDCommand(stereo);
        StereoOffCommand  stereoOff =
                new StereoOffCommand(stereo);


        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);


        System.out.println(remoteControl);


        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }
}
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]LightOnCommand LightOffCommand
[slot2]CeilingFanOnCommand CeilingFanOffCommand
[slot3]StereoOnWithCDCommand StereoOffCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
Living Room light is on
Living Room light is off
Kitchen light is on
Kitchen light is off
Living Room ceiling fan is on high
Living Room ceiling fan is off
Living Room stereo is on
Living Room stereo is set for CD input
Living Room stereo volume set to 11
Living Room stereo is off

즐겨찾기 처럼 슬롯에 저장해둔 커맨드 객체는 언제든지 호출할 수 있다.

4~6 슬롯에 NoCommand 객체는 아무런 일도 안한다. 일종의 Null 객체로 이렇게 구현해두면 호출하는 쪽에서 Null 걱정을 을 할필요가 없다.

 

실행 했던 커맨드 되돌리기 

public class CommandTest3 {
    // 리시버
    static class Light {
        String location = "";
        public Light(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " light is on");
        }
        public void off() {
            System.out.println(location + " light is off");
        }
    }
    static class TV {
        String location;
        int channel;
        public TV(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println("TV is on");
        }
        public void off() {
            System.out.println("TV is off");
        }
        public void setInputChannel() {
            this.channel = 3;
            System.out.println("Channel is set for VCR");
        }
    }
    static class Stereo {
        String location;
        public Stereo(String location) {
            this.location = location;
        }
        public void on() {
            System.out.println(location + " stereo is on");
        }
        public void off() {
            System.out.println(location + " stereo is off");
        }
        public void setCD() {
            System.out.println(location + " stereo is set for CD input");
        }
        public void setDVD() {
            System.out.println(location + " stereo is set for DVD input");
        }
        public void setRadio() {
            System.out.println(location + " stereo is set for Radio");
        }
        public void setVolume(int volume) {
            // code to set the volume
            // valid range: 1-11 (after all 11 is better than 10, right?)
            System.out.println(location + " stereo volume set to " + volume);
        }
    }
    static class Hottub {
        boolean on;
        int temperature;
        public Hottub() {
        }
        public void on() {
            on = true;
        }
        public void off() {
            on = false;
        }
        public void bubblesOn() {
            if (on) {
                System.out.println("Hottub is bubbling!");
            }
        }
        public void bubblesOff() {
            if (on) {
                System.out.println("Hottub is not bubbling");
            }
        }
        public void jetsOn() {
            if (on) {
                System.out.println("Hottub jets are on");
            }
        }
        public void jetsOff() {
            if (on) {
                System.out.println("Hottub jets are off");
            }
        }
        public void setTemperature(int temperature) {
            this.temperature = temperature;
        }
        public void heat() {
            temperature = 105;
            System.out.println("Hottub is heating to a steaming 105 degrees");
        }
        public void cool() {
            temperature = 98;
            System.out.println("Hottub is cooling to 98 degrees");
        }
    }
    static class CeilingFan {
        public static final int HIGH = 3;
        public static final int MEDIUM = 2;
        public static final int LOW = 1;
        public static final int OFF = 0;
        String location;
        int speed;
        public CeilingFan(String location) {
            this.location = location;
            speed = OFF;
        }
        public void high() {
            speed = HIGH;
            System.out.println(location + " ceiling fan is on high");
        }
        public void medium() {
            speed = MEDIUM;
            System.out.println(location + " ceiling fan is on medium");
        }
        public void low() {
            speed = LOW;
            System.out.println(location + " ceiling fan is on low");
        }
        public void off() {
            speed = OFF;
            System.out.println(location + " ceiling fan is off");
        }
        public int getSpeed() {
            return speed;
        }
    }
    static class GarageDoor {
        String location;
        public GarageDoor(String location) {
            this.location = location;
        }
        public void up() {
            System.out.println(location + " garage Door is Up");
        }
        public void down() {
            System.out.println(location + " garage Door is Down");
        }
        public void stop() {
            System.out.println(location + " garage Door is Stopped");
        }
        public void lightOn() {
            System.out.println(location + " garage light is on");
        }
        public void lightOff() {
            System.out.println(location + " garage light is off");
        }
    }
    // 커맨드
    static interface Command {
        void execute();
        void undo();
    }
    static class NoCommand implements Command {
        public void execute() {
        }
        public void undo() {
        }
    }
    static class LightOnCommand implements Command {
        Light light;
        public LightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
        public void undo() {
            light.off();
        }
    }
    static class LightOffCommand implements Command {
        Light light;
        public LightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
        @Override
        public void undo() {
            light.on();
        }
    }
    static class GarageDoorUpCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorUpCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.up();
        }
        @Override
        public void undo() {
            garageDoor.down();
        }
    }
    static class GarageDoorDownCommand implements Command {
        GarageDoor garageDoor;
        public GarageDoorDownCommand(GarageDoor garageDoor) {
            this.garageDoor = garageDoor;
        }
        public void execute() {
            garageDoor.down();
        }
        public void undo() {
            garageDoor.up();
        }
    }
    static class StereoOnWithCDCommand implements Command {
        Stereo stereo;
        public StereoOnWithCDCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
        @Override
        public void undo() {
            stereo.off();
        }
    }
    static class StereoOffCommand implements Command {
        Stereo stereo;
        int prev;
        public StereoOffCommand(Stereo stereo) {
            this.stereo = stereo;
        }
        public void execute() {
            stereo.off();
        }
        @Override
        public void undo() {
            stereo.on();
            stereo.setCD();
            stereo.setVolume(11);
        }
    }
    static class CeilingFanHighCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanHighCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.high();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanMediumCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanMediumCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.medium();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanLowCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanLowCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.low();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class CeilingFanOffCommand implements Command {
        CeilingFan ceilingFan;
        int prevSpeed;
        public CeilingFanOffCommand(CeilingFan ceilingFan) {
            this.ceilingFan = ceilingFan;
        }
        public void execute() {
            prevSpeed = ceilingFan.getSpeed();
            ceilingFan.off();
        }
        public void undo() {
            if (prevSpeed == CeilingFan.HIGH) {
                ceilingFan.high();
            } else if (prevSpeed == CeilingFan.MEDIUM) {
                ceilingFan.medium();
            } else if (prevSpeed == CeilingFan.LOW) {
                ceilingFan.low();
            } else if (prevSpeed == CeilingFan.OFF) {
                ceilingFan.off();
            }
        }
    }
    static class LivingroomLightOnCommand implements Command {
        Light light;
        public LivingroomLightOnCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.on();
        }
        public void undo() {
            light.off();
        }
    }
    static class LivingroomLightOffCommand implements Command {
        Light light;
        public LivingroomLightOffCommand(Light light) {
            this.light = light;
        }
        public void execute() {
            light.off();
        }
        public void undo() {
            light.on();
        }
    }
    static class HottubOnCommand implements Command {
        Hottub hottub;
        public HottubOnCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
        public void undo() {
            hottub.cool();
            hottub.off();
        }
    }
    static class HottubOffCommand implements Command {
        Hottub hottub;
        public HottubOffCommand(Hottub hottub) {
            this.hottub = hottub;
        }
        public void execute() {
            hottub.cool();
            hottub.off();
        }
        @Override
        public void undo() {
            hottub.on();
            hottub.heat();
            hottub.bubblesOn();
        }
    }
   
    // 인보커
    static class RemoteControlWithUndo {
        Command[] onCommands;
        Command[] offCommands;
        Command undoCommand;
        public RemoteControlWithUndo() {
            onCommands = new Command[7];
            offCommands = new Command[7];
            Command noCommand = new NoCommand();
            for (int i = 0; i < offCommands.length; i++) {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
            undoCommand = noCommand;
        }
        public void setCommand(int slot, Command onCommand, Command offCommand) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
        public void onButtonWasPushed(int slot) {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
        public void offButtonWasPushed(int slot) {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }
        public void undoButtonWasPushed() {
            undoCommand.undo();
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("\n------ 리모컨 ------\n");
            for (int i = 0; i < offCommands.length; i++) {
                sb.append("[slot" + i + "]" + onCommands[i].getClass().getSimpleName());
                sb.append("    " + offCommands[i].getClass().getSimpleName() + "\n");
            }
            sb.append("[undo] " + undoCommand.getClass().getSimpleName() + "\n");
            return sb.toString();
        }
    }
    // 클라이언트, 커맨드 객체 생성, 인보커에게 커맨드 객체 전달
    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
        Light livingRoomLight = new Light("Living Room");
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        CeilingFan ceilingFan = new CeilingFan("Living Room");
        CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
        CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        remoteControl.onButtonWasPushed(1);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
    }
}
Living Room light is on
Living Room light is off
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] LightOffCommand
Living Room light is on
Living Room light is off
Living Room light is on
------ 리모컨 ------
[slot0]LightOnCommand LightOffCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] LightOnCommand
Living Room light is off
Living Room ceiling fan is on medium
Living Room ceiling fan is off
------ 리모컨 ------
[slot0]CeilingFanMediumCommand CeilingFanOffCommand
[slot1]CeilingFanHighCommand CeilingFanOffCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] CeilingFanOffCommand
Living Room ceiling fan is on medium
Living Room ceiling fan is on high
------ 리모컨 ------
[slot0]CeilingFanMediumCommand CeilingFanOffCommand
[slot1]CeilingFanHighCommand CeilingFanOffCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] CeilingFanHighCommand
Living Room ceiling fan is on medium

여러개 커맨드 한번에 실행

    static class MacroCommand implements Command{
        Command[] commands;
       
        public MacroCommand(Command[] commands) {
            this.commands = commands;
        }
        public void execute() {
            for(Command command : commands) {
                command.execute();
            }
        }
        public void undo() {
            for(Command command : commands) {
                command.undo();
            }
        }
    }




    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
        Light light = new Light("Living Room");
        TV tv = new TV("Living Room");
        Stereo stereo = new Stereo("Living Room");
        Hottub hottub = new Hottub();


        LightOnCommand lightOn = new LightOnCommand(light);
        StereoOnCommand stereoOn = new StereoOnCommand(stereo);
        TVOnCommand tvOn = new TVOnCommand(tv);
        HottubOnCommand hottubOn = new HottubOnCommand(hottub);
        LightOffCommand lightOff = new LightOffCommand(light);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        TVOffCommand tvOff = new TVOffCommand(tv);
        HottubOffCommand hottubOff = new HottubOffCommand(hottub);
        Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
        Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};


        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);


        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);


        System.out.println(remoteControl);
        System.out.println("--- Pushing Macro On---");
        remoteControl.onButtonWasPushed(0);
        System.out.println("--- Pushing Macro Off---");
        remoteControl.offButtonWasPushed(0);
    }
------ 리모컨 ------
[slot0]MacroCommand MacroCommand
[slot1]NoCommand NoCommand
[slot2]NoCommand NoCommand
[slot3]NoCommand NoCommand
[slot4]NoCommand NoCommand
[slot5]NoCommand NoCommand
[slot6]NoCommand NoCommand
[undo] NoCommand
--- Pushing Macro On---
Living Room light is on
Living Room stereo is on
TV is on
Channel is set for VCR
Hottub is heating to a steaming 105 degrees
Hottub is bubbling!
--- Pushing Macro Off---
Living Room light is off
Living Room stereo is off
TV is off
Hottub is cooling to 98 degrees

커맨드 객체 배열을 한 번더 커맨드 객체로 캡슐화하여한 번에 요청으로 일련의 행동을 수행 할 수 있다.

 

핵심요약

커맨드 패턴을 사용하면 요청하는 객체와 요청을 수행하는 객체를 분리할 수 있다.

핵심은 커맨드 객체이며, 커맨드 객체는 실제 행동을 수행하는 리시버를 캡슐화한다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

싱글턴 패턴은 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해 주는 패턴

 

어플리케이션에서 하나만 있어도 잘 돌아가는 객체들이 있다. 스레드 풀, 캐시, 설정, 로그 등 객체들

이런 객체들은 오히려 2개 이상이면 오동작할 수 있다.

 

고전적인 싱글턴 패턴 구현법

이 방식을 절대로 쓰면 안된다. 

public class ChocolateBoilerTest {
	//고전적인 싱글턴, 문제를 멀티스레딩 시 발생
	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//static
		private static ChocolateBoiler boiler;
		//생성자 private 이 클래스에서만 접근가능
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		//static 
		public static ChocolateBoiler getInstance() {
			//이렇게 객체를 필요할 때 생성하는 방법을 지연로딩이라 한다.
			if(boiler == null) {
				boiler = new ChocolateBoiler();
			}
			return boiler;
		}
		public void fill() {
			if(isEmpty()) {
				empty = false;
				boiled = false;
			}
		}
		public void drain() {
			if(!isEmpty()&& isBoiled()) {
				empty = true;
			}
		}
		public void boil() {
			if(!isEmpty()&& !isBoiled()) {
				boiled = true;
			}
		}
		public boolean isEmpty() {
			return empty;
		}
		public boolean isBoiled() {
			return boiled;
		}
	}
}

싱글턴 패턴의 정의

싱글턴 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

 

인스턴스는 그 클래스에서 관리하면 된다. 

인스턴스는 어디서는 접근할 수 있도록 전역 접근 지점을 제공한다.

지연 로딩 구현 방식은 비용이 큰 객체에 적용하면 유용하다.

 

멀티쓰레드 문제

객체가 여러 개 생길 수있다.

 

멀티쓰레딩 문제 해결하기

synchronized 키워드

		//가장 간단한 방법, 성능 저하 약 100배, 이유는 매번 호출 마다 동기화하기 때문
		public static synchronized ChocolateBoiler getInstance() {
			if(boiler == null) {
				boiler = new ChocolateBoiler();
			}
			return boiler;
		}

synchronized  키워드를 추가하면 한 스레드가 메서드 사용을 끝내기 전까지 다른 쓰레드는 기다려야 한다.

 

이 방법에 문제는 객체 생성 처음에만 유효할 뿐 이후 호출에는 전혀 쓸모없는 오버헤드를 유발한다.

 효율적으로 멀티스레딩 문제 해결하기

현재 이 방법이 부하가 크지 않다면, 그대로 사용해도 무방하다.

 

처음부터 초기화

	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//시작시 바로JVM이 정적 초기화
		//단점으로 지연로딩을 원한다면 불가능하다.
		private static ChocolateBoiler boiler = new ChocolateBoiler();

DCL(Double-Checked Locking) 사용

처음에만 동기화하고 이후 동기화 안하게 된다.

	static class ChocolateBoiler{
		private boolean empty;
		private boolean boiled;
		//volatile, 객체 생성 시 CPU 캐시되는 것을 방지하기 위함
		private static volatile ChocolateBoiler boiler;
		
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		
		//DCL 이중확인 락 방법, 맨 처음에만 동기화하고 이 후 동기화 안함.
		public static ChocolateBoiler getInstance() {
			if(boiler == null) {
				synchronized (ChocolateBoiler.class) {
					if(boiler == null) {
						boiler = new ChocolateBoiler();
					}
				}
			}
			return boiler;
		}

이 방법은 JDK1.4 이전 버전에선 정상동작을 보장못한다. volatile 키워드를 써도 동기화가 제대로 안 된다.

 

궁극의 해결법 enum

enum을 사용하면, 동기화 문제, 클래스 로딩 문제, 리플렉션, (역)직렬화 문제 등 모든 문제가 해결된다.

public class ChocolateBoilerTest5 {
	static enum ChocolateBoiler{
		BOILER;
		private boolean empty;
		private boolean boiled;
		
		private ChocolateBoiler() {
			empty = true;
			boiled = false;
		}
		
		public void fill() {
			if(isEmpty()) {
				empty = false;
				boiled = false;
			}
		}
		public void drain() {
			if(!isEmpty()&& isBoiled()) {
				empty = true;
			}
		}
		public void boil() {
			if(!isEmpty()&& !isBoiled()) {
				boiled = true;
			}
		}
		public boolean isEmpty() {
			return empty;
		}
		public boolean isBoiled() {
			return boiled;
		}
	}
}

핵심 정리

  • 싱글턴 패턴을 적용하면, 그 클래스 객체는 한 개만 존재한다.
  • 객체는 어디서든 접근하도록 해야한다.
  • 생성자는 private
  • 클래스 로더가 여러 개 있으면 여러 개 인스턴스가 생길 수 있어 주의해야 한다
  • enum 지원하기 시작한 jdk 1.5 이후 부턴 싱글턴 생성 시 enum을 쓰면 된다.

source.zip
0.00MB

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

객체 인스턴스를 만드는 작업이 항상 공개되어야 하는 것은 아니며, 오히려 모든 것을 공개했다가는 결합 문제가 생길 있다는 사실을 배운다

'new'연산자를 사용한다면 '구상' 떠올려라.

객체 생성 시점에는 구상 클래스에 의존할 밖에 없다.

객체 생성은 추상적인(미완성인) 클래스는 인스턴스화를 없기 때문이다.

 

팩토리메서드

하나의 타입으로 다룰 있다지만, 어떤 구상 클래스로 인스턴스화 할지는 제각각이다.

 

코드를 보면, 런타임 시점에 값을 받아 그에 맞는 구상 클래스 객체를 생성한다.

이런 코드는 변경하거나 확장할 코드를 다시 확인하고 새로운 코드를 추가하거나 기존 코드를 제거해야 한다.

, 코드가 변경에 취약하고, 찾은 변경은 버그의 확률을 높힌다.

 

new 자체는 자바에 필수 연산자다. 당연히 안쓸 없다.

사실 진짜 문제는 "변화"

변화하는 무엇가 때문에 new 조심해서 사용해야 한다.

 

인터페이스에 맞춰서 코딩하는 것은 변화에 다형성으로 대응하기 위함이다. new 연산자는 다형성 대상이 못된다. 무조건 구상 클래스에 의존할 수 밖에 없다. 변경에 닫힌 코드가 된다.

 

팩토리메서드

위 처럼 자주 변하는 부분을 캡슐화해 변하는 부분을 특정했다.

 

객체 생성 부분 캡슐화하기

객체를 생성하는 코드 부분만 뽑아내 객체 생성만을 담당하는 별도 클래스를 만든다.

 

 

객체 생성을 처리하는 클래스를 팩토리라고 부른다

이제 새로운 객체를 직접 만들지 않고 팩토리 메서드를 호출해 처리한다.

이상 어떤 구상 클래스를 인스턴스화 할지 고민하지 않아도 된다.

 

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

public class SimpleFactoryTest {
	static abstract class Pizza{
		String name;
		String dough;
		String sauce;
		List<String> toppings = new ArrayList<>();
		
		public String getName() {
			return name;
		}
		
		public void prepare() {	System.out.println("준비중 ... " + name);	}
		public void bake() {System.out.println("굽는 중 ... "+ name);	}
		public void cut() {	System.out.println("짜르는 중 ... " + name);	}
		public void box() {	System.out.println("포장 중 ... "+ name);	}
		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("---- " +name+" ----\n")
				.append(dough+"\n")
				.append(sauce+"\n");
			toppings.forEach(topping -> sb.append(topping+"\n"));
			return sb.toString();
		}
	}
	
	static class PepperoniPizza extends Pizza {
		public PepperoniPizza() {
			name = "페퍼로니 피자";
			dough = "크러스트";
			sauce = "마리나라 소스";
			toppings.add("슬라이스 페퍼로니");
			toppings.add("슬라이스 양파");
			toppings.add("모짜렐라 치즈");
		}
	}
	static class ClamPizza extends Pizza {
		public ClamPizza() {
			name = "조개 피자";
			dough = "씬 크러스트";
			sauce = "마늘 소스";
			toppings.add("조개");
			toppings.add("모짜렐라 치즈");
		}
	}
	static class VeggiePizza extends Pizza {
		public VeggiePizza() {
			name = "야채 피자";
			dough = "크러스트";
			sauce = "마리나라 소스";
			toppings.add("슈레드 모짜랄라");
			toppings.add("파마산");
			toppings.add("다신 양파");
			toppings.add("슬라이스 버섯");
			toppings.add("슬라이스 피망");
			toppings.add("슬라이스 블랙 올리브");
		}
	}
	static class CheesePizza extends Pizza {
		public CheesePizza() {
			name = "피즈 피자";
			dough = "크러스트";
			sauce = "마리나라 피자 소스";
			toppings.add("생 모짜렐라");
			toppings.add("파마산");
		}
	}
	
	static class SimplePizzaFactory {
		public Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new CheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new PepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new ClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new VeggiePizza();
			}
			return pizza;
		}
	}
	
	static class PizzaStore{
		SimplePizzaFactory pizzaFactory;

		public PizzaStore(SimplePizzaFactory pizzaFactory) {
			this.pizzaFactory = pizzaFactory;
		}
		
		public Pizza orderPizza(String type) {
			Pizza pizza;
			pizza = pizzaFactory.createPizza(type);
			
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			
			return pizza;
		}
		
	}
	public static void main(String[] args) {
		SimplePizzaFactory factory = new SimplePizzaFactory();
		PizzaStore store = new PizzaStore(factory);

		Pizza pizza = store.orderPizza("cheese");
		System.out.println("주문하신 피자 " + pizza.getName() + "\n");
		System.out.println(pizza);
 
		pizza = store.orderPizza("veggie");
		System.out.println("주문하신 피자 " + pizza.getName() + "\n");
		System.out.println(pizza);
	}
	
}
준비중 ... 피즈 피자
굽는 중 ... 피즈 피자
짜르는 중 ... 피즈 피자
포장 중 ... 피즈 피자
주문하신 피자 피즈 피자

---- 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

준비중 ... 야채 피자
굽는 중 ... 야채 피자
짜르는 중 ... 야채 피자
포장 중 ... 야채 피자
주문하신 피자 야채 피자

---- 야채 피자 ----
크러스트
마리나라 소스
슈레드 모짜랄라
파마산
다신 양파
슬라이스 버섯
슬라이스 피망
슬라이스 블랙 올리브

심플 팩토리를 정적 메소드로 정의할 수도 있다. 정적 팩토리라 불르고, 팩토리 인스턴스 생성 없이 바로 객체를 생성할 수 있다는 장점이 있지만, 서브 클래스를 만들어서 객체 생성 메소드의 행동을 변경할 수 없다는 단점이 있다.

 

'간단한 팩토리' 정의

심플 팩토리는 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다.

하지만, 자주 쓰이는 기법이다.

패턴이 아니라고 해서 중요하지 않은 것은 아니다.

 

 

다양한 팩토리 만들기

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

public class FactoryMethodTest {
	static abstract class Pizza{
		String name;
		String dough;
		String sauce;
		List<String> toppings = new ArrayList<>();
		
		public String getName() {	return name;	}
		
		public void prepare() {
			System.out.println("준비중 ... " + name);
			System.out.println("도우를 돌리는 중 ...");
			System.out.println("소스를 뿌리는 중 ...");
			System.out.println("토핑을 올리는 중 ...");

			toppings.forEach(topping->System.out.println(" "+topping));

		}
		public void bake() {System.out.println("175도에서 25분 간 굽기 ... ");	}
		public void cut() {	System.out.println("피자를 사선으로 짜르기 ... ");}
		public void box() {	System.out.println("피자 상자에 담기 ... ");	}
		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("---- " +name+" ----\n")
				.append(dough+"\n")
				.append(sauce+"\n");
			toppings.forEach(topping -> sb.append(topping+"\n"));
			return sb.toString();
		}
	}
	
	//추상 팩터리
	abstract static class PizzaStore{
		public Pizza orderPizza(String type) {
			Pizza pizza;
			pizza = createPizza(type);
			
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			
			return pizza;
		}
		//객체 생성을 서브클래스로 위임
		protected abstract Pizza createPizza(String type);
	}
	
	static class NYPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new NYStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new NYStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new NYStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new NYStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class NYStylePepperoniPizza extends Pizza {
			public NYStylePepperoniPizza() {
				name = "NYStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class NYStyleClamPizza extends Pizza {
			public NYStyleClamPizza() {
				name = "NYStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class NYStyleVeggiePizza extends Pizza {
			public NYStyleVeggiePizza() {
				name = "NYStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class NYStyleCheesePizza extends Pizza {
			public NYStyleCheesePizza() {
				name = "NYStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	static class ChicagoPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new ChicagoStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new ChicagoStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new ChicagoStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new ChicagoStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class ChicagoStylePepperoniPizza extends Pizza {
			public ChicagoStylePepperoniPizza() {
				name = "ChicagoStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
			@Override
			public void cut() {
				System.out.println("네모난 모양으로 피자 자르기");
			}
		}
		static class ChicagoStyleClamPizza extends Pizza {
			public ChicagoStyleClamPizza() {
				name = "ChicagoStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class ChicagoStyleVeggiePizza extends Pizza {
			public ChicagoStyleVeggiePizza() {
				name = "ChicagoStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class ChicagoStyleCheesePizza extends Pizza {
			public ChicagoStyleCheesePizza() {
				name = "ChicagoStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	static class CaliforniaPizzaStore extends PizzaStore{
		@Override
		protected Pizza createPizza(String type) {
			Pizza pizza = null;
			if (type.equals("cheese")) {
				pizza = new CaliforniaStyleCheesePizza();
			} else if (type.equals("pepperoni")) {
				pizza = new CaliforniaStylePepperoniPizza();
			} else if (type.equals("clam")) {
				pizza = new CaliforniaStyleClamPizza();
			} else if (type.equals("veggie")) {
				pizza = new CaliforniaStyleVeggiePizza();
			}
			return pizza;
		}
		
		static class CaliforniaStylePepperoniPizza extends Pizza {
			public CaliforniaStylePepperoniPizza() {
				name = "CaliforniaStyle 페퍼로니 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슬라이스 페퍼로니");
				toppings.add("슬라이스 양파");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class CaliforniaStyleClamPizza extends Pizza {
			public CaliforniaStyleClamPizza() {
				name = "CaliforniaStyle 조개 피자";
				dough = "씬 크러스트";
				sauce = "마늘 소스";
				toppings.add("조개");
				toppings.add("모짜렐라 치즈");
			}
		}
		static class CaliforniaStyleVeggiePizza extends Pizza {
			public CaliforniaStyleVeggiePizza() {
				name = "CaliforniaStyle 야채 피자";
				dough = "크러스트";
				sauce = "마리나라 소스";
				toppings.add("슈레드 모짜랄라");
				toppings.add("파마산");
				toppings.add("다신 양파");
				toppings.add("슬라이스 버섯");
				toppings.add("슬라이스 피망");
				toppings.add("슬라이스 블랙 올리브");
			}
		}
		static class CaliforniaStyleCheesePizza extends Pizza {
			public CaliforniaStyleCheesePizza() {
				name = "CaliforniaStyle 피즈 피자";
				dough = "크러스트";
				sauce = "마리나라 피자 소스";
				toppings.add("생 모짜렐라");
				toppings.add("파마산");
			}
		}
	}
	
	public static void main(String[] args) {
		//추상화 타입
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
		System.out.println(orderPizza(nyStore,"cheese"));
		System.out.println(orderPizza(chicagoPizzaStore,"cheese"));
	}
	
	//추상화된 타입으로 다루기
	static Pizza orderPizza(PizzaStore pizzaStore, String type) {
		return pizzaStore.orderPizza(type);
	}
}
준비중 ... NYStyle 피즈 피자
도우를 돌리는 중 ...
소스를 뿌리는 중 ...
토핑을 올리는 중 ...
 생 모짜렐라
 파마산
175도에서 25분 간 굽기 ... 
피자를 사선으로 짜르기 ... 
피자 상자에 담기 ... 
---- NYStyle 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

준비중 ... ChicagoStyle 피즈 피자
도우를 돌리는 중 ...
소스를 뿌리는 중 ...
토핑을 올리는 중 ...
 생 모짜렐라
 파마산
175도에서 25분 간 굽기 ... 
피자를 사선으로 짜르기 ... 
피자 상자에 담기 ... 
---- ChicagoStyle 피즈 피자 ----
크러스트
마리나라 피자 소스
생 모짜렐라
파마산

 

팩토리를 추상화하여 추상화 타입으로 만들고 객체 생성 부분을 서브클래스로 위임했다.

팩토리 메서드 패턴의 정의

객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴를 만들지는 서브클래스에서 결정한다.

 

모든 팩토리 패턴은 객체 생성을 캡슐화한다

팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화한다.

 

의존성 뒤집기 원칙

DIP(Dependency Inversion Principle)

디자인 원칙
추상화된 것에 의존하게 만들고 구상 클래스에 의전하지 않게 만든다.

 

의존성 뒤집기 원칙에서는 추상화를 더 강조

고수준 구성 요소가 저수준 구성 요소에 의존하면 안 되며, 항상 추상화에 의존하게 만들어야 한다.

의존성 뒤집기

 

의존성 뒤집기 원칙을 지키는 방법

  • 변수에 구성 클래스의레퍼런스를 저장하지 말기
    new 연산자로 구상클래스에 의존하지 말고, 팩토리를 사용
  • 구상 클래스에서 유도된 클래스를 만들지 말기
    Interface나 abstract class 처럼 추상화된 것으로부터 클래스를 만들어야 한다
  • 베이스 클래스에 이미 구현된 메서드 오버라이드하지 말기
    재정의 시 완전한 추상화가 안된다. 정말 공통적인 것만 베이스 클래스에 있어야한다.

실전에선 위 원칙을 완벽히 준수하기는 힘들다. 하지만, 알고 요령을 피우는 것과 모르고 지키기 않는 것은 차이가 있다.

 

 

 

추상 팩토리 패턴

public class AbstractFactory {
	public static void main(String[] args) {
		PizzaStore nyStore = new NYPizzaStore();
		PizzaStore chicagoStore = new ChicagoPizzaStore();
 
		Pizza pizza = nyStore.orderPizza(PizzaStore.PizzaType.CHEESE);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.CHEESE);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.CLAM);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.CLAM);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.PEPPERONI);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.PEPPERONI);
		System.out.println("Joel 주문한  " + pizza + "\n");

		pizza = nyStore.orderPizza(PizzaStore.PizzaType.VEGGIE);
		System.out.println("Ethan 주문한  " + pizza + "\n");
 
		pizza = chicagoStore.orderPizza(PizzaStore.PizzaType.VEGGIE);
		System.out.println("Joel 주문한  " + pizza + "\n");
	}
	
	/*야채*/
	static interface Veggies{
		String toString();
	}
	static class BlackOlives implements Veggies {
		public String toString() {
			return "검정 올리브";
		}
	}
	static class Eggplant implements Veggies {
		public String toString() {
			return "가지";
		}
	}
	static class Garlic implements Veggies {
		public String toString() {
			return "마늘";
		}
	}
	static class Mushroom implements Veggies {
		public String toString() {
			return "버섯";
		}
	}
	static class Onion implements Veggies {
		public String toString() {
			return "양파";
		}
	}
	static class RedPepper implements Veggies {
		public String toString() {
			return "빨간 피망";
		}
	}
	static class Spinach implements Veggies {
		public String toString() {
			return "Spinach";
		}
	}
	/*치즈*/
	static interface Cheese {
		public String toString();
	}
	static class MozzarellaCheese implements Cheese {
		public String toString() {
			return "슈레드 모짜렐라 치즈";
		}
	}
	static class ParmesanCheese implements Cheese {
		public String toString() {
			return "슈레드 파마산 치즈";
		}
	}
	static class ReggianoCheese implements Cheese {
		public String toString() {
			return "레지아노 치즈";
		}
	}
	/*조개*/
	static interface Clam {
		public String toString();
	}
	static class FreshClams implements Clam {
		public String toString() {
			return "생물 조개";
		}
	}
	static class FrozenClams implements Clam {
		public String toString() {
			return "냉동 조개";
		}
	}
	/*도우*/
	static interface Dough {
		public String toString();
	}
	static class ThickCrustDough implements Dough {
		public String toString() {
			return "두툼하고 빠삭한 스타일 도우";
		}
	}
	static class ThinCrustDough implements Dough {
		public String toString() {
			return "얇고 빠삭한 도우";
		}
	}
	/*소스*/
	static interface Sauce {
		public String toString();
	}
	static class MarinaraSauce implements Sauce {
		public String toString() {
			return "마리나라 소스";
		}
	}
	static class PlumTomatoSauce implements Sauce {
		public String toString() {
			return "이태리 토마토 소스";
		}
	}
	/*페퍼로니*/
	static interface Pepperoni {
		public String toString();
	}
	static class SlicedPepperoni implements Pepperoni {
		public String toString() {
			return "얇게 썰은 페퍼로니";
		}
	}

	/* 핵심  추상 팩토리 정의*/
	static interface PizzaIngredientFactory {
		public Dough createDough();
		public Sauce createSauce();
		public Cheese createCheese();
		public Veggies[] createVeggies();
		public Pepperoni createPepperoni();
		public Clam createClam();
	}
	//시카고는 내륙이라 냉동 조개를 씀
	static class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
		public Dough createDough() {
			return new ThickCrustDough();
		}
		public Sauce createSauce() {
			return new PlumTomatoSauce();
		}
		public Cheese createCheese() {
			return new MozzarellaCheese();
		}
		public Veggies[] createVeggies() {
			return new Veggies[] { new BlackOlives(), new Spinach(), new Eggplant() };
		}
		public Pepperoni createPepperoni() {
			return new SlicedPepperoni();
		}
		public Clam createClam() {
			return new FrozenClams();
		}
	}
	//뉴욕은 연안이라 생물 조개 사용
	static class NYPizzaIngredientFactory implements PizzaIngredientFactory {
		public Dough createDough() {
			return new ThinCrustDough();
		}
		public Sauce createSauce() {
			return new MarinaraSauce();
		}
		public Cheese createCheese() {
			return new ReggianoCheese();
		}
		public Veggies[] createVeggies() {
			return new Veggies[] { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
		}
		public Pepperoni createPepperoni() {
			return new SlicedPepperoni();
		}
		public Clam createClam() {
			return new FreshClams();
		}
	}
	
	/*피자 군*/
	abstract static class Pizza{
		//팩토리를 사용하여 거의 동일하나 다른 스타일의 피자를 생산할 수 있다.
		PizzaIngredientFactory ingredientFactory;
		String name;
		
		Dough dough;
		Sauce sauce;
		Veggies[] veggies;
		Cheese cheese;
		Pepperoni pepperoni;
		Clam clam;
		
		abstract void prepare();
		
		void bake() {	System.out.println("25분 간 굽습니다.");	}
		void cut() {System.out.println("대각선으로 피자를 썰어줍니다.");}
		void box() {System.out.println("피자박스에 피자를 옯겨 포장합니다.");}
		
		void setName(String name) {
			this.name = name;
		}
		String getName() {
			return name;
		}
		public String toString() {
			StringBuilder result = new StringBuilder();
			result.append("---- " + name + " ----\n");
			if (dough != null) {
				result.append(dough);
				result.append("\n");
			}
			if (sauce != null) {
				result.append(sauce);
				result.append("\n");
			}
			if (cheese != null) {
				result.append(cheese);
				result.append("\n");
			}
			if (veggies != null) {
				for(Veggies veggie : veggies) {
					result.append(veggie+", ");
				}
				result.delete(result.length()-2, result.length());
				result.append("\n");
			}
			if (clam != null) {
				result.append(clam);
				result.append("\n");
			}
			if (pepperoni != null) {
				result.append(pepperoni);
				result.append("\n");
			}
			return result.toString();
		}
	}
	
	static class CheesePizza extends Pizza {
	 
		public CheesePizza(PizzaIngredientFactory ingredientFactory) {
			super.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
		}
	}
	static class ClamPizza extends Pizza {
		PizzaIngredientFactory ingredientFactory;
	 
		public ClamPizza(PizzaIngredientFactory ingredientFactory) {
			this.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			clam = ingredientFactory.createClam();
		}
	}
	static class PepperoniPizza extends Pizza {
		PizzaIngredientFactory ingredientFactory;
	 
		public PepperoniPizza(PizzaIngredientFactory ingredientFactory) {
			this.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			veggies = ingredientFactory.createVeggies();
			pepperoni = ingredientFactory.createPepperoni();
		}
	}
	static class VeggiePizza extends Pizza {
	 
		public VeggiePizza(PizzaIngredientFactory ingredientFactory) {
			super.ingredientFactory = ingredientFactory;
		}
	 
		void prepare() {
			System.out.println("준비중..." + name);
			dough = ingredientFactory.createDough();
			sauce = ingredientFactory.createSauce();
			cheese = ingredientFactory.createCheese();
			veggies = ingredientFactory.createVeggies();
		}
	}

	
	
	
	public abstract static class PizzaStore {
		//타입 안정성을 위해 인자를 enum으로 받기
		public enum PizzaType{
			CHEESE, VEGGIE, CLAM, PEPPERONI
		}
		//팩토리 메서드 패턴, 객체 생성을 서브타입에 맡긴다.
		protected abstract Pizza createPizza(PizzaType item);
		
		public Pizza orderPizza(PizzaType type) {
			Pizza pizza = createPizza(type);
			System.out.println("--- Making a " + pizza.getName() + " ---");
			pizza.prepare();
			pizza.bake();
			pizza.cut();
			pizza.box();
			return pizza;
		}
	}
	static class ChicagoPizzaStore extends PizzaStore {
		protected Pizza createPizza(PizzaType item) {
			Pizza pizza = null;
			PizzaIngredientFactory ingredientFactory =
			new ChicagoPizzaIngredientFactory();//팩터리
			
			switch (item) {
			case CHEESE:
				pizza = new CheesePizza(ingredientFactory);
				pizza.setName("시카고 스타일 치즈 피자");
				break;
			case VEGGIE:
				pizza = new VeggiePizza(ingredientFactory);
				pizza.setName("시카고 스타일 야채 피자");
				break;
			case CLAM:
				pizza = new ClamPizza(ingredientFactory);
				pizza.setName("시카고 스타일 조개 피자");
				break;
			case PEPPERONI:
				pizza = new PepperoniPizza(ingredientFactory);
				pizza.setName("시카고 스타일 페퍼로니 피자");
				break;
			default:
				throw new IllegalArgumentException();
			}
			return pizza;
		}

	}
	static class NYPizzaStore extends PizzaStore {
		 
		protected Pizza createPizza(PizzaType item) {
			Pizza pizza = null;
			PizzaIngredientFactory ingredientFactory =
			new ChicagoPizzaIngredientFactory();//팩터리
			
			switch (item) {
			case CHEESE:
				pizza = new CheesePizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 치즈 피자");
				break;
			case VEGGIE:
				pizza = new VeggiePizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 야채 피자");
				break;
			case CLAM:
				pizza = new ClamPizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 조개 피자");
				break;
			case PEPPERONI:
				pizza = new PepperoniPizza(ingredientFactory);
				pizza.setName("뉴욕 스타일 페퍼로니 피자");
				break;
			default:
				throw new IllegalArgumentException();
			}
			return pizza;
		}
	}
}
--- Making a 뉴욕 스타일 치즈 피자 ---
준비중...뉴욕 스타일 치즈 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 치즈 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈


--- Making a 시카고 스타일 치즈 피자 ---
준비중...시카고 스타일 치즈 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 치즈 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈


--- Making a 뉴욕 스타일 조개 피자 ---
준비중...뉴욕 스타일 조개 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 조개 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
냉동 조개


--- Making a 시카고 스타일 조개 피자 ---
준비중...시카고 스타일 조개 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 조개 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
냉동 조개


--- Making a 뉴욕 스타일 페퍼로니 피자 ---
준비중...뉴욕 스타일 페퍼로니 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 페퍼로니 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지
얇게 썰은 페퍼로니


--- Making a 시카고 스타일 페퍼로니 피자 ---
준비중...시카고 스타일 페퍼로니 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 페퍼로니 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지
얇게 썰은 페퍼로니


--- Making a 뉴욕 스타일 야채 피자 ---
준비중...뉴욕 스타일 야채 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Ethan 주문한  ---- 뉴욕 스타일 야채 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지


--- Making a 시카고 스타일 야채 피자 ---
준비중...시카고 스타일 야채 피자
25분 간 굽습니다.
대각선으로 피자를 썰어줍니다.
피자박스에 피자를 옯겨 포장합니다.
Joel 주문한  ---- 시카고 스타일 야채 피자 ----
두툼하고 빠삭한 스타일 도우
이태리 토마토 소스
슈레드 모짜렐라 치즈
검정 올리브, Spinach, 가지

추상 팩토리 패턴은 구상 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생상하는 인터페이스를 제공한다. 구상 클래스는 서브 클래스에서 만든다.

 

 

 

추상 팩토리 패턴과 팩토리 메소드 패턴 비교

팩토리 메소드는 상속으로 객체를 만든다. 

추상 팩토리는 객체(팩토리) 구성으로 객체를 만든다.

 

팩토리 메소드는 클라이언트와 구상 형식을 분리하는 역할이 목적

추상 팩토리는 일련 연관된 클래스(추상 또는 인터페이스) 군을 하나로 묶은 형식을 제공한다. 다만, 클래스 추가 시 모든 구상 팩토리가 영향을 받는다.

 

핵심 정리

팩토리를 쓰면 객체 생성을 캡슐화할 수 있다.

심플 팩토리는 패턴이 아니라 기법이다.

팩토리 메소드 패턴은 상속으로 객체 생성을 서브 클래스에게 위임한다.

추상 팩토리 패턴은 객체 구성을 활용해 팩토리 인터페이스에 선언한 메소드에서 객체 군을 생성한다.

모든 팩토리는 구상 클래스 의존성을 줄여 느슨한 결합을 도와준다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

기존코드를 바꾸지 않고 객체에 새로운 임무를 추가할 수 있다.

 

상속으로 기능을 추가하기 VS 구성(데코레이터)으로 기능 추가하기

상속으로 서브 클래스에 기능을 구현하려면, 모든 서브 클래스가 오버라이드 해야 한다. 따라서 기존 코드를 수정할 수 밖에 없다.

클래스가 많아지면 일부 서브 클래스에는 적합하지 않은 불필요한 기능까지 상속받는다.

상속으로 기능 추가는 컴파일 시점에 완전히 결정된다. 

구성으로 기능을 추가하면, 기존 코드를 건들지 않고, 기능을 추가할 수 있다. 

기존 코드를 건드리지 않으므로, 버그 발생 범위가 제한된다. 

구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.

 

자바는 단일 상속 언어로 일반적으로 구성이 옳다고 여겨진다.

 

OCP 살펴보기

디자인 원칙
클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.

풀어 말하면, 기존 코드를 수정하지 않으면서 기능 추가가 가능해야 한다는 것

이 점에서 데코레이터 패턴은 OCP를 완전히 준수한다.

원 코드는 건들지 않은채(클라이언트 입장에선 데코레이터 존재조차 모른다) 기능을 추가할 수 있다.

코드에서 확장해야 부분을 선택할 때는 세심한 주의를 기울여야 한다.

무조건  OCP 적용한다면 괜히 쓸데없는 일을 하며 시간을 낭비할 있다. 또한 필요 이상으로 복잡하고 이해하기 힘든 코드를 만들게 되는 부작용이 발생할 있다.

 

데코레이터 패턴 살펴보기

데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.

객체를 여러 개의 데코레이터로 감쌀 있다.

데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼 클래스를 가지고 있기에 원래 객체가 들어갈 자리에 데코레이터 객체를 넣어도 상관없다.

데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 말고도 추가 작업을 수행할 있다.

객체는 언제든지 감쌀 있으모로 실행 중에 필요한 데코레이터를 마음대로 적용할 있다.

 

데코레이터 패턴 예시

public class DecoratorTest {
	//Compenet
	static abstract class Beverage{
		String decription = "제목 없음";
		public String getDecription() {
			return decription;
		}
		public abstract double cost();
	}
	/**
	 * Decorator
	 * 형태만 (다형성) 맞추기 위해 상속(구현)하지만,
	 * 행동은 구성와 위임으로 런타임 시 동적으로 변경 가능하다.
	 */
	static abstract class CondimentDecorator extends Beverage{
		//다형성
		Beverage beverage;
		@Override
		public abstract String getDecription();
	}
	
	
	static class Espresso extends Beverage{
		public Espresso() {
			super.decription = "에스프레소";
		}
		@Override
		public double cost() {
			return 1.99;
		}
	}
	
	static class HouseBlend extends Beverage{
		public HouseBlend() {
			super.decription = "하우스 블렌드 커피";
		}
		@Override
		public double cost() {
			return .89;
		}
	}
	static class DarkRoast extends Beverage{
		public DarkRoast() {
			super.decription = "다크로스트 커피";
		}
		@Override
		public double cost() {
			return .99;
		}
	}
	static class Decaf extends Beverage{
		public Decaf() {
			super.decription = "디카페인 커피";
		}
		@Override
		public double cost() {
			return 1.05;
		}
	}
	
	static class Mocha extends CondimentDecorator{
		public Mocha(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 모카";
		}
		@Override
		public double cost() {
			return beverage.cost()+ .20;
		}
	}
	static class Milk extends CondimentDecorator{
		public Milk(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 우유";
		}
		@Override
		public double cost() {
			return beverage.cost()+.10;
		}
	}
	static class Whip extends CondimentDecorator{
		public Whip(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 휘핑크림";
		}
		@Override
		public double cost() {
			return beverage.cost()+.10;
		}
	}
	static class Soy extends CondimentDecorator{
		public Soy(Beverage beverage) {
			super.beverage = beverage;
		}
		@Override
		public String getDecription() {
			return beverage.getDecription() + ", 두유";
		}
		@Override
		public double cost() {
			return beverage.cost()+.15;
		}
	}
	
	public static void main(String[] args) {
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDecription()+" $"+beverage.cost());
		
		Beverage beverage2 = new DarkRoast();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDecription()+" $"+beverage2.cost());
		
		Beverage beverage3 = new HouseBlend();
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDecription()+" $"+beverage3.cost());
	}
}
에스프레소 $1.99
다크로스트 커피, 모카, 모카, 휘핑크림 $1.49
하우스 블렌드 커피, 두유, 모카, 휘핑크림 $1.34

java.io 클래스와 데코레이터 패턴

java.io 패키지는 데코레이터 패턴을 바탕으로 만들어졌다.

io 패키지에서 FilterInputStream, FilterOutputStream을 서브 클래스들이 데코레이터 패턴이 적용된 클래스이다.

따라서 FilterInputStream, FilterOutputStream 서브클래스들은 단속으로 사용할 수 없다.

문자열 기반 IO스트림은 FilterWriter, FilterReader 도  똑같다.

 

데코레이터의 단점은 사용 시 클래스가 많아진다. 그리고 중첩 사용 시 중첩 구조를 파악하기 힘든다.

 

자바 I/O 데코레이터 만들기

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
package designpattern.structural.decorator;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class IOStream {
	public static void main(String[] args) throws FileNotFoundException {
		
		try(InputStream in = 
				new UpperCaseInputStream(
				new BufferedInputStream(
                //자기에 맞는 프로젝트명 패키지 경로 파일 입력 필요
				new FileInputStream("src/designpattern/structural/decorator/text.txt")));)
		{
			StringBuilder sb = new StringBuilder();
			for(int c=0; (c=in.read()) != -1;) {
				sb.append((char)c);
			}
			System.out.println(sb);
			
		} catch (IOException e) {	}
	}
	
	static class UpperCaseInputStream extends FilterInputStream{

		public UpperCaseInputStream(InputStream in) {
			super(in);
		}
		@Override
		public int read() throws IOException {
			int data = in.read();
			return data == -1 ? data : Character.toUpperCase(data);
		}
	}
}

결과

LOREM IPSUM IS SIMPLY DUMMY TEXT OF THE PRINTING AND TYPESETTING INDUSTRY. LOREM IPSUM HAS BEEN THE INDUSTRY'S STANDARD DUMMY TEXT EVER SINCE THE 1500S, WHEN AN UNKNOWN PRINTER TOOK A GALLEY OF TYPE AND SCRAMBLED IT TO MAKE A TYPE SPECIMEN BOOK. IT HAS SURVIVED NOT ONLY FIVE CENTURIES, BUT ALSO THE LEAP INTO ELECTRONIC TYPESETTING, REMAINING ESSENTIALLY UNCHANGED. IT WAS POPULARISED IN THE 1960S WITH THE RELEASE OF LETRASET SHEETS CONTAINING LOREM IPSUM PASSAGES, AND MORE RECENTLY WITH DESKTOP PUBLISHING SOFTWARE LIKE ALDUS PAGEMAKER INCLUDING VERSIONS OF LOREM IPSUM.

핵심 정리

디자인 유연성 면에선 상속보단 구성(과 위임)이 더 좋다. (OCP)

기존 코드 수정 없이 행동을 확장할 수 있다.

구성은 런타임 시점에 기능을 동적으로 변경할 수 있다.

인터페이스로 설계 시 클라이언트는 데코레이터 존재를 알 수 없다. 

데코레이터 패턴을 사용하면 코드가 다소 복잡해진다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

 

옵저버 패턴(Observer Pattern)

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 갱신되는 방식으로 일대다 의존성을 정의한다.

 

신문사와 잡지 관계와 유사하다.

구독자가 신문사에 구독 신청, 신간이 나올때마다 배달 받는다.

구독자가 해지 신청을 하면 이상 받지 않는다.

신문사가 Subject(주제)

구독자가 Observer(관찰자)

 

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

public class ObserverEx01 {
	static interface Subject<T>{
		//옵저버 등록
		void addObserver(Observer<T> observer);
		//옵저서 삭제
		void removeObserver(Observer<T> observer);
		//상태 변화 시 알림
		void notityObservers();
		
		T getData();
	}
	
	//핵심 관찰 대상1
	static class News<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T news;
		
		public T getNews() {
			return this.news;
		}
		public void setNews(T news) {
			this.news = news;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return news;
		}
		public String toString() {
			return "News 구독자 : "+observers.toString();
		}
	}
	//핵심 관찰 대상1
	static class Magazine<T> implements Subject<T>{
		//이 주제를 관찰할 옵저버 목록
		List<Observer<T>> observers = new ArrayList<>();
		//옵저버들의 핵심 관찰 데이터
		T magazine;
		
		public T getMagazine() {
			return this.magazine;
		}
		public void setMagazine(T magazine) {
			this.magazine = magazine;
			notityObservers();
		}
		public void addObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void removeObserver(Observer<T> observer) {
			observers.add(observer);
		}
		public void notityObservers() {
			for(Observer<T> observer : observers)
				observer.update(this);
		}
		public T getData() {
			return magazine;
		}
		public String toString() {
			return "Magazine 구독자 : "+observers.toString();
		}
	}
	
	static interface Observer<T>{
		//업데이터 받는 방식, Pull방식
		void update(Subject<T> subject);
	}
	static class Person<T> implements Observer<T>{
		String name;
		//갱신 받은 데이터 목록
		List<T> updateData = new ArrayList<>();
		
		public Person(String name) {
			super();
			this.name = name;
		}
		public void update(Subject<T> subject) {
			updateData.add(subject.getData());
		}
		@Override
		public String toString() {
			return name+" "+updateData.toString();
		}
	}
	//테스트
	public static void main(String[] args) {
		Observer<String> person1 = new Person<>("홍길동");
		Observer<String> person2 = new Person<>("임꺽정");
		Observer<String> person3 = new Person<>("김자바");
		
		//대한잡지
		Magazine<String> magazine = new Magazine<>();
		//민국뉴스
		News<String> news = new News<>();
		
		magazine.addObserver(person1);
		magazine.addObserver(person2);
		
		news.addObserver(person2);
		news.addObserver(person3);
		
		System.out.println(magazine);
		System.out.println(news);
		
		magazine.setMagazine("1월 호 잡지 출판");
		news.setNews("속보... 주저리주저리");
		
		magazine.setMagazine("2월 호 잡지 출판");
		news.setNews("오늘 날씨 정보");
		
		System.out.println(person1);
		System.out.println(person2);
		System.out.println(person3);
		
	}
}
Magazine 구독자 : [홍길동 [], 임꺽정 []]
News 구독자 : [임꺽정 [], 김자바 []]
홍길동 [1월 호 잡지 출판, 2월 호 잡지 출판]
임꺽정 [1월 호 잡지 출판, 속보... 주저리주저리, 2월 호 잡지 출판, 오늘 날씨 정보]
김자바 [속보... 주저리주저리, 오늘 날씨 정보]

Subject 인터페이스의 필수 메서드는 옵저버 등록/삭제, 갱신 이 3가지다.

Observer 인터페이스 구현은 나중에 Subject를 구독할 가능성이 있어도 구현하는 것이 좋다.

 

구조상 한개의 주제에 다수의 옵저버가 연결되어있다. 그래서 일대다 관계인 것

 

옵저버 패턴에서는 주제가 상태를 저장하고 제어한다. 반면에 옵저버는 상태를 사용하지만, 반드시 소유할 필요는 없다. 때문에 주제에서 상태가 바뀌었다는 사실을 알려주길 기다리는 의존적인 성질을 가지게 된다.

 

느슨한 결합의 위력

느슨한 결합은 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계를 말한다. 

인터페이스에 맞게 개발하는 것을 의미한다.

 

디자인 원칙
상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.

가상 스테이션 구현하기

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

public class WheatherStationTest {
	static interface Subject{
		void registerObserver(Observer o);//등록
		void removeObserver(Observer o);  //삭제
		void notifyObservers();// 알림
	}
	
	@FunctionalInterface
	static interface Observer{
		void update();
	}
	
	static interface DisplayElement{
		void display();
	}
	
	
	static class WeatherData implements Subject{
		private List<Observer> observers;
		private float temperature;
		private float humidity;
		private float pressure;

		public WeatherData() {
			observers = new ArrayList<>();
		}
		public void registerObserver(Observer o) {
			observers.add(o);
		}
		public void removeObserver(Observer o) {
			observers.remove(o);
		}
		public void notifyObservers() {
			observers.forEach((ob)->ob.update());
		}
		public void measurementsChanged() {
			notifyObservers();
		}
		public void setMeasurements(float temperature, float humidity, float pressure) {
			this.temperature = temperature;
			this.humidity = humidity;
			this.pressure = pressure;
			measurementsChanged();
		}
		public float getTemperature() {
			return temperature;
		}
		public float getHumidity() {
			return humidity;
		}
		public float getPressure() {
			return pressure;
		}
	}
	
	static class CurrentConditionsDisplay implements Observer, DisplayElement{
		private float temperature;
		private float humidity;
		private WeatherData weatherData;

		public CurrentConditionsDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		@Override
		public void update() {
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}

		@Override
		public void display() {
			System.out.println("현재 상태: 온도 " +temperature + "F, 습도 "+humidity + "%");
		}
		
	}
	static class StatisticsDisplay implements Observer, DisplayElement {
		private float maxTemp = 0.0f;
		private float minTemp = 200;
		private float tempSum= 0.0f;
		private int numReadings;
		private WeatherData weatherData;

		public StatisticsDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		public void update() {
			float temp = weatherData.getTemperature();
			tempSum += temp;
			numReadings++;

			if (temp > maxTemp) {
				maxTemp = temp;
			}
	 
			if (temp < minTemp) {
				minTemp = temp;
			}

			display();
		}

		public void display() {
			System.out.println("Avg/Max/Min 온도 = " + (tempSum / numReadings)
				+ "/" + maxTemp + "/" + minTemp);
		}
	}
	static class ForecastDisplay implements Observer, DisplayElement {
		private float currentPressure = 29.92f;  
		private float lastPressure;
		private WeatherData weatherData;

		public ForecastDisplay(WeatherData weatherData) {
			this.weatherData = weatherData;
			weatherData.registerObserver(this);
		}

		public void update() {
	        lastPressure = currentPressure;
			currentPressure = weatherData.getPressure();

			display();
		}

		public void display() {
			System.out.print("기상 예보: ");
			if (currentPressure > lastPressure) {
				System.out.println("날씨가 좋아지고 있습니다.");
			} else if (currentPressure == lastPressure) {
				System.out.println("지금과 비슷합니다.");
			} else if (currentPressure < lastPressure) {
				System.out.println("쌀쌀하며 비가 올 것 같습니다.");
			}
		}
	}
	
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
		CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
		
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

 

현재 상태: 온도 80.0F, 습도 65.0%
Avg/Max/Min 온도 = 80.0/80.0/80.0
기상 예보: 날씨가 좋아지고 있습니다.
현재 상태: 온도 82.0F, 습도 70.0%
Avg/Max/Min 온도 = 81.0/82.0/80.0
기상 예보: 쌀쌀하며 비가 올 것 같습니다.
현재 상태: 온도 78.0F, 습도 90.0%
Avg/Max/Min 온도 = 80.0/82.0/78.0
기상 예보: 지금과 비슷합니다.

 

 

 

 

핵심 정리

옵저버 패턴에는 Push 방식과 Pull 방식이 있다. 일반적으로 Pull방식이 더 좋다고 여겨진다.

옵저버 인터페이스만 구현하면 어떤 클래스도 옵저버 패턴에 참여할 수 있다.

 

정의

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

 

코드

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

public class PrototypeEx {

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

		public Shape() {}

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

		public abstract Shape clone();

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

	static class Circle extends Shape {
		public int radius;

		public Circle() {
		}

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

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

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

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

		public Rectangle() {
		}

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

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

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

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

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

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

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

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

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

		cloneAndCompare(shapes, shapesCopy);
	}

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

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

결과

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

 

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

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

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

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

 

정의

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

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

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

 

코드

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

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

결과

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

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

정의

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

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

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

 

 

코드

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

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

결과

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

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

 

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

 

 

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

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

정의

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

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

 

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

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

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

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

 

빌더 적용

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

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

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

디렉터

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

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

builder.zip
0.00MB

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

 

코드

import java.util.ArrayList;
import java.util.Collections;

/**
 * 벽돌은 가장 넓은 것이 아래에 위치
 * 그리고 가장 무거운 것일 수록 아래 위치
 */
public class 가장높은탑쌓기{

	static class Brick implements Comparable<Brick>{
		public final int width, heigth, weight;
		public Brick(int width, int heigth, int weight) {
			this.width = width;
			this.heigth = heigth;
			this.weight = weight;
		}
		public int compareTo(Brick o){
			return o.width-this.width;
		}
	}
	
	static int[] dy;
	static ArrayList<ArrayList<Brick>> list;
	
	public static void main(String[] args){
		int[][] input = 
			{
					{25, 3, 4 }
					,{4 ,4 ,6  }
					,{9 ,2 ,3  }
					,{16, 2, 5 }
					,{1 ,5 ,2  }
			};
		int n=input.length;
		
		ArrayList<Brick> arr=new ArrayList<>();
		
		dy=new int[n];
		for(int i=0; i<n; i++){
			int a= input[i][0] ;
			int b= input[i][1] ;
			int c= input[i][2] ;
			arr.add(new Brick(a, b, c));
		}
		System.out.print(solution(arr));
	}
	
	static int solution(ArrayList<Brick> arr){
		//너비 순으로 내림차순
		Collections.sort(arr);
		dy[0]=arr.get(0).heigth;
		int answer= dy[0];
		
		//높이 비교 1부터
		for(int i=1; i<arr.size(); i++){
			//높이 tmp
			int max_h=0;
			//이전 벽돌 비교
			for(int j=i-1; j>=0; j--){
				//무게는 j가 커야한다. 그리고 그 중에 제일 높은 값을 구한다.
				if(arr.get(j).weight > arr.get(i).weight && dy[j]>max_h){
					max_h=dy[j];
				}
			}
			//제일 높은 값을 구했으니, 그 값에 나의 높이를 더 한다.
			dy[i]=max_h+arr.get(i).heigth;
			//지금까지 높이 중 가장 큰 높이를 구한다.
			answer=Math.max(answer, dy[i]);
		}
		return answer;
	}
}

결과

10

이전 최대부분증가수열과 거의 같다. 

일부 특수화가 됐을 뿐이다.

 

'자료구조&알고리즘 > 자바(Java) 알고리즘 문제풀이 : 코딩테스트 대비' 카테고리의 다른 글

최대부분증가 수열  (0) 2023.04.27
돌다리 건너기  (0) 2023.04.25
계단오르기  (0) 2023.04.24
다익스트라 알고리즘  (0) 2023.04.22
씨름선수  (0) 2023.04.07

+ Recent posts