객체지향 설계를 하는 5가지 원칙 (SOLID)
(1) Factory Method
팩토리 패턴은 객체를 생성하는 인터페이스는 미리 정의하되, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 내리는 패턴입니다.
팩토리 패턴에서는 클래스의 인스턴스를 만드는 시점을 서브 클래스로 미룹니다.
package com.programmers.java.poly;
public interface Login {
void login();
}
Login 인터페이스를 슈퍼클래스라고 하고 kakaoLogin, NaverLogin 두 서브 클래스를 만들어 보자.
package com.programmers.java.poly;
public class KakaoLogin implements Login {
@Override
public void login() {
System.out.println("카카오로 로그인 합니다.");
}
}
package com.programmers.java.poly;
public class NaverLogin implements Login {
@Override
public void login() {
System.out.println("네이버로 로그인 합니다.");
}
}
이제 팩토리 클래스를 만들어 보자.
package com.programmers.java.poly;
public class LoginFactory {
public Login createLogin(String name){
switch( name ){
case "kakao": return new KakaoLogin();
case "naver": return new NaverLogin();
}
return null;
}
}
LoginFactory 클래스의 createLogin 메소드를 보면 name값이 "kakao"일 경우 kakaoLogin 클래스의 인스턴스를 "Naver"일 경우 Naver클래스의 인스턴스를 리턴한다.
이렇듯 팩토리 메소드 패턴을 사용하게 된다면 Application에서 Login의 구체(구현) 클래스에 의존하지 않고도 인스턴스를 생성/사용할 수 있게 된다.
즉, Login 클래스에 더 많은 Sub 클래스가 추가된다 할지라도 createLogin()를 통해 인스턴스를 제공받던 Application의 코드는 수정할 필요가 없게 되는 것이다!
package com.programmers.java.poly;
public class Main {
public static void main(String[] args) {
LoginFactory login = new LoginFactory();
login.createLogin("kakao").login();
login.createLogin("naver").login();
}
}
Effective Java의 빌더 패턴
규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라
객체 생성
점층적 생성자 패턴(telescoping constructor pattern) 소개
점층적 생성자 패턴의 대안으로 자바빈 패턴 소개
자바빈 패턴의 대안으로 빌더 패턴 소개
단점
(1) 인자가 추가되는 일이 발생하면 코드를 수정하기 어렵다.
(2) 코드 가독성이 떨어진다. -> 특히 인자 수가 많을 때 호출 코드만 봐서는 의미를 알기 어렵다.
장점
(1) 각 인자의 의미를 파악하기 쉽다.
(2) 복잡하게 여러 개의 생성자를 만들지 않아도 된다.
단점
(1) 객체 일관성(consistency)이 깨진다. -> 1회의 호출로 객체 생성이 끝나지 않음.
(2) setter 메서드가 있으므로 변경 불가능(immutable)클래스를 만들 수가 없다.
필수 매개변수만 가지고 있는 생성자를 호출해서 객체를 만든다.
// Effective Java의 Builder Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters(필수 인자)
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this; // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
싱글톤(Singleton) 이란 ?
싱글톤 패턴은 객체의 인스턴스가 오직 1개만 생성되는 패턴이다.
싱글톤 패턴을 구현하는 방법은 여러가지가 있지만, 여기서는 객체를 미리 생성해두고 가져오는 가장 단순하고 안전한 방법을 소개하겠다.
위와 같이 인스턴스를 오직 한 개로만 가져가면 어떤 이점이 있을까?
가장 먼저 떠올릴 수 있는 이점은 아무래도 메모리 측면일 것이다.
최초 한번의 new 연산자를 통해서 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다. 뿐만 아니라 이미 생성된 인스턴스를 활용하니 속도 측면에서도 이점이 있다고 볼 수 있다.
또다른 이점은 다른 클래스 간에 데이터 공유가 쉽다는 것이다. 싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다. 하지만 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 동시에 접근하게 되면 동시성 문제가 발생할 수 있으니 이점을 유의해서 설계하는 것이 좋다.