View
팩토리 패턴
객체를 사용하는 측에서 바로 생성하지 않고 중간에 생성을 전담하는 객체를 두고 생성하여 결합도를 낮춘다.
예제
동물 카페(AnimalCafe)에서 동물을 만날 수 있는데 현재는 토끼만 만날 수 있다.
interface Animal { /* ... */ }
class Rabbit implements Animal { /* ... */ }
class AnimalCafe {
public Animal meet() {
Animal animal = new Rabbit();
// 기타 로직(등록, 지불 등)
return animal;
}
}
위 코드에서 meet()
클래스는 고객에게 제공할 동물을 선택하고, 제공하기 위해서 기타 로직
을 수행한다. 이후 추가 케이스로 강아지(Puppy)가 추가되었고 카페에선 오전엔 토끼, 오후엔 강아지를 제공하려고 한다.
class Puppy implements Animal { /* ... */ }
class AnimalCafe {
public Animal meet() {
Animal animal;
if (지금시간 == 오전)
animal = new Rabbit();
else
animal = new Puppy();
// 기타 로직(등록, 지불 등)
return animall;
}
}
또 다른 동물 종류가 추가되면 meet()
메소드의 수정이 지속적으로 필요하다. meet()
메소드는 조건에 따라서 제공할 동물을 선택하는 작업
과 고객에게 동물을 제공하기 위한 작업
이 두 가지 작업에 대한 책임을 가지고 있다. 두 가지 작업 중 하나라도 변경이 필요하면 meet()
메소드는 수정되어야한다. meet()
메소드가 하나의 책임만을 가지도록 변경하기 위해 조건에 따라서 제공할 동물을 선택하는 작업
을 아래처럼 분리할 수 있다.
class AnimalFactory {
public static Animal pick() {
if (지금시간 == 오전)
return Rabbit();
else
return new Puppy();
}
}
class AnimalCafe {
public Animal meet() {
Animal animal = AnimalFactory.pick();
return animall;
}
}
AnimalCafe
클래스에서 조건에 따라 동물을 생성하는 로직을 AnimalFactory
클래스의 pick()
메소드로 분리하였다. 이로써 AnimalCafe#meet()
메소드의 고객에게 동물을 제공하기 위한 작업
에 대한 책임만을 가지게 되었다.
싱글톤 패턴 활용
상황에 따라서 매번 인스턴스를 생성할 필요가 없을 경우 싱글톤 패턴 적용을 아래와 같이 고려할 수 있다.
class Pupply implements Animal() {
private Animal puppy;
public static Animal getInstance() {
if (puppy == null)
puppy = new Puppy();
return puppy;
}
}
템플릿 메서드 패턴 활용
// TODO
싱글톤 패턴
클라이언트가 사용하려는 객체가 클라이언트의 라이프 사이클 동안 단 하나의 인스턴스임을 보장할 수 있는 패턴. 아래와 같은 상황에서 활용될 수 있다.
- 공유 자원 접근
- 복수의 시스템이 하나의 자원에 접근할 때
- 유일한 객체가 필요할 때
- 값의 캐시가 필요할 때
예제
모든 사람이 사용할 수 있는 공용 자원인 단 한대의 TV
class Tv {
private static Tv tv;
private int nowChannelNo = 0;
private Tv tv() {}
public static Tv getInstance() {
if (tv == null)
tv = new Tv();
return tv;
}
public void changeChannel(int channerNo) {
this.nowChannelNo = channerNo;
}
}
멀티쓰레드 환경에서의 싱글톤
여러 쓰레드가 위 앞선 예제의 getInstance()
메소드에 접근할 경우 if (singletone == null)
접근 시점에 따라 여러 Singletone
객체를 생성할 수 있다. 이를 방지하기 위해 바로 초기화 하는 방법
과 동기화
방식이 있다.
// 바로 초기화 하는 방식
class Tv {
private static Tv tv = new Tv();;
public static Tv getInstance() {
return tv;
}
}
// 동기화(synchronized 사용)
class Tv {
private static Tv tv;
public synchronized static Tv getInstance() {
if (tv == null)
tv = new Tv();
return tv;
}
}
싱글톤 vs Statis Method
정적 변수와 메소드들로만 이루어진 정적 클래스를 구성하면 싱글톤 패턴의 클래스와 같은 역할을 할 수 있다. 하지만 인터페이스를 구현해야하는 경우 정적 메소드를 사용할 수 없다. 위의 예제의 경우 Tv를 사용할 때 interface Tv
를 구현한 TvA
, TvB
가 있다고 할 때, 클라이언트에서 Tv
를 참조하고있고, 경우에 따라 TvA
의 인스턴스, TvB
의 인스턴스를 사용한다고 하면 이는 정적 클래스로 구현이 불가능하다.
전략 패턴
객체의 행위를 캡슐화하여 분리한다.
예제
로봇은 움직일 수 있고 공격할 수 있다. 각 로봇의 타입에 따라서 움직이거나 공격하는 방식이 다르다.
interface Robot {
void move();
void attack();
}
class RobotA implements Robot {
void move() { do("로봇 A가 걸어갑니다") }
void attack() { do("로봇 A가 총을 사용합니다.") }
}
class RobotB implements Robot {
void move() { do("로봇 B가 뛰어갑니다.") }
void attack() { do("로봇 B가 화염방사기를 사용합니다.") }
}
class Client {
main() {
RobotA a = new RobotA();
RobotB b = new RobotB();
a.move()
a.attack()
b.move()
b.attack()
}
}
- 로봇 A가 날아가게 하려면? => 아래와 같은 기존 코드 변경이 필요하다.
class RobotA implements Robot { void move() { // do("로봇 A가 걸어갑니다") do("로봇 A가 날아갑니다") } ... }
- 로봇C를 추가하고 B와 똑같은 방식으로 동작하게하려면? 화염방사기가 고장나서 물대포 방사기로 변경하여야된다면? => 두 곳의 수정이 필요하다.
class RobotB implements Robot { ... void attack() { do("로봇 C가 물대포 방사기를 사용합니다.") } } class RobotB implemenets Robot { ... void attack() { // do("로봇 B가 화염 방사기를 사용합니다.") do("로봇 B가 물대포 방사기를 사용합니다.") } } class RobotB implemenets Robot { void move() { do("로봇 C가 뛰어갑니다.") } void attack() { // do("로봇 B가 화염 방사기를 사용합니다.") do("로봇 C가 물대포 방사기를 사용합니다.") } }
수행하는 행위를 캡슐화하여 외부로 추출하여 사용할 수 있다.
interface MoveStrategy { void move(); }
class WalkMoveStrategy implements MoveStrategy { void move() { do("걷는다") }
class FlyMoveStrategy implements MoveStrategy { void move() { do("난다") }
interface AttackStrategy { void attack(); }
class GunAttackStrategy implements AttackStrategy { void attack() { do("총 공격") }
class FireAttackStrategy implements AttackStrategy { void attack() { do("화염방사기 공격") }
interface Robot {
void move(MoveStrategy moveStrategy);
void attack(AttackStrategy attackStrategy);
}
class RobotA implements Robot {
public RobotA(MoveStrategy moveStrategy, AttackStragegy attackStrategy) {
setMoveStrategy(moveStrategy)
setAttackStrategy(attackStrategy)
}
void move() { moveStrategy.move() }
void attack() { attackStrategy.attack() }
}
class RobotB implements Robot {
public RobotA(MoveStrategy moveStrategy, AttackStragegy attackStrategy) {
setMoveStrategy(moveStrategy)
setAttackStrategy(attackStrategy)
}
void move() { moveStrategy.move() }
void attack() { attackStrategy.attack() }
}
MoveStrategy
, AttackStrategy
만 변경하여 로봇에 주입함으로써 로봇의 움직임, 공격 방식을 수정하거나 기존에 만들었던 전략을 활용 가능함으로써 재활용도 가능해졌다.
상태 패턴
객체가 상태(객체가 가질 수 있는 어떤 조건이나 상황)가 변함에 따라 수행할 수 있는 행위를 외부로 분리하여 상태의 변경에 유연하게 대처할 수 있도록 한다.
예제
OFF, 약풍이 있는 선풍기 객체를 설계한다. 여기서 OFF, 약풍은 선풍기 객체의 상태에 해당한다.
class Fan {
private static int OFF = 0;
private static int 약풍 = 1;
private int 현재상태 = OFF;
...
public push(int signal) {
if (signal == OFF)
끈다(); 현재상태=OFF;
else if (signal == 약풍)
약풍(); 현재상태=약풍;
}
}
여기서 강풍이 추가된다면 아래의 변경이 필요하다. => 새로운 상태가 추가될 때마다 그 상태를 처리하기 위한 기존 클래스의 수정이 지속적으로 발생하게 된다.
class Fan {
...
private static int 강풍 = 2;
...
public void push(int signal) {
...
else if (signal == 강풍)
강풍(); 현재상태=강풍;
}
}
여기서 변경되는 부분은 상태
, 고정되는 부분은 선풍기가 상태에 해당하는 행위를 실행시키는 책임
이다. 아래와 같이 상태를 분리할 수 있다.
interfcae State {
State action();
}
class Off implements State { State action() { 끈다() } }
class 약풍 implements State { State action() { 약풍() } }
class Fan {
private int 현재상태;
public void push(State state) {
현재상태 = state.action();
}
}
선풍기 객체는 상태 인터페이스만 참조하고 구체적인 구현에 의존하지 않는다. 강풍이 추가된다면 선풍기 객체 Fan에 변경이 필요한 것이 아니라 새로운 상태를 추가하기만 하면 된다.
class 강풍 implements State { State action() { 강풍() } }
싱글톤과의 결합
앞선 예제에서 상태를 생성할 때 매번 새로운 인스턴스를 생성할 필요가 없을 경우 싱글톤 패턴을 적용할 수 있다.
커맨드 패턴
예제
운동 로봇은 여러 모드가 있는데 그 중 축구를 할 수 있게 하는 축구 모드가 있고 리모컨으로 조정할 수 있다.
class SoccerRobot {
void do() { 축구() }
}
class RemoteController {
SoccerRobot soccerRobot;
void push() { soccerRobot.do() }
}
- 농구 로봇이 필요하면? => 농구 로봇을 추가하고 리모컨을 수정한다 => 기존 코드(RemoteController)의 변경이 필요하다.
class BaseballRobot { void do() { 농구() } void push() { soccerRobot.do() } } class RemoteController { BaseballRobot baseballRobot; ... }
- 버튼을 눌렀을 때 이전 모드에서 변경된 모드로 동작하게 한다. 이전 모드가 축구라면 농구를, 농구라면 축구를 한다.새로운 로봇이 추가되고 새로운 조건이 추가된다면? => RemoteController의 수정이 계속 요구된다.
class RemoteController { final int BASEBALL = 1; final int SOCCER = 2; BaseballRobot baseballRobot; SoccerRobot soccerRobot; int nowState = BASEBALL void push() { if (nowStatue == BASEBALL) nowState = SOCCER; soccerRobot.do(); else if (nowStatus == SOCCER) nowState = BASEBALL; baseballRobot.do(); } }
여기서 계속 변하는 것은 버튼을 눌렀을 때 할 행위이다. 이 행위를 캡슐화하여 외부로 추출한다.
interface Command { void execut(); }
class SoccerCommand implements Command {
SoccerRobot soccerRobot;
void execute() { soccerRobot.do() }
}
class BaseballCommand implements Command {
BaseballRobot baseballRobot;
void execute() { baseballRobot.do() }
}
class RemoteController {
Command command;
void push() {
command.execute();
}
}
리모컨의 버튼을 눌렀을 때 일어날 행위를 추가하거나 변경하고 싶다면 Command
인터페이스를 구현한 구현체만 추가하면 되고 기존 클래스의 변경이 필요하지 않다.
옵저버 패턴
예제
경마 게임에서 말이 도착하면 바로 전광판에 보여주는 클래스를 설계한다.
class ArrivalRecorder {
List<Integer> rank;
ScoreBoardPrinter scoreBoardPrinter;
void crossed(Int horseNumber) {
rank.add(horseNumber);
ScoreBoardPrinter.update();
}
}
class ScoreBoardPrinter {
ArrivalRecorder recorder;
public void update() { 화면출력(recorder.getRank()); }
}
class Client {
main() {
ArrivalRecorder recorder = new ArrivalRecorder();
ScoreBoardPrinter scoreBoardPrinter = new ScoreBoardPrinter(recorder);
recoder.scoreBoardPrinter = scoreBoardPrinter;
}
}
- 다른 종류의 스코어 보드를 사용한다면? 예를들어 소리로 말하는
ScoreBoardSpeaker
로 변경한다면? => 기존 클래스의 변경이 필요하다. class ArrivalRecorder { ... ScoreBoardSpeaker scoreBoardSpeaker; ... } class ScoreBoardSpeaker { ... }
- 여러 종류의 스코어 보드를 사용한다면? Printer, Speaker 방식 둘 다 사용하고 싶다, 2개의 Speaker를 사용하고 싶다 => 기존 클래스의 변경이 필요하다.
class ArrivalRecorder { ... List<ScoreBoardSpeaker> scoreBoardSpeaker; ScoreBoardPrinter scoreBoardPrinter; ... }
변하는 것은 ArrivalRecorder가 알려줘야 할 대상, 즉 Recorder에 새로운 이벤트가 생기면 그 이벤트를 받아서 처리해야되는 대상과 그 대상을 ArrivalRecorder에 등록하는 부분.
interface Observer { abstract void update(); }
abstract class Subject {
private List<Observer> observers = new ArrayList<Observer>();
void attach(Observer observer) { ... }
void detach(Observer observer) { ... }
void notify() {
for (Observer observer: observers) {
observer.update()
}
}
}
class ArrivalRecorder extends Subject {
List<Integer> rank;
void crossed(Int horseNumber) {
rank.add(horseNumber);
this.notify();
}
}
class ScoreBoardPrinter implements Observer {
void update() { 화면출력(recorder.getRank()); }
}
class ScoreBoardSpeaker implements Observer {
void update() { 스피커출력(recorder.getRank()); }
}
새로운 출력 방식이 필요하면 Observer
를 구현한 새로운 SocreBoard
를 구현하고 ArrivalRecorder
에 등록하면 기존 코드 수정 없이 새로운 출력 방식의 추가나 변경이 가능하도록 된다.
데코레이터 패턴
여러 기능을 구현한 클래스의 조합을 구현하고자 할 때 사용할 수 있다.
예제
스포츠를 할 수 있는 로봇이 있다. 로봇은 현재 축구 기능을 제공한다.
interface Robot {
void do();
}
class SoccerRobot implements Robot {
void do() {
축구();
}
}
- 다른 기능을 하는 로봇이 추가된다면? 예를들어 농구를 하는 로봇, 달리기를 하는 로봇
- 여러 기능을 조합한 로봇을 원한다면? 예를들어 농구, 축구를 하는 로봇.
각 기능별로 Robot
인터페이스를 구현한 클래스를 만들어야되고, 조합의 경우 모든 구현체에 대한 조합을 하나하나 구현해야 한다. 축구, 농구, 달리기 로봇을 조합하면 축구+농구, 축구+달리기, 농구+달라기 로봇을 구현해야한다.
각 추가 기능별로 개별 클래스를 설계하고 객체의 조합을 담당하는 클래스(데코레이터)를 만든다.
interface Robot {
void do();
}
class Defaultrobot implements robot {
void do() {
기본_동작();
}
}
abstract class RobotDecorator extends Robot {
Robot robot;
void do() {
robot.do();
}
}
class SoccerDecorator extends RobotDecorator {
SoccerDecorator(Robot robot) {
super(robot)
}
void do() {
robot.do();
축구();
}
}
class BaseballDecorator extends RobotDecorator {
BaseballDecorator(Robot robot) {
super(robot)
}
void do() {
robot.do();
농구();
}
}
다른 기능의 로봇이 필요할 경우 RobotDecorator
를 구현한 신규 기능의 클래스를 구현하고 아래와 같이 클라이언트에서 사용할 수 있다.
class RunningDecorator extends Decorator {
...
}
main() {
Robot runningRobot = new RunningDecorator(new DefaultRobot());
runningrobot.do();
}
여러 기능을 조합한 로봇의 경우 아래와 같이 구현할 수 있다.
main() {
Robot runningAndSoccerRobot = new RunningDecorator(
new SoccerDecorator(
new DefaultRobot()));
runningAndSoccerRobot.draw();
}
템플릿 메소드 패턴
전체적으로 동일하면서 부분적으로 다른 구문으로 구성된 메소드의 코드 중복을 줄인다. 동일한 기능을 상위 클래스에서 정의하고 변경이 발생하는 부분만 하위 클래스에서 구현한다.
예제
차가 출발하려면 문이 닫혀있는지 확인해야 한다.
class Car {
move() {
문_확인()
출발();
}
}
여기서 여러 종류의 차가 있다면 아래와 같이 설계해볼 수 있다.
interface Car {
move();
}
class CarA implements Car {
move() {
문_확인();
CarA방식_출발();
}
}
class CarB implements Car {
move() {
문_확인();
CarB방식_출발();
}
}
문_확인()
부분이 계속 중복되는 문제가 있다. 예를들어 창문_확인()
동작이 추가되어야 한다면? -> Car
구현체를 모두 찾아 변경해야 한다.
여기서 변하는 부분은 출발 전 확인하는 부분. 이 행위를 별도의 클래스로 분리한다.
abstrafct class CarTemplate {
move() {
문_확인();
창문_확인();
moveCar();
}
protected abstract moveCar();
}
class CarA extends CarTemplate {
protected moveCar() {
CarA방식_출발();
}
}
다른 방식이 추가된다면 CarTemplate
클래스의 move()
메소드만 수정하면 된다.
컴퍼짓 패턴
객체가 서로 '부분-전체'의 조합을 가지고 전체에서 부분의 공통적인 동작들을 요구할 때 활용할 수 있다.
예제
A, B 부품으로 구성된 로봇. 각 부품 또는 전체(부품의 합)의 전력 소모 합을 제공한다.
class Robot {
A a;
B b;
int getPower() {
return a.getPower() + b.getPower() + c.getPower();
}
}
class A {
int getPower() {
return 100;
}
}
class B {
int getPower() {
return 50;
}
}
부품 C가 추가된다면? -> 새로운 클래스
C
를 만들고Robot
의 멤버를 수정한다. 즉 기존 클래스의 수정이 필요하다.class C { int getPower() { return 400; } } class Robot { ... C c; int getPower() { return a.getPower() + b.getPower() + c.getPower(); } }
추가되는 클래스를 추상화하고, 추상화된 클래스를 변경없이 다룰 수 있는 구조로 설계한다.
interface RobotPart {
int getPower();
}
class A implements RobotPart { ... }
class B implements RobotPart { ... }
class C implements RobotPart { ... }
class Robot {
List<RobotPart> robotParts = new ArrayList<>();
void add(RobotPart part) {
robotParts.add(part);
}
int getPower() {
return robotParts.getPower().sum()
}
}
Reference
'Program language > Java' 카테고리의 다른 글
run java project with h2 without installation (0) | 2022.01.22 |
---|---|
Java Reflection (0) | 2018.04.03 |