WEB/Design Pattern

객체 생성 관련 디자인 패턴

Tony Lim 2023. 1. 26. 14:19

싱글턴 패턴 2

public class Settings3 {

    private static volatile Settings3 instance;

    private Settings3() { }

    public static Settings3 getInstance() {
        if (instance == null) {
            synchronized (Settings3.class) {
                if (instance == null) {
                    instance = new Settings3();
                }
            }
        }
        return instance;
    }
}

instance가 없는 경우에 multithread 환경에서만 synchronized가 락을 잡고 안전하게 싱글톤을 유지하게 되고 평상시 상황에는 락없이 바로 return 하게 된다.

또한 해당 instance가 필요한 시점에 생성을 하게 된다.

volatile을 써서 서로 다른 코어에 스레드가 캐시에 있는 값을 읽어오는게 아니라 메인 메모리에 직접 읽어옴으로서 visibility를 확보하게 된다.

public class Settings4 {

    private Settings4() { }

    private static class Settings4Holder {
        private static final Settings4 INSTANCE = new Settings4();
    }

    public static Settings4 getInstance() {
        return Settings4Holder.INSTANCE;
    }

}

getInstance가 호출되는 시점에만 Instance가 생겨나게 된다.

https://stackoverflow.com/questions/24538509/does-the-java-classloader-load-inner-classes

 

Does the Java ClassLoader load inner classes?

If I have a inner class declaration such as: Class A { public static class B { } } followed by: Class<?> implClass = getClass().getClassLoader().loadClass("A"); Will the A$B inner...

stackoverflow.com

여기에 보면 static inner class는 그냥 A.java에 우연히 정희된것일 뿐 컴파일하고 나면 완전히 다른 클래스로 컴파일된다.

classloader를 통해 로딩을 해도 outer class만 로딩 될뿐 static inner class는 로딩이 되지 않는다.


싱글톤 패턴 3부 - 싱글톤 패턴 구현 방법을 깨트리는 방법

Settings4 settings = Settings4.getInstance();
Constructor<Settings4> constructor = Settings4.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings4 settings1 = constructor.newInstance();

reflection api를 통해서 새로운 객체를 만들 수 있다.

try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
    out.writeObject(settings);
}

try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
    settings1 = (Settings5) in.readObject();
}

직렬화한 객체를 다시 역직렬화 해올때 새로운 객체를 만들게 할 수 있다.

public class Settings4 implements Serializable {

    private Settings4() { }

    private static class Settings4Holder {
        private static final Settings4 INSTANCE = new Settings4();
    }

    public static Settings4 getInstance() {
        return Settings4Holder.INSTANCE;
    }

    
    protected Object readResolve() {
        return getInstance();
    }
}

reflection은 못막지만 역직렬화는 readResolve 메소드를 통해서 막을 수 있다. Serializable interface에 정의 된것도 아닌데 내부적으로 역직렬화할때 호출되어 객체만드는것에 끼여들 수 있다.


싱글톤 패턴 4부 - 안전하고 단순하게 구현하는 방법

public enum Settings5 {
    INSTANCE;
}

자바에서는 Enum 은 reflection api newInstance로 새로운 객체를 만드는것을 허용하지 않는다.


팩터리 메소드 패턴 

ShipFactory 인터페이스가 존재하고 ShipFactory 실제 구현체들은 주입받는 형태로 Client단에서 사용하게 된다.

새로운 Ship이 추가될때마다. Factory도 추가 되지만 client는 코드의 변경없이 사용이 가능하다.(DI를 하면 없겠지만 안해주면 parameter를 변경을 해야할것이다 어떤 Ship을 생성할것인가에 대한 구분자를)

Spring에서는 BeanFactory 는 여러가지 종류의 ApplicationContext 구현체(BlackshipFactory,WhiteshipFactory) 를 가지고 있다. 해당 BeanFactory에서 getBean을 통해 여러 구체적인 구현체(WhiteShip,BlackShip)를 넘겨받는다.


추상 팩토리 패턴

