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

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

ebook-product.kyobobook.co.kr

어댑터 생각해보기

일상 생활에서 어댑터란 무언가를 호환되도록 도와준다.

한국의 220V 콘센트를 일본의 110V 콘센트와 호환되게 해주는 어댑터가 그 예다.

 

코드에서도 어댑터 패턴은 이 역할을 한다.

기존 시스템과 업체에서 제공한 코드가 있다고 치자. 이 사이를 어댑터 코드로 연결지어주면, 두 코드는 코드 변경이 없다.

어댑터 사용 방법 알아보기

package designpattern.headfirst.chapter7;

public class AdapterTest {
	//기존 코드
	static interface Duck{
		void quack();
		void fly();
	}
	static class MallardDuck implements Duck{
		@Override
		public void quack() {
			System.out.println("꽥!");
		}
		@Override
		public void fly() {
			System.out.println("날고 있어요!!");
		}
	}
	
	//새로 제공된 코드
	static interface Turkey{
		void gobble();
		void fly();
	}
	static class WildTurkey implements Turkey{
		@Override
		public void gobble() {
			System.out.println("골골");
		}
		@Override
		public void fly() {
			System.out.println("짧은 거리를 날고 있어요!");
		}
	}
	
	//어댑터
	static class TurkeyAdapter implements Duck{
		Turkey turkey;
		
		public TurkeyAdapter(Turkey turkey) {
			this.turkey = turkey;
		}

		public void quack() {
			turkey.gobble();
		}

		public void fly() {
			for (int i = 0; i < 5; i++) {
				turkey.fly();
			}
		}
	}
	static class DuckAdapter implements Turkey{
		Duck duck;
		public DuckAdapter(Duck duck) {
			this.duck = duck;
		}
		public void gobble() {
			duck.quack();
		}
		public void fly() {
			duck.fly();
		}
	}
	
	public static void main(String[] args) {
		Duck duck = new MallardDuck();
		Turkey turkey = new WildTurkey();
		Duck turkeyAdapter = new TurkeyAdapter(turkey);
		Turkey duckAdapter = new DuckAdapter(duck);
		
		System.out.println("칠면조---");
		turkey.gobble();
		turkey.fly();
		System.out.println("\n오리---");
		testDuck(duck);
		System.out.println("\n칠면조 어댑터---");
		testDuck(turkeyAdapter);
		System.out.println("\n오리 어댑터---");
		testTurkey(duckAdapter);
		testTurkey(turkey);
		
		
	}
	static void testTurkey(Turkey turkey){
		turkey.gobble();
		turkey.fly();
	}
	static void testDuck(Duck duck) {
		duck.quack();
		duck.fly();
	}
}
칠면조---
골골
짧은 거리를 날고 있어요!

오리---
꽥!
날고 있어요!!

칠면조 어댑터---
골골
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!
짧은 거리를 날고 있어요!

오리 어댑터---
꽥!
날고 있어요!!
골골
짧은 거리를 날고 있어요!

어댑터 패턴 알아보기

public class 어댑터패턴 {
	static interface Adaptee {
		void 적응이필요해();
	}
	static class AdapteeClass implements Adaptee{
		public void 적응이필요해() {
			System.out.println("적응이 필요한 코드 뭉치");
		}
	}
	//타겟 인터페이스
	static interface Origin{
		void 원본코드();
	}
	static class OriginClass implements Origin{
		public void 원본코드() {
			System.out.println("원본 코드");
		}
	}
	//핵심
	static class OriginAdapter implements Origin{
		Adaptee adaptee; // 어댑티 객체 인스턴스가 있다.
		
		public OriginAdapter(Adaptee adaptee) {
			this.adaptee = adaptee;
		}
		@Override
		public void 원본코드() {
			adaptee.적응이필요해();
		}
	}
	public static void main(String[] args) {
		Origin origin = new OriginClass();
		Adaptee adaptee = new AdapteeClass();
		Origin originAdapter = new OriginAdapter(adaptee);
		action(origin);
		action(originAdapter);
	}
	//기존에 클라이언트가 사용하던 원본 코드는 변경이 없다.
	static void action(Origin origin) {
		origin.원본코드();
	}
}
원본 코드
적응이 필요한 코드 뭉치

클라이언트는 타겟 인터페이스로 다루니 코드 변경이 없다.

어댑터는 반드시 적응 시켜야할 어댑티 메서드와 일대일 매칭되리란 보장이 없다.

클라이언트는 중간에 어댑터가 있다는 사실도 모른다.

 

어댑터 패턴의 정의

특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환한다.

호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용할 수 있다. 

객체 어댑터와 클래스 어댑터

