팩토리 메소드 패턴 vs 프록시 패턴
팩토리 메소드 패턴은 "객체의 생성"을 서브 class에 위임하는 것
프록시 패턴
프록시 패턴은 class끼리 직접 참조하지 않고 간접적으로 서브 class를 통해 참조해서 값을 출력하거나 getter로 받거나 하는 것
프록시 패턴 vs 중재자 패턴
프록시 패턴은 서브 class를 통해서 값을 바꿀 수 없지만
중재자 패턴은 서브 class를 통해서 값을 바꿀 수도 있다
더 쉽게 설명해주겠다.
interface DeliveryFood{
public void setPrice(int price);
public int getPrice();
}
class Pizza implements DeliveryFood {
private int price=0;
public Pizza(int price) {
this.price=price;
}
public int getPrice() {
return this.price;
}
public void setPrice(int price) {
this.price=price;
}
}
class Chicken implements DeliveryFood{
private Chicken chicken() {
}
private int price=0;
private Chicken(int price) {
this.price=price;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
// pizza로도 확장 가능성이 존재하고
// 치킨이랑 pizza가 존재한다면..
// 또한 피자는 따로 배달이 안된다면..
class FlyWeight{
public Chicken chicken;
public Chicken getChicken() {
return chicken;
}
public Chicken getChicken(int price) {
chicken.setPrice(price);
return chicken;
}
}
class Proxy{
private Chicken chicken;
public int chickenPrice() {
return chicken.getPrice();
}
}
class ChickenService{
private Proxy proxy;
private final int chickenPrice;
private int deliveryPrice;
public ChickenService() {
this.chickenPrice=proxy.chickenPrice();
}
public void chickenPrice(){
if(chickenPrice>=30000) {
deliveryPrice=0;
}
else {
deliveryPrice=3000;
}
}
}
public class java46 {
public static void main(String[] args) {
FlyWeight flyWeight=new FlyWeight();
Chicken chicken=flyWeight.getChicken();
Pizza pizza=new Pizza(10000);
// 관심사 분리를 한번 더 생각해서, Chicken chicken=new Chicken();
// 이렇게 하고 싱글톤으로 하나의 객체로 관리하는 방식이 스프링에서 쓰는 방법이다
// 이건 new로 새로 생성하지 않고
// 하나의 객체를 참조하기 때문에
// 플라이웨이트를 이용한 프록시라고 할 수 있다.
}
}
여기서, 프록시 패턴은
치킨이랑 피자를 의도적으로 숨기는 용도로 쓰이는 경우로 사용한 것은 아니다.
(프록시는 처음공부할 떄 프록시를 생각하면 보안이 생각나서
숨기는 용도로 사용한다고 생각했지만,
실제로 proxy는 대리자. 대리자의 패턴을 의미한다)
여기서 굳이 서브 class로 분리한 이유는 관심사의 분리.
더 쉽게 말하면 하나의 class가 감당할 것이 너무 많아져서 분리하거나,
중복된 목적이나 코드들을 따로 처리하기 위해,
확장 가능성이 존재할 때 이를 쉽게 확장하기 위해서 사용한다
저기서, chickenService만 따로 둔 이유는 피자는 배달이 안된다고 생각했을 때를
가정했기 때문에 Service를 따로 만들지 않았다
여기서, 프록시 패턴은 실제로 값을 set으로 바꿀 수 없다.
오직, 기존에 있는 값을 참고할 뿐이다..
여기 뒤부터는 사실 중요한 내용은 아니다.(바로 프록시 패턴을 참고해도 좋을 것 같다)
또한 왜 싱글톤을 이용한 프록시/ 플라이웨이트를 이용한 프록시로 나누냐면
호출 규약을 어떻게 하느냐에 따라 플라이웨이트로 짜려다가
싱글톤에 있는 코드를 비슷하게 생각없이 그대로 호출해서 new 로 접근한적이 있어서
새로운 객체가 참조된 적이 있어서 올렸다
위의 코드에서 Proxy라는 class가
싱글톤처럼 확실히 막아놓으려고 생성자를 막고 간접적으로 참조하도록
플라이웨이트 패턴을 사용했다
(근데 다음날 수정할 때 생성자를 혹시 몰라서 막아주는 걸 안해놓고 new()로 참조하니까
아 내가 실수했네 이렇게 생각했는데, (생성자는 확실히 막는데, 플라이웨이트는 거기서 자유롭게
짜는 형식이기 때문에 싱글톤처럼 private 생성자를 쓰면 얼마든지 막을 수 있다)
)
차이점은 얘를 이용하면 static으로 사전에 정의를 하지않아도 잘 작동하고,
좀 더 기능을 분리하지만 class가 하나 더 증가한다는 특성이 있겠다
class가 하나 더 늘면, 코드 자체는 너무 길어져 버린다는 단점을 생각해볼 수 있다
장점은 static 키워드는 multi-thread 환경에서 문제가 생기고,
multi-thread 접근을 방지하기 위한 방법으로는 synchronized의 사용이 있다.
https://yeonyeon.tistory.com/113
[Java] static과 synchronized
흔히 static 키워드는 multi-thread 환경에서 문제가 생기고, multi-thread 접근을 방지하기 위한 방법으로는 synchronized의 사용이 있다. static만 쓰거나, synchronized만 쓰거나, 둘 다 쓰거나... 이런 경우가..
yeonyeon.tistory.com
(쉽게 얘기하면 static으로 사전에 정의하는데 그걸 한꺼번에 돌리니 한꺼번에 늘어날 가능성이 존재하고,
synchronized로 돌아가는 환경을 고정시켜버리면 성능저하가 일어난다 위의 해결 방법으로는
현재 싱글톤으로 lazyHolder 방식이 있는 것 같은데,
이 방법은 static이어도 multi-thread에 안전하고 synchronized를 사용하지 않는다)
또한, 하나의 class에 너무 많은 기능을 부여하는 것을 관심사의 분리작업으로 분리가 된다
Chicken,Pizza,dakbal,galbi 등 이렇게 여러 곳에서 값을 간접적으로 참조한다면 다른 class에서 그냥
각각의 class에 메소드를 부여해서 참조할 목적이라면 이 부분은 그냥 구분하는 게 낫다
(사실 이 부분에 대해서는 그냥 각각의 class가 부담하는 게 적으면
관심사 분리가 중요한가 싶었는데,
코드 설계 단위에서 부담할 게 많아지면
그 때부터 함수로 분리하는 코드를 제작한다면
사실 코드 자체가 너무 많아서 이렇게 분리했구나 라고 이해는 되겠지만,
하나의 class가 감당하는 게 너무 많으니
그 이후부터의 코드만을 따로 구분하기 때문에, 이런 코드는 왜 같이 해놓고, 이 코드는 왜 따로 처리했을 까 하며
고의로 코드를 짤 때 해당 목적을 강조하는 듯한 느낌과 하나의 class안에 여러가지 기능이 있는데
한 class가 너무 많은 것을 내포하고 있어서 그 이후부터, 목적을 따로 구분하기 때문에
이질적인 느낌도 드는 것 같다)
따라서 같은 메모리주소를 참조한다면 한 메소드에 넣지말고, 이런 코드는 관심사의 분리 용도로
처음부터 구분하는 것이 맞는 것 같다.는 결론이 나왔다)
위에 대한 내용은 스프링 김영한 강사님의 기초강의에서 관심사 분리를 듣고, 의문이 들어서 생각해본 결론이다.
(참고로 스프링 컨테이너에서는 싱글톤에서 관리를 한다
이걸 작성하는 동안에는 그냥 플라이웨이트를 써도 되지 않았을까?
싶었는데 기본을 싱글톤에서 관리하되, 다른 객체를 생성해야하면,
프로토타입 스코프로 Provider를 이용하면 알아서 처리를 다 해주기 떄문에
Config를 수동 생성 할 때도, 굳이 플라이웨이트 패턴을 쓸 필요가 없다)
이제 중재자 패턴이란 무엇인가?
class Client {
private Mediator mediator;
public void findID() {
mediator.findID();
}
public void setID(String id) {
mediator.setID(id);
}
public void setPassword(String password) {
mediator.setPassword(password);
}
}
class Mediator{
private String userID;
private String userPassword;
public void findID() {
System.out.println(userID);
}
public void setID(String id) {
this.userID=id;
}
public void setPassword(String password) {
this.userPassword=password;
}
}
class Server {
private Mediator mediator;
public void findID() {
mediator.findID();
}
public void setID(String id) {
mediator.setID(id);
}
public void setPassword(String password) {
mediator.setPassword(password);
}
}
public class java46 {
public static void main(String[] args) {
}
}
Server 쪽이랑 Client 각 단에서
서로가 서로의 값을 바꾸려고 시도해서 접근해도 실제로는 mediator가 관리한다
class NonMember {
private Mediator mediator;
public void setEmail(String email) {
mediator.setEmail(email);
}
public void setID(String id) {
mediator.setID(id);
}
public void setPassword(String password) {
mediator.setPassword(password);
}
}
class Member{
Mediator mediator;
public void findID(String id) {
mediator.findID(id);
}
public void findPassword(String Password) {
mediator.findPassword();
}
}
class Mediator{
private String email;
private String userID;
private String userPassword;
public void setEmail(String email) {
this.email=email;
}
public void findID() {
System.out.println(userID);
}
public void findID(String email) {
if(email==null) {
System.out.println("어떠한 이메일도 등록되지않았습니다");
}
if(email!=this.email) {
System.out.println("등록된 이메일이 바꼈거나 다른 이메일입니다");
}
System.out.println(userID);
}
public void findPassword() {
System.out.println(userPassword);
}
public void setID(String id) {
this.userID=id;
}
public void setPassword(String password) {
this.userPassword=password;
}
}
class Server {
private Mediator mediator;
public void findID() {
mediator.findID();
}
public void setID(String id) {
mediator.setID(id);
}
public void setPassword(String password) {
mediator.setPassword(password);
}
}
public class java46 {
public static void main(String[] args) {
}
}
원래는 db같은 곳에 넣던가 (작게 테스트를 할꺼면) 임시로 동적 배열에 추가하면서 이메일,id,password로 정해야하지만
그것까진 생각안하고 오직 하나의 계정에 대해서만 만들고 조회하도록 했다
만약 저기서 Mediator를 쓰지 않았다면
NONMEMBER<-> MEMBER MEMBER<->SERVER SERVER<->NONMEMBER 이런식으로 서로가 서로의 데이터에
접근하게 짜야되서 코드가 더 복잡해보일 것이다
따라서, 이렇게 전부 다 Mediator에 접근해서 실제 코드로는 Mediator가 접근하도록 할 수 있다
그리고, 관련 자료를 오지게 뒤졌는데, Mediator를 cpp에서는 locate같이 직접 class로 접근해서 하는 경우도 있는데
java 블로그들은 다 interface로 접근했지만, 어차피 위와 같은 mediator는 다른 mediator를 만들어야 할 확장성이 있는 것도 아니고,
기능과 역할(interface와 class)에 대해서 굳이 비교할 필요없이 굳이 중간에 값 전달및 세팅만 하는 친구이고,
전달 및 중간에 값 수정만 하는 거라서 큰 기능을 하는 것도 아니다.
그리고, 최근에 김영한 강사님의 스프링을 공부하는데 강사님께서도 interface로 연결안하고, 바로 class를 사용하시는 게 좀 컸다
따라서 위 코드에서는 Mediator interface를 따로 두지않고, class로 바로 연동시켰다
그 자료가 어디있냐면
스프링 핵심 원리 기본편의 AppConfig 리팩토링 쪽을 보길 권한다
보면 알겠지만 Public class로 구현하셨다
(실제 스프링 환경에서 수동 빈등록도 사용하신다고 하셨고, 위 기초 강의를 보면 알겠지만, 그 윗부분은 쭉 인터페이스와 class로 기능과 구현을 분리하는 걸 강조하시는데, 위 부분에서 interface로 두지 않는 것만 봐도 어느정도 의미가 있지 않나 싶다)