어댑터, 프록시, 데코레이터 패턴은 비슷하기에 잘 구분해야 함.
ODBC/JDBC처럼 데이터베이스 시스템을 공통의 인터페이스를 이용해 조작하여 사용하는 방식이 어댑터 패턴을 이용한 것
//어댑터를 사용하기 전
//ServiceA 클래스
pubic class ServiceA{
void runServiceA(){
sout("이진아1");
}
}
//ServiceB 클래스
public class ServiceB{
void runServiceB(){
sout("이진아2");
}
}
//ClientNoAdapter 클래스
public class ClientNoAdapter{
public static void main(String[] args){
ServiceA s1 = new ServiceA();
ServiceB s2 = new ServiceB();
s1.runServiceA();
s2.runServiceB();
}
}
//어댑터를 사용한 후
//ServiceA를 위한 변환기 AdapterServiceA
public class AdapterServiceA{
ServiceA s1 = new ServiceA();
void runService(){
s1.runServiceA();
}
}
//ServiceB를 위한 변환기 AdapterServiceB
public class AdapterServiceB{
ServiceB s2 = new ServiceB();
void runService(){
s2.runServiceB();
}
}
//기존의 ServiceA와 ServiceB의 메서드를 service()라고 하는 같은 이름의 메서드로 호출하여 사용할 수 있게 하는 변환기
//어댑터 패턴을 사용하는 ClientWithAdapter
public class ClientWithAdapter{
public static void main(String[] args){
AdapterServiceA As1 = new AdapterServiceA();
AdapterServiceB As2 = new AdapterServiceB();
As1.runService();
As2.runService();
}
}
프록시를 번역하면 대리자, 대변인의 의미. 프로그램에도 똑같이 프록시에게 어떤 일을 대신 시키는 것
//프록시를 적용하지 않았을 때
//Service 클래스
public class Service{
publice String runSomething(){
return "프록시";
}
}
publice class ClientNoProxy{
public static void main(String[] args){
//프록시를 이용하지 않은 호출
Service ser = new Service();
sout(ser.runSomething());
}
}
//프록시를 적용했을 때
public interface IService{
String runSomething();
}
//IService 인터페이스를 구현한 Service
public class Service implement IService{
public String runSomething(){
return "프록시";
}
}
//IService 인터페이스를 구현한 Proxy
public class Proxy implement IService{
IService service1;
public String runSomething(){
sout("호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달");
service1 = new Service();
return service1.runSomething();
}
}
//프록시를 사용한 ClientWithProxy
publice class ClientWithProxy{
public static void main(String[] args){
//프록시를 이용한 호출
IService proxy = new proxy();
sout(proxy.runSomething());
}
}
✔︎ 대리자는 실제 서비스와 같은 이름의 메서드를 인터페이스를 사용하여 구현
주어진 상황 및 용도에 따라 어떤 객체에 책임(기능)을 동적으로 추가하는 패턴
Component : 실질적인 인스턴스를 컨트롤하는 역할
ConcreteComponent : Component의 실질적인 인스턴스의 부분으로 책임의 주체의 역할
Decorator : Component와 ConcreteDecorator를 동일시 하도록 해주는 역할
ConcreteDecoreator : 실질적인 장식 인스턴스 및 정의이며 추가된 책임의 주체
//component.interface
public interface component {
String add(); //재료 추가
}
//Basecomponent.java
public class Basecomponent implements component {
@Override
public String add() {
return "원두";
}
}
//Basecomponent에서는 component를 상속받아 커피의 기본 재료가 되는 원두를 넣는 것
//Decorator.java
abstract public class Decorator implements component {
private component coffeecomponent;
public Decorator(component coffeecomponent) {
this.coffeecomponent = coffeecomponent;
}
public String add() {
return coffeecomponent.add();
}
}
//Decorator는 커피의 재료들의 근간이 되는 추상클래스로 재료들은 이 Decorator를 상속받아 재료를 추가
//Water.java
//물을 추가해주는 클래스
public class Water extends Decorator {
public Water(component coffeecomponent) {
super(coffeecomponent);
}
@Override
public String add() {
return super.add() + " + 물";
}
}
//물을 추가하는 것으로 add메소드를 정의
//Milk.java
//우유를 추가해주는 클래스
public class Milk extends Decorator {
public Milk(component coffeecomponent) {
super(coffeecomponent);
}
@Override
public String add() {
return super.add() + " + 우유";
}
}
//우유를 추가하는것으로 add메소드를 정의
//Main.java
public class Main {
public static void main(String[] args) {
component espresso = new Basecomponent();
sout("에스프레소 : " + espresso.add());
component americano = new Water(new Basecomponent());
sout("아메리카노 : " + americano.add());
component latte = new Milk(new Water(new Basecomponent()));
sout("라떼 : " + latte.add());
}
}
->
에스프레소 : 원두
아메리카노 : 원두 + 물
라떼 : 원두 + 물 + 우유
객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미
public class Cat {
private static Cat instance = new Cat ();
private Cat () {
pubtic static Cat getInstance() { // 외부에서 가져오기 위해서 static으로 생성
return instance;
}
}
//static키워드로 instance생성 후 getInstance로 객체를 가져옴
public class Test {
public static void main(String[] args) {
Cat cat1 = Cat. getInstance ( );
Cat cat2 = Cat. getInstance ();
sout("cat1: " + cat1) ;
sout("cat2: " + cat2);
}
}
//인스턴스를 가져오고 싶을 경우 호출을 통해 가져옴
//2개의 객체를 생성하고 인스턴스를 가져올 경우 출력해도 동일한 값
->
cat1: classpart.Cat@28a418fc
cat2: classpart.Cat@28a418fc
어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴
✔︎ 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드(재정의)할 수 있도록 하는 행동 디자인 패턴
//템플릿 메서드 패턴을 사용하지 않았을 때
// Ramyun1.java
public class Ramyun1 {
public void cook() {
sout("불을 켠다.");
sout("냄비에 물을 받고 끓인다.");
sout("라면1을 준비한다.");
sout("재료를 넣고 끓인다.");
sout("불을 끈다.");
}
}
// Ramyun2.java
public class Ramyun2 {
public void cook() {
sout("가스불을 켠다.");
sout("냄비에 물을 받고 끓인다.");
sout("라면2를 준비한다.");
sout("재료를 넣고 끓인다.");
sout("불을 끈다.");
}
}
// Ramyun3.java
public class Ramyun3 {
public void cook() {
sout("가스불을 켠다.");
sout("냄비에 물을 받고 끓인다.");
sout("라면3을 준비한다.");
sout("재료를 넣고 끓인다.");
sout("불을 끈다.");
}
}
//템플릿 메서드 패턴을 사용했을 때
//일부 중복되는 동작을 Ramyun 이라는 상위 클래스에 구현
abstract class Ramyun {
public void cook() {
sout("가스불을 켠다.");
sout("냄비에 물을 받고 끓인다.");
getRamyun();
sout("재료를 넣고 끓인다.");
sout("불을 끈다.");
}
protected abstract void getRamyun();
}
// Ramyun1.java
public class Ramyun1 extends Ramyun {
@Override
protected void getRamyun() {
sout("라면1을 준비한다.");
}
}
// Ramyun2.java
public class Ramyun2 extends Ramyun {
@Override
protected void getRamyun() {
sout("라면2를 준비한다.");
}
}
// Ramyun3.java
public class Ramyun3 extends Ramyun {
@Override
protected void getRamyun() {
sout("라면3을 준비한다.");
}
}
객체를 생성 반환하는 메서드
✔︎ 무신사에서 가방과 가디건을 구매하는 과정
1. 쇼핑몰(musinsa)에 들어간다
2. 카테고리를 선택한다.
3. 물건을 검색(find), 장바구니에 담고(cart), 주문(order), 마지막으로 주문을 확인(check)
//Shopping.java
public interface Shopping {
public void find(); //물건을 검색
public void cart(); //장바구니에 담기
public void order(); //결제 후 주문
public void check(); //주문이 완료되었는지 확인
} //인터페이스로 작성
//Shopping.java
public abstract class Shopping{
public Shopping shopping(String category) {
Shopping shopping = selectCategory(category); //factory method 사용
shopping.find();
shopping.cart();
shopping.order();
shopping.check();
return shopping;
}
//factory method
abstract Shopping selectCategory(String category);
}
//musinsa.java
public class musinsa extends Shopping{
@Override
Shopping selectCategory(String category) {
System.out.println("-----------musinsa-----------");
if (category.equals("Bag")) {
return new BagCategory();
} else if (category.equals("Cardigan")) {
return new CardiganCategory();
}
return null;
}
}
//musinsa 클래스는 Shopping클래스를 상속을 받음.
//나중에 Main 클래스에서 선언을 해주면 Shopping클래스에 기작성 되어있던 selectCategory 메서드와 쇼핑 과정들이 동작하는 것을 볼 수 있음
//BagCategory.java
public class BagCategory implements Shopping{
@Override
public void find() {
System.out.println("가방 브랜드를 검색");
}
@Override
public void cart() {
System.out.println("가방을 장바구니에");
}
@Override
public void order() {
System.out.println("가방을 주문");
}
@Override
public void check() {
System.out.println("주문이 올바르게 되었는지 확인");
}
}
//CardiganCategory.java
public class CardiganCategory implements Shopping{
@Override
public void find() {
System.out.println("가디건 브랜드를 검색");
}
@Override
public void cart() {
System.out.println("가디건을 장바구니에");
}
@Override
public void order() {
System.out.println("가디건을 주문");
}
@Override
public void check() {
System.out.println("주문이 올바르게 되었는지 확인");
}
}
//Main.java
public class Main {
public static void main(String[] args) {
Shopping musinsa = new musinsa();
Shopping bag = musinsa.shopping("bag");
Shopping cardigan = musinsa.shopping("cardigan"); // 식료품 카테고리
}
}
//musinsa 클래스는 Shopping 클래스를 상속받았으므로, 기작성 된 selectCategory(메서드)와 4개의 과정들이 동작하게 됨.
//그중 가장 먼저 동작하는 selectCategory에 알맞은 매게 변수(문자열 category)를 넣어주면, 그에 따라 적절한 카테고리 객체가 반환되는 것
->
-----------musinsa-----------
가방 브랜드를 검색
가방을 장바구니에
가방을 주문
주문이 올바르게 되었는지 확인
-----------musinsa-----------
가디건 브랜드를 검색
가디건을 장바구니에
가디건을 주문
주문이 올바르게 되었는지 확인
같은 기능이지만 서로 다른 전략을 가진 클래스들을 각각 캡슐화하여 상호교환 할 수 있도록 하는 패턴
예시로) 커피머신에 두 가지 전략이 있다. 하나는 아메리카노 하나는 카페라떼
그러나 "커피를 내린다" 라는 기능은 같다.
public interface coffee{
void brew();
}
public class Americano implements coffee{
private static final String americano = "아메리카노";
@override
publIc String brew() {
//아메리카노를 내리는 기능
return americano;
}
}
public class Cafelatte implements coffee{
private static final string cafelatte = "카페라떼" ;
@override
public String brew() {
//카페라떼를 내리는 기능
return cafelatte;
}
}
//커피머신
public class CoffeeMachine{
public String brew(coffee coffee) {
return coffee.brew() ;
}
}
//전략패턴을 사용하면 커피머신을 아주 간결하게 구현할 수 있음
//커피 인터페이스를 받으면 클라이언트에서 주입하는 구현체에 따라 전략이 결정되기 때문.
전략 패턴의 변형으로 DI에서 사용하는 형태의 패턴
//Strategy.java
public interface Strategy{
public abstract void runStrategy();
}
//Student.java
public class Student{
void runContext(Strategy strategy){
sout("공부 시작");
strategy.runStrategy();
sout("공부 끝");
}
}
//Client.java
public class Client {
public static void main(String[] args) (
Student takenotes = new Student();
takenotes.runContext (new Strategy() {
@Override
public void runStrategy() {
sout("밑줄긋기");
}
});
sout();
takenotes.runContext (new Strategy () {
@Override
public void runStrategy() {
sout("틀린 부분 지우기");
}
});
System.out.printin();
}
}
✔︎ 중복된 코드가 있기에 템플릿 콜백 패턴으로 수정하여도 된다.
분량 정말 너무 많다.. 교재 읽는데 눈 빠질 뻔 한 것 같다.. ㅋㅋ