자바의 경우 단일 상속 언어로 클래스 어댑터를 쓰지 않는다.

클래스 어댑터는 다중 상속이 가능한 언어에서 쓰일 수 있다.

 

객체 어댑터는 구성관계를 사용하므로 어떤 언어도 상관없다. 단순히 호출을 전달만 하면 된다.

 

실전 사례

Enumberation , Iterator

public class EnumIterAdapter {
	static class EnumerationIterator implements Iterator<Object>{
		Enumeration<?> enumeration;
		public EnumerationIterator(Enumeration<?> enumeration) {
			this.enumeration = enumeration;
		}
		@Override
		public boolean hasNext() {
			return enumeration.hasMoreElements();
		}
		@Override
		public Object next() {
			return enumeration.nextElement();
		}
		/*Enumeration는 원래 없던 기능이라 구현이 불가능하다. 
		 * 이처럼 어댑터는 반드시 일대일 대응되리란 보장이 없다. */
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
	static class IteratorEnumeration implements Enumeration<Object>{
		Iterator<?> iterator;
		public IteratorEnumeration(Iterator<?> iterator) {
			this.iterator = iterator;
		}
		@Override
		public boolean hasMoreElements() {
			return iterator.hasNext();
		}
		@Override
		public Object nextElement() {
			return iterator.next();
		}
	}
}

퍼사드 패턴

기존 복잡한 시스템을 단순한 인터페이스로 변경하는 패턴

쓰기 쉬운 인터페이스를 제공하는 퍼사드 클래스를 구현하는 핵심

 

감싸고 있는 클래스 수가 퍼사드, 어댑터를 구분짓는 요소가 아니다.

어댑터가 여러 클래스를 적응시킬 수도 있고, 하나의 복잡한 인터페이스를 하나의 퍼사드로 처리할 수도 있다.

package designpattern.headfirst.chapter7;

public class FacadeTest {
	static class PopcornPopper {
		String description;

		public PopcornPopper(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void pop() {
			System.out.println(description + " popping popcorn!");
		}
		public String toString() {
			return description;
		}
	}

	static class Projector {
		String description;
		StreamingPlayer player;

		public Projector(String description, StreamingPlayer player) {
			this.description = description;
			this.player = player;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void wideScreenMode() {
			System.out.println(description + " in widescreen mode (16x9 aspect ratio)");
		}
		public void tvMode() {
			System.out.println(description + " in tv mode (4x3 aspect ratio)");
		}
		public String toString() {
			return description;
		}
	}

	static class StreamingPlayer {
		String description;
		int currentChapter;
		Amplifier amplifier;
		String movie;

		public StreamingPlayer(String description, Amplifier amplifier) {
			this.description = description;
			this.amplifier = amplifier;
		}

		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void play(String movie) {
			this.movie = movie;
			currentChapter = 0;
			System.out.println(description + " playing \"" + movie + "\"");
		}
		public void play(int chapter) {
			if (movie == null) {
				System.out.println(description + " can't play chapter " + chapter + " no movie selected");
			} else {
				currentChapter = chapter;
				System.out.println(description + " playing chapter " + currentChapter + " of \"" + movie + "\"");
			}
		}
		public void stop() {
			currentChapter = 0;
			System.out.println(description + " stopped \"" + movie + "\"");
		}
		public void pause() {
			System.out.println(description + " paused \"" + movie + "\"");
		}
		public void setTwoChannelAudio() {
			System.out.println(description + " set two channel audio");
		}
		public void setSurroundAudio() {
			System.out.println(description + " set surround audio");
		}
		public String toString() {
			return description;
		}
	}

	static class CdPlayer {
		String description;
		int currentTrack;
		Amplifier amplifier;
		String title;

		public CdPlayer(String description, Amplifier amplifier) {
			this.description = description;
			this.amplifier = amplifier;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void eject() {
			title = null;
			System.out.println(description + " eject");
		}
		public void play(String title) {
			this.title = title;
			currentTrack = 0;
			System.out.println(description + " playing \"" + title + "\"");
		}
		public void play(int track) {
			if (title == null) {
				System.out.println(description + " can't play track " + currentTrack + ", no cd inserted");
			} else {
				currentTrack = track;
				System.out.println(description + " playing track " + currentTrack);
			}
		}
		public void stop() {
			currentTrack = 0;
			System.out.println(description + " stopped");
		}
		public void pause() {
			System.out.println(description + " paused \"" + title + "\"");
		}
		public String toString() {
			return description;
		}
	}

	static class TheaterLights {
		String description;

		public TheaterLights(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void dim(int level) {
			System.out.println(description + " dimming to " + level + "%");
		}
		public String toString() {
			return description;
		}
	}

	static class Tuner {
		String description;
		Amplifier amplifier;
		double frequency;

		public Tuner(String description, Amplifier amplifier) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void setFrequency(double frequency) {
			System.out.println(description + " setting frequency to " + frequency);
			this.frequency = frequency;
		}
		public void setAm() {
			System.out.println(description + " setting AM mode");
		}
		public void setFm() {
			System.out.println(description + " setting FM mode");
		}
		public String toString() {
			return description;
		}
	}

	static class Screen {
		String description;

		public Screen(String description) {
			this.description = description;
		}
		public void up() {
			System.out.println(description + " going up");
		}
		public void down() {
			System.out.println(description + " going down");
		}
		public String toString() {
			return description;
		}
	}

	static class Amplifier {
		String description;
		Tuner tuner;
		StreamingPlayer player;

		public Amplifier(String description) {
			this.description = description;
		}
		public void on() {
			System.out.println(description + " on");
		}
		public void off() {
			System.out.println(description + " off");
		}
		public void setStereoSound() {
			System.out.println(description + " stereo mode on");
		}
		public void setSurroundSound() {
			System.out.println(description + " surround sound on (5 speakers, 1 subwoofer)");
		}
		public void setVolume(int level) {
			System.out.println(description + " setting volume to " + level);
		}
		public void setTuner(Tuner tuner) {
			System.out.println(description + " setting tuner to " + tuner);
			this.tuner = tuner;
		}
		public void setStreamingPlayer(StreamingPlayer player) {
			System.out.println(description + " setting Streaming player to " + player);
			this.player = player;
		}
		public String toString() {
			return description;
		}
	}
	//퍼사드
	static class HomeTheaterFacade {
		Amplifier amp;
		Tuner tuner;
		StreamingPlayer player;
		CdPlayer cd;
		Projector projector;
		TheaterLights lights;
		Screen screen;
		PopcornPopper popper;

		public HomeTheaterFacade(Amplifier amp, Tuner tuner, StreamingPlayer player, Projector projector, Screen screen,
				TheaterLights lights, PopcornPopper popper) {
			this.amp = amp;
			this.tuner = tuner;
			this.player = player;
			this.projector = projector;
			this.screen = screen;
			this.lights = lights;
			this.popper = popper;
		}

		public void watchMovie(String movie) {
			System.out.println("Get ready to watch a movie...");
			popper.on();
			popper.pop();
			lights.dim(10);
			screen.down();
			projector.on();
			projector.wideScreenMode();
			amp.on();
			amp.setStreamingPlayer(player);
			amp.setSurroundSound();
			amp.setVolume(5);
			player.on();
			player.play(movie);
		}

		public void endMovie() {
			System.out.println("Shutting movie theater down...");
			popper.off();
			lights.on();
			screen.up();
			projector.off();
			amp.off();
			player.stop();
			player.off();
		}

		public void listenToRadio(double frequency) {
			System.out.println("Tuning in the airwaves...");
			tuner.on();
			tuner.setFrequency(frequency);
			amp.on();
			amp.setVolume(5);
			amp.setTuner(tuner);
		}

		public void endRadio() {
			System.out.println("Shutting down the tuner...");
			tuner.off();
			amp.off();
		}
	}
	public static void main(String[] args) {
		Amplifier amp = new Amplifier("Amplifier");
		Tuner tuner = new Tuner("AM/FM Tuner", amp);
		StreamingPlayer player = new StreamingPlayer("Streaming Player", amp);
		CdPlayer cd = new CdPlayer("CD Player", amp);
		Projector projector = new Projector("Projector", player);
		TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
		Screen screen = new Screen("Theater Screen");
		PopcornPopper popper = new PopcornPopper("Popcorn Popper");
 
		HomeTheaterFacade homeTheater = 
				new HomeTheaterFacade(amp, tuner, player, 
						projector, screen, lights, popper);
 
		homeTheater.watchMovie("Raiders of the Lost Ark");
		System.out.println("=========================");
		homeTheater.endMovie();
	}
}

퍼사드 패턴의 정의

서브시스템에 있는 일련의 인터페이스를 통합 인터페이스로 묶어준다.

 

최소 지식 원칙(==디메테르 원칙)

객체 사이의 상호 작용은 될 수 있으면 긴밀한 사이에서만 허용

어떤 객체든 그 객체와 상호작용하는 클래스의 수와 상호작용 방식에 주의를 기울여야 한다.

시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야하는 상황이라면, 최소 지식 원칙이 잘 지켜지지 않고 있는 것

public class 최소지식 {
	static class A1{
		A2 a2 = new A2();
		void callA1() {System.out.println("A1 야호!");}
		void callA2() {a2.callA2();}
		void callA3() {a2.a3.callA3();}
	}
	static class A2{
		A3 a3 = new A3();
		void callA2() {System.out.println("A2 야호!");}
	}
	static class A3{
		void callA3() {System.out.println("A3 야호!");}
	}
	public static void main(String[] args) {
		A1 a1 = new A1();
		a1.callA1();
		a1.callA2();
		a1.a2.callA2();
		a1.callA3();
		a1.a2.a3.callA3();
	}
	
}

핵심정리

인터페이스가 맞지 않으면 어댑터를 쓰면 된다.

큰 인터페이스와 여러 인터페이스를 단순하게 바꾸거나 통합해야 하면 퍼사드를 쓰면 된다.

 

 

 

헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보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방식이 더 좋다고 여겨진다.

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

 

 

 

토비의 스프링 부트 - 이해와 원리 - 인프런 | 강의

스프링 부트의 핵심 기능을 직접 만들어보면서 스프링 부트의 동작 원리를 이해하고, 이를 통해 스프링 부트를 잘 학습하고 사용하는 방법을 배우는 강의입니다. 스프링 부트가 사용하는 스프

www.inflearn.com

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class KimspringApplication {

	public static void main(String[] args) {
		SpringApplication.run(KimspringApplication.class, args);
	}
}

위 코드가 이 강의의 시작과 끝이다. 

 

위 코드의 모습까지 여정을 토비님이 라이브 코딩으로 쉽게 풀어주신다.

 

다만, 강의를 원활하게 듣기 위해선 다음과 같은 사전 지식이 있으면 좋을 것같다.

  • 스프링 프로젝트로 개발을 시작할 수 있도록 처음부터 셋팅해본 경험
  • 간단한 디자인 패턴 이해
  • 아무 객체지향에 관련된 서적 한 권 정도 읽은 지식
  • HTTP 기초
  • 스프링 관련 서적 한 권 정도 읽은 지식

 

위 같은 사전 지식이 없으면, 강의 중간중간에 관련 용어가 나올 때, 물음표가 지어질 수 있을 것 같다. 

예를 들어,  객체와 생성과 조립을 담당한다~~~~ ,...  빈 생에주기를 관리한다~~~~ 

이런 말이 나오면, 이해가 잘 안될 수도 있겠다 싶다.

 

 

디자인 패턴의 중요성

프로그래밍 자주 발생하는 문제를 이미 누군가 좋은 해답으로 해결한

그것을 우리는 배워서 재사용하는 것이다.

디자인 패턴은 코드가 아닌 경험을 재사용하는 것이다.

 

 

디자인 원칙
애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.(캡슐화)

달라지는 부분을 캡슐화하면 향후 다시 변경 시 나머지 안 달라지는 부분에 대한 영향도가 감소한다.

 

 

디자인 원칙
구현보다는 인터페이스에 맞춰서 프로그래밍한다.(다형성)

구현부가 존재하는 구체적인 클래스에 맞춰 프로그래밍하면 유연하지 못하다.

 

디자인 원칙
상속보다는 구성을 활용한다

자바는 단일 단일 상속만 지원한다. 따라서 더욱 적극적으로 구성을 활용하는 것이 좋다.

 

전략패턴(Strategy Pattern)

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.

알고리즘은 일련의 행동을 정의해둔 행동이다

 

public interface Movable {
	void move();
}

public class Fly implements Movable{

	@Override
	public void move() {
		System.out.println("날아갑니다...");
	}

}

public class Riding implements Movable{

	@Override
	public void move() {
		System.out.println("말타고 갑니다...");
	}

}
public class Walk implements Movable{

	@Override
	public void move() {
		System.out.println("걸어가기...");
	}

}

public interface Weapon {
	void attack();
}

public class BowWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("화살 쏘기!");
	}
	
}
public class SpearWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("찌르기!");
	}
	
}

public class SwordWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("베기!");
	}
	
}



public abstract class Character {
	String type;
	//상속보단 일반적으로 구성과 위임이 더 유연하다(특히 자바는 단일 상속만 되므로)
	Movable movable;
	Weapon weapon;
	
	public void setMovable(Movable movable) {
		this.movable = movable;
	}
	public void setWeapon(Weapon weapon) {
		this.weapon = weapon;
	}
	
	public void attack() {
		weapon.attack();
	}
	
	public void move() {
		movable.move();
	}
}
public class Angel extends Character{
	
	public Angel() {
		super.type = "천사";
	}
	
	@Override
	public void attack() {
		System.out.print("신성한 ");
		super.attack();
	}
	
}
public class Devil extends Character{
	
	public Devil() {
		super.type = "악마";
	}
	
	@Override
	public void attack() {
		System.out.print("타락한 ");
		super.attack();
	}
}


public class Ex {
	public static void main(String[] args) {
		
		//인터페이스에 맞춰서 프로그래밍 한다.
		Character angel = new Angel();
		Character devil = new Devil();
		
		//전략 객체 
		//인터페이스에 맞춰서 프로그래밍 한다.
		Movable fly = new Fly();
		Movable riding = new Riding();
		Movable walk = new Walk();
		Weapon sword = new SwordWeapon();
		Weapon spear = new SpearWeapon();
		Weapon bow = new BowWeapon();
		
		//실세 런타임 환경이라면 버튼이던 이벤트로 설정자 주입으로 객체 행동을 마음것 바꿀 수 있다.
		angel.setMovable(fly);
		angel.setWeapon(sword);
		behavior(angel);
		angel.setMovable(riding);
		angel.setWeapon(spear);
		behavior(angel);
		angel.setMovable(walk);
		angel.setWeapon(bow);
		behavior(angel);
		
		//람다식을 사용하면 전략 객체를 일급함수처럼(변수처럼) 다룰 수 있다.
		devil.setMovable(()->System.out.println("뛰기!"));
		devil.setWeapon(()->System.out.println("침뱉기!"));
		behavior(devil);
	}
	
	static void behavior(Character character) {
		character.move();
		character.attack();
	}
	
}
날아갑니다...
신성한 베기!
말타고 갑니다...
신성한 찌르기!
걸어가기...
신성한 화살 쏘기!
뛰기!
타락한 침뱉기!

mark4.zip
0.00MB

 

 

패턴과 전문 용어

서로 알고 있는 패턴은 막강하다. 불필요한 설명이 필요없이 짧은 대화로 모든 의사소통된다.

 

예시, 우리는 여기 부분을 전략 패턴으로 설계했습니다. 

속뜻, 자주 변경되는 부분을 캡슐화하여 쉽게 확장하고 런타임 환경에서 객체 행동을 자유롭게 변경할 수 있게 설계했다.

 

 

 

 

핵심 정리

패턴의 밑바탕에는 객체지향 기초(추상화, 캡슐화, 다형성, 상속)가 있다.

프로그램 생애주기를 보면 개발보다 유지보수가 훨씬 더 많은 비중을 차지한다. 따라서 재사용성, 확장성, 관리 용이성을 갖추는 것이 중요하다.

패턴은 검증받은 객체지향 경험이다.

대부분 패턴과 원칙은 변경에 관련된 것이다.

변경 가능성 있는 부분은 캡슐화해야 한다.

 

 

 

 

 

 

 

 

 

일반화와 특수화

범주의 계층

18세기 자연에 대한 관심 증가로 매년 수천 종의 생물들이 새롭게 발견

끝임없이 쏟아져 보편적 분류 체계가 필요하다 느낌

1735 카를로스 린네 자연의 체계를 발표

계문강목과속종

 

린네의 분류 체계는 범주 간의 계층적인 구조를 가짐

모든 포유류는 척추가 있고 새끼를 낳아 젖을 먹여 기른다는 특징을 공유

따라서 얼룩고양이도 척추가 있고 새끼를 낳아 기르며 젖을 먹여 새끼를 기를 것이라는 사실을 쉽게 있다.

 

린네의 계층 구조는 세부적인 범주가 계층의 하위에 위치하고 일반적인 범주가 계층의 상위에 위치한다.

계층 상위에 위치한 범주를 계층 하위에 위치한 범주의 일반화

계층의 하위에 위치한 범주는 계층의 상위에 위치한 범주의 특수화라고 한다.

 

서브타입

객체지향의 세게에서 범주는 개념, 개념은 타입

따라서 일반화와 특수화는 계층 구조 안에서 존재하는 타입 간의 관계를 의미한다.

어떤 타입이 다른 타입보다 일반적이라면 슈퍼 타입(supertype)

어떤 타입이 다른 타입보다 특수하다면 서브타입(subtype)이라고 한다.

슈퍼타입은 서브타입의 일반화

서브타입은 슈퍼타입의 특수화

 

일반화와 특수화 계층 구조에서 서브타입은 슈퍼타입이 가진 본질적인 속성과 함께 자신만의 추가적인 속성을 가진다.

 

크레이그 라만은 어떤 타입이 다른 타입의 서브타입이 되기 위해서는 100% 규칙과 Is-a 규칙을 준수해야한다고 했다.

100% 규칙은 내연 관련

Is-a 규칙은 외연 관련 규칙이다.

  • 100%규칙
    슈퍼타입의 정의가 서브타입에 100% 적용되어야 한다
    서브타입은 속성과 연관관계 면에서 슈퍼타입과 100% 일치해야 한다.
  • Is-a 규칙(is-a-kind-of)
    서브타입의 모든 인스턴스는 슈퍼타입 집합에 포함돼야 한다.
    "서브타입은 슈퍼타입이다" 라는 말에 대입해서 맞다면 적용되는 것으로 본다.
    인간은 동물이다. (일치) / 컴퓨터는 동물이다(불일치)
    서브타입이 슈퍼타입의 부분집합인 것을 알 수 있다.

 

상속

일반화 특수화 관계를 구현하는 일반적인 방법은 클래스 상속을 사용하는

반드시 모든 상속 관계가 일반화 관계인 것은 아니다.

 

일반화의 원칙은 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼타입에 순응(conformance)해야 한다는

순응은 구조적인 순응(structural conformance) 행위적인 순응(behavioral conformance) 가지 종류가 있다.

가지 모두 특정 기대 집합에 대해 서브타입의 슈퍼타입에 대한 대체 가능성 의미한다.

  • 구조적인 순응의 경우 기대 집합은 속성과 연관관계에 관한 것
  • 행위적인 순응의 경우 기대 집합은 행위가 동일한 계약을 기반으로 하느냐

 

구조적인 순응은 타입의 내연과 관련된 100% 규칙을 의미(속성)

Person 클래스에 name 속성이 있다고 가정, Person 서브타입인 Employee 역시 name 속성을 가져야 한다.

Employee 클래스는 구조적으로 Person클래스를 대체할 있다.

 

행위적 순응은 흔히 리스코프 치환 원칙(Liskov Substitution Principle,LSP)이라고 한다.

Person.getAge() 나이를 반환한다면 Employee.getAge()역시 나이를 반환해야 한다.

Employee Person 대해 행위적으로 순응하기 때문에 대체 가능하다.

 

상속의 또다른 용도는 코드 중복을 방지 공통 코드를 재사용하기 위한 언어적 메커니즘을 제공하는

 

 

상속은 서브타이핑(subtyping) 서브클래싱(subclassing) 가지 용도로 사용될 있다.

  • 서브 클래스가 슈퍼클래스를 대체할 수 있는 경우 이를 서브타이핑이라고 한다.
  • 서브 클래스가 슈퍼클래스를 대체할 수 없는 경우 서브클래싱이라고 한다.

 

서브타이핑은 설계의 유연성이 목표

서브클래싱은 코드의 중복 제거와 재사용이 목적

 

서브타이핑은 인터페이스 상속(interface inheritance)

서브클래싱을 구현 상속(implementation inheritance)이라고 한다.

 

클래스가 다른 클래스를 상속받았다는 사실만으로 클래스 간의 관계가 서브타이핑인지, 서브클래싱인지 여부를 결정할 없다.

서브타이핑의 전제 조건은 대체 가능성이기 때문에 클라이언트 관점에서 실제로 어떻게 사용되고 있는지를 확인해야 한다.

 

가능한 모든 상속관계가 서브타이핑의 대체 가능성을 준수하도록 하는 것이 좋다

 

여러 클래스로 구성된 상속 계층에서 수신된 메시지를 이해하는 기본적인 방법은 클래스 위임(delegation) 사용하는  

 

 

집합과 분해

계층적인 복잡성

복잡성은 계층의 형태를 띈다.

단순한 형태로부터 복잡한 형태로 진화하는데 걸리는 시간은 그 사이에 존재하는 안정적인 형태의 수와 분포에 의존한다

 

시계는 중간 부품들을 조립해서 만들어진다.

중간 부품은 작은 부품들을 조립해서 만들어진다.

결국 시계는 전체적으로 연쇄적인 계층 구조 구성된다.

같이 안정적인 형태의 부분으로부터 전체를 구축하는 행위를 집합이라고 한다.

반대로 전체를 부분으로 분할하는 행위를 분해라고 한다.

 

집합의 가치는 많은 수의 사물들의 형상을 하나의 단위 다룸으로써 복잡성을 줄일 있다는 있다.

 

집합은 불필요한 세부 사항을 배제하고 그림에서 대상을 다룰 있게 한다. (추상화, 캡슐화)

그러나 필요한 시점에는 전체를 분해함으로써 안에 포함된 부분들을 새로운 전체 다룰 있다.

 

합성 관계

상품 주문을 생각해보자.

상품 주문은 여러 상품을 번에 주문할 있다.