Factory method pattern 을 client입장에서 보는것이다.

public class WhiteshipFactory extends DefaultShipFactory {

    @Override
    public Ship createShip() {
        Ship ship = new Whiteship();
        ship.setAnchor(new WhiteAnchor());
        ship.setWheel(new WhiteWheel());
        return ship;
    }
}

anchor , wheel 이 계속 변경될때마다. setter의 값을 변경을 해줘야한다. -> 확장에 열려있고 변경에 닫혀있길 원한다.

public class WhiteshipFactory extends DefaultShipFactory {

    private ShipPartsFactory shipPartsFactory;

    public WhiteshipFactory(ShipPartsFactory shipPartsFactory) {
        this.shipPartsFactory = shipPartsFactory;
    }

    @Override
    public Ship createShip() {
        Ship ship = new Whiteship();
        ship.setAnchor(shipPartsFactory.createAnchor());
        ship.setWheel(shipPartsFactory.createWheel());
        return ship;
    }
}

ShipPartsFactory는 인터페이스이고 createAnchor 와 createWheel 을 메소드로 가지고 있다.

client에서 ShipPartsFactory에 구현체를 주입받아서 brand 별로 다른 Anchor와 Wheel을 자연스럽게 교체가 가능하다. client는 실제 anchor, wheel 의 구현체는 신경쓸 필요가 없게 된것이다.


빌더 패턴

Director가 중간에 존재하여 매번 모든 빌더 메소드를 이용해서 하나하나 주입하는게 아니라 많이 쓰는 것들은 따로 미리 만들어 놓아서 client에게 제공해줄 수 있다.

public static void main(String[] args) {
    TourDirector director = new TourDirector(new DefaultTourBuilder());
    TourPlan tourPlan = director.cancunTrip();
    TourPlan tourPlan1 = director.longBeachTrip();
}
public class TourDirector {

    private TourPlanBuilder tourPlanBuilder;

    public TourDirector(TourPlanBuilder tourPlanBuilder) {
        this.tourPlanBuilder = tourPlanBuilder;
    }

    public TourPlan cancunTrip() {
        return tourPlanBuilder.title("칸쿤 여행")
                .nightsAndDays(2, 3)
                .startDate(LocalDate.of(2020, 12, 9))
                .whereToStay("리조트")
                .addPlan(0, "체크인하고 짐 풀기")
                .addPlan(0, "저녁 식사")
                .getPlan();
    }

    public TourPlan longBeachTrip() {
        return tourPlanBuilder.title("롱비치")
                .startDate(LocalDate.of(2021, 7, 15))
                .getPlan();
    }
}

마지막 build() == getPlan() 메소드 에서 제대로 객체가 생성될 수 있는지 검사할 수 있다.


프로토타입 패턴

어떤 객체가 생성되는데 오래걸릴때 (http 요청을 보내서 받아오거나) 아니면 원하는 field만 추출해서 쓰고 싶은 경우에 쓰이게 된다.

@Override
protected Object clone() throws CloneNotSupportedException {
    GithubRepository repository = new GithubRepository();
    repository.setUser(this.repository.getUser());
    repository.setName(this.repository.getName());

    GithubIssue githubIssue = new GithubIssue(repository);
    githubIssue.setId(this.id);
    githubIssue.setTitle(this.title);

    return githubIssue;
}

clone 메소드는 shallow copy이다.  현재 위 clone 메소드에서는 override해서 주입받은 GithubRepository를 새로 만들어서 이전 객체에 영향을 받고있지 않고 deep copy가 발생하게 한것이다. 

따로 override 하지않으면 내부적으로 참조하고 있는 객체는 그대로 참조만 하게 된다. 따로 직접 객체를 만들지 않는다.

자주 쓰이는 ModelMapper가 해당 패턴을 사용하고 있다 (reflection).

'WEB > Design Pattern' 카테고리의 다른 글

행동 관련 디자인 패턴  (1) 2023.01.29
구조 관련 디자인 패턴  (0) 2023.01.27