이때 상품을 주문했는지를 가리켜 주문 항목이라고 한다.

주문 항목은 주문과 독립적으로 존재할 없다. 반드시 어느 주문에 주문항목이 일부로 생성되기 때문이다.

객체와 객체 사이의 전체-부분 관계를 구현하기 위해서는 합성 관계를 사용한다.

 

합성관계는 부분을 전체 안에 캡슐화함으로써 단순화한다.

, 주문과 상품만이 존재하는 것처럼 모델을 다룰 있다.

필요시 주문 내부의 세부적인 주문항목을 보는

 

주문항목은 주문과 전체와 부분을 나타내는 합성관계다.

주문항목과 상품 간에도 선이 연결되어 있는데 이를 연관 관계라 한다.

 

일반적으로 합성관계로 연결된 객체는 포함하는 객체가 제거되면 같이 제거된다.

주문이 제거되면 주문항목이 존재할 없으니 같이 제거

 

연관 관계는 주문 제거되더라도 상품은 계속 판매될 것이다.

 

 

패키지

합성 관계를 통해 단순화 하더라도 결국 클래스 수가 많아지면 복잡해진다.

 

서로 관련성이 높은 클래스 집합을 논리적인 단위로 통합

 

관련된 클래스 집합을 하나의 논리적인 단위로 묶는 구성 요소를 패키지(package)또는 모듈(module)이라한다.

 

합성 관계가 내부에 포함된 객체들의 존재를 감춤으로써 내부 구조를 추상화하는 처럼 패키지는 내부에 포함된 클래스들을 감춤으로써 시스템의 구조를 추상화한다.

 

추상화 기법

추상화는 도메인의 복잡성을 단순화하고 직관적인 멘탈 모델을 만드는 사용할 있는 기본적인 인지 수단이다

동일한 특성을 공유하는 객체들을 타입으로 분류하는 것이 추상화 기법의 예다.

 

추상화 기법은 결국 복잡성을 낮추기,세부사항 감추기

 

  • 분류와 인스턴스화 
    분류는 객체의 구체적인 세부 사항을 숨김, 인스턴스 간 공유하는 공통적인 특성을 기반으로 범주화
    인스턴스화는 범주로부터 객체를 생성하는 것
  • 일반화와 특수화
    범주 사이의 차이를 숨기고 번주 간에 공유하는 공통적인 특성을 강조
    이 반대가 특수화
  • 집홥과 분해
    부분과 관련된 세부 사항을 숨기고 부분을 사용해서 전체를 형성하는 과정을 가리킴
    집합의 반대 과정은 전체를 부분으로 분리하는 분해

 

 

분류와 인스턴스화

개념과 범주

 

개념이란 속성과 행위가 유사한 객체에 공통적으로 적용되는 관념이나 아이디어다 -Martin

객체를 분류하고 범주로 묶는 것은 객체들의 특정 집합에 공통의 개념을 적용하는 것을 의미한다.

 

나무라는 범주에는 '푸른 잎과 갈색의 줄기를 가진 다년생 식물'이라는 개념을 적용할 있다.

객체들을 공통적인 특성을 기반으로 범주로 묶고, 개념을 적용하는 것은 범주라는 정신적인 렌즈를 통해 세상을 바라보는 것과 유사하다.

자동차라는 렌즈를 통해 바라본 세상은 자동차 범주에 속한 객체만 보일

 

세상에 존재하는 객체에 개념을 적용하는 과정을 분류라고 한다. -Martin

분류는 객체를 특정한 개념을 가타내는 집합의 구성 요소로 포함시킨다.

 

세상에 존재하는 서로 다른 상태를 가진 무수히 많은 자동차와 나무를 개별적으로 다루기는 불가능하다. 그래서 범주로 묶어 세상이 가진 복잡성을 낮춘다.

사람들은 분류를 통해 개별 현상을 하나의 개념으로 다룬다.

이때 '수많은 개별적인 현상들' 객체라하고, '하나의 개념' 타입이라고 한다.

분류는 객체를 타입과 연관시키는 것이다.

 

분류의 역은 타입에 해당하는 객체를 생성하는 과정으로 인스턴스화 또는 예시라고 한다.

 

객체지향 세계에서 개념을 가리키는 표준 용어는 타입이다. -Martin

객체를 타입의 인스턴스라고 한다.

 

분류는 객체와 타입 간의 관계를 나타낸 것이다.

어떤 객체가 타입의 정의에 부합할 경우 객체는 해당 타입으로 분류되며 자동으로 타입의 인스턴스가 된다.

 

타입

타입을 객체의 분류 장치로서 적용할 수 있으려면 다음과 같은 세 가지 관점에서의 정의가 필요하다 - Martin
  • 심볼
    타입을 가리키는 간단략 명칭
  • 내연
    타입의 완전한 정의
  • 외연
    타입에 속하는 모든 객체들의 집합

 

 

외연과 집합

타입의 외연은 타입에 속하는 객체들의 집합으로 표현

집합 = 외연

객체들은 동시에 서로 다른 집합에 포함 수도 있다.

 

객체가 하나의 타입에 속하는 경우 단일 분류(single classification)

여러 타입에 속하는 경우 다중 분류(multiple classification) 한다.

 

대부분의 객체지향 프로그래밍 언어들은 단일 분류만을 지원한다.

대부분의 언어에서 객체는 오직 클래스의 인스턴스여야만 하며 동시에 개의 클래스의 인스턴스일 수는 없다. 관점에서 다중 분류를 다중 상속과 혼동해서는 안된다.

다중 상속은 하나의 타입이 다수의 슈퍼타입을 가질 있도록 허용하지만 타입 정의를 생략할 수는 없다. 반면 다중 분류는 특정한 타입을 정의하지 않고도 하나의 객체가 서로 다른 타입의 인스턴스가 되도록 허용한다.

 

객체를 특정한 타입으로 분류하면 해당 객체는 타입의 집합에 포함된다.

객체가 집합에서 다른 집합의 원소로 자신이 속하는 타입을 변경할 있는 경우 이를 동적 분류(dynamic classification)이라고 한다.

객체가 자신의 타입을 변경할 없는 경우 이를 정적 분류(static classification) 한다.

 

하나의 컴퓨터가 시간의 흐름에 따라 교육용 컴퓨터에서 사무용 컴퓨터로, 다시 사무용에서 교육용 컴퓨터로 분류가 바뀌는 과정은 컴퓨터가 여러 집합에 속할 있다는

 

다중 분류와 동적 분류는 서로 배타적인 개념이 아니다. 함께 적용하는 것이 실세계의 복잡성을 모델링하는데 유용하다.

 

클래스 기반의 객체지향에서 타입은 클래스로 구현된다.

대부분의 언어는 일단 클래스로부터 인스턴스를 생성한 클래스를 변경할 있는 방법을 제공하지 않는다. , 객체의 타입을 변경할 없다. 따라서 우리가 사용하는 대부분의 언어는 정적 분류만 허용하며 동적 분류를 구현할 있는 방법을 제공하지 않는다.

 

이처럼 언어의 제약으로 인해 다중 분류와 동적 분류를 구현으로 옮기기 쉽지 않다.

절충안으로 도메인 모델의 초안을 다중 분류와 동적 분류로 만들고, 실제 구현에 적합하도록 단일 분류와 정적 분류 방식으로 객체들의 범주를 재조정하는 것이 좋다.

 

디자인 템플릿은 유연성이라는 측면에서 필요한 경우에만 사용

단순함을 위해서는 단일 분류와 정적 분류를 선택

 

 

클래스

타입을 구현하는 가장 보편적인 방법

클래스는 타입 구현 용도 외에 코드 재사용 용도로 사용할 있다.

추상 클래스, 인터페이스도 타입 구현 가능

 

현재 객체지향 패러다임은 아리스토텔레스 분류법의 근간을 형성하는 아이디어를 근간으로 한다

 

아리스토 텔레스는 객체의 특성을 본질적인 속성과 우연적인 속성으로 분류했다.

본질(essence)이란 사물의 가장 핵심적이고 필수불가결한 솓성

본질적인 속성 속성을 우연적(accidental)속성이라고 한다.

예컨대 사람이 취직을 해서 회사원이 되어도 사람은 여전히 사람이다. 회사원이 사람의 본질을 바꾸지는 못한다.

 

클래스는 객체가 공유하는 본질적인 속성을 정의한다. 대부분의 객체지향 언어에는 우연적인 속성은 추가할 없다.

안타깝지만 분류 수준과 결과는 누가 분류를 하는가와 무엇을 기반으로 분류하는가에 따라 크게 달라진다. 실제로 사람들은 동일한 사물을 다양한 방식으로 인식하며 다양한 방식으로 분류한다.

 

자바스크립트처럼 클래스가 존재하지 않는 프로토타입 기반의 언어는 아리스토텔레스의 객관적인 분류 체계가 존재한다는 사상 대한 철학적 의문에 뿌리를 두고 있다. 클래스가 없는 프로토타입 언어에서 분류와 인스턴스화는 프로토타입이라는 객체의 복사를 통해 이뤄진다.

 

+ Recent